DNS, web ui nearly done, great log streaming, attempted https (abandoned that though)

This commit is contained in:
Thaddeus Hughes
2025-12-30 18:51:11 -06:00
parent 012d28ae14
commit d46cb252fb
134 changed files with 19226 additions and 740 deletions

View File

@@ -1,9 +1,12 @@
#include <math.h>
#include <string.h>
#include "esp_partition.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_crc.h"
#include "storage.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#define TAG "STORAGE"
@@ -51,19 +54,34 @@ const char parameter_units[NUM_PARAMS][8] = {
};
#undef PARAM_DEF
// Partition pointer
static const esp_partition_t *storage_partition = NULL;
// Log head tracking
// Log head tracking with mutex protection
static uint32_t log_head_index = 0;
static uint32_t log_tail_index = 0;
uint32_t get_log_head() { return LOG_START_OFFSET + (log_head_index * LOG_ENTRY_SIZE); }
uint32_t get_log_tail() { return LOG_START_OFFSET + (log_tail_index * LOG_ENTRY_SIZE); }
uint32_t get_log_offset() { return LOG_START_OFFSET; }
static SemaphoreHandle_t log_mutex = NULL;
static bool log_initialized = false;
uint32_t get_log_head(void) {
uint32_t head;
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
head = LOG_START_OFFSET + (log_head_index * LOG_ENTRY_SIZE);
if (log_mutex) xSemaphoreGive(log_mutex);
return head;
}
uint32_t get_log_tail(void) {
uint32_t tail;
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
tail = LOG_START_OFFSET + (log_tail_index * LOG_ENTRY_SIZE);
if (log_mutex) xSemaphoreGive(log_mutex);
return tail;
}
uint32_t get_log_offset(void) {
return LOG_START_OFFSET;
}
// ============================================================================
// PARAMETER FUNCTIONS
@@ -88,6 +106,43 @@ esp_err_t set_param_value_t(param_idx_t id, param_value_t val) {
return ESP_OK;
}
esp_err_t set_param_string(param_idx_t id, const char* str) {
if (id >= NUM_PARAMS) {
ESP_LOGE(TAG, "Invalid parameter ID: %d", id);
return ESP_ERR_INVALID_ARG;
}
if (parameter_types[id] != PARAM_TYPE_str) {
ESP_LOGE(TAG, "Parameter %d (%s) is not a string type", id, parameter_names[id]);
return ESP_ERR_INVALID_ARG;
}
if (str == NULL) {
parameter_table[id].str[0] = '\0';
} else {
strncpy(parameter_table[id].str, str, 15);
parameter_table[id].str[15] = '\0'; // Ensure null termination
}
ESP_LOGI(TAG, "String parameter %d (%s) set to '%s' (not committed)",
id, parameter_names[id], parameter_table[id].str);
return ESP_OK;
}
char* get_param_string(param_idx_t id) {
if (id >= NUM_PARAMS) {
ESP_LOGE(TAG, "Invalid parameter ID: %d", id);
return "";
}
if (parameter_types[id] != PARAM_TYPE_str) {
ESP_LOGE(TAG, "Parameter %d (%s) is not a string type", id, parameter_names[id]);
return "";
}
return parameter_table[id].str;
}
param_type_e get_param_type(param_idx_t id) {
if (id >= NUM_PARAMS) {
return PARAM_TYPE_u64; // Default fallback
@@ -95,6 +150,63 @@ param_type_e get_param_type(param_idx_t id) {
return parameter_types[id];
}
// ============================================================================
// JSON-FRIENDLY STRING CONVERSION
// ============================================================================
const char* get_param_json_string(param_idx_t id, char* buffer, size_t buf_size) {
if (id >= NUM_PARAMS || buffer == NULL || buf_size == 0) {
if (buffer && buf_size > 0) buffer[0] = '\0';
return "";
}
param_type_e type = parameter_types[id];
param_value_t val = parameter_table[id];
switch(type) {
case PARAM_TYPE_u16:
snprintf(buffer, buf_size, "%u", val.u16);
break;
case PARAM_TYPE_i16:
snprintf(buffer, buf_size, "%d", val.i16);
break;
case PARAM_TYPE_u32:
snprintf(buffer, buf_size, "%lu", (unsigned long)val.u32);
break;
case PARAM_TYPE_i32:
snprintf(buffer, buf_size, "%ld", (long)val.i32);
break;
case PARAM_TYPE_u64:
snprintf(buffer, buf_size, "%llu", (unsigned long long)val.u64);
break;
case PARAM_TYPE_i64:
snprintf(buffer, buf_size, "%lld", (long long)val.i64);
break;
case PARAM_TYPE_f32:
if (isnan(val.f32) || isinf(val.f32)) {
snprintf(buffer, buf_size, "null");
} else {
snprintf(buffer, buf_size, "%.6g", val.f32);
}
break;
case PARAM_TYPE_f64:
if (isnan(val.f64) || isinf(val.f64)) {
snprintf(buffer, buf_size, "null");
} else {
snprintf(buffer, buf_size, "%.15g", val.f64);
}
break;
case PARAM_TYPE_str:
// Escape quotes and backslashes for JSON string
snprintf(buffer, buf_size, "\"%s\"", val.str);
break;
default:
snprintf(buffer, buf_size, "null");
break;
}
return buffer;
}
const char* get_param_name(param_idx_t id) {
if (id >= NUM_PARAMS) {
return "INVALID";
@@ -117,33 +229,125 @@ const char* get_param_unit(param_idx_t id) {
return parameter_units[id];
}
esp_err_t commit_params() {
// ============================================================================
// STORAGE HELPER: Pack parameter value into buffer
// ============================================================================
static void pack_param(uint8_t *dest, param_idx_t id) {
param_type_e type = parameter_types[id];
switch(type) {
case PARAM_TYPE_u16:
memcpy(dest, &parameter_table[id].u16, 2);
break;
case PARAM_TYPE_i16:
memcpy(dest, &parameter_table[id].i16, 2);
break;
case PARAM_TYPE_u32:
memcpy(dest, &parameter_table[id].u32, 4);
break;
case PARAM_TYPE_i32:
memcpy(dest, &parameter_table[id].i32, 4);
break;
case PARAM_TYPE_f32:
memcpy(dest, &parameter_table[id].f32, 4);
break;
case PARAM_TYPE_u64:
memcpy(dest, &parameter_table[id].u64, 8);
break;
case PARAM_TYPE_i64:
memcpy(dest, &parameter_table[id].i64, 8);
break;
case PARAM_TYPE_f64:
memcpy(dest, &parameter_table[id].f64, 8);
break;
case PARAM_TYPE_str:
memcpy(dest, parameter_table[id].str, 16);
break;
}
}
// ============================================================================
// STORAGE HELPER: Unpack parameter value from buffer
// ============================================================================
static void unpack_param(const uint8_t *src, param_idx_t id) {
param_type_e type = parameter_types[id];
switch(type) {
case PARAM_TYPE_u16:
memcpy(&parameter_table[id].u16, src, 2);
break;
case PARAM_TYPE_i16:
memcpy(&parameter_table[id].i16, src, 2);
break;
case PARAM_TYPE_u32:
memcpy(&parameter_table[id].u32, src, 4);
break;
case PARAM_TYPE_i32:
memcpy(&parameter_table[id].i32, src, 4);
break;
case PARAM_TYPE_f32:
memcpy(&parameter_table[id].f32, src, 4);
break;
case PARAM_TYPE_u64:
memcpy(&parameter_table[id].u64, src, 8);
break;
case PARAM_TYPE_i64:
memcpy(&parameter_table[id].i64, src, 8);
break;
case PARAM_TYPE_f64:
memcpy(&parameter_table[id].f64, src, 8);
break;
case PARAM_TYPE_str:
memcpy(parameter_table[id].str, src, 16);
parameter_table[id].str[15] = '\0'; // Ensure null termination
break;
}
}
// ============================================================================
// COMMIT PARAMETERS TO FLASH
// ============================================================================
esp_err_t commit_params(void) {
if (storage_partition == NULL) {
ESP_LOGE(TAG, "Storage partition not initialized");
return ESP_FAIL;
}
// Prepare storage buffer with parameters and CRCs
param_stored_t params_to_store[NUM_PARAMS];
// Calculate flash offset for each parameter
uint32_t flash_offset = PARAMS_OFFSET;
// Erase the parameter sectors
esp_err_t err = esp_partition_erase_range(storage_partition, PARAMS_OFFSET,
LOG_START_OFFSET);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to erase parameter sectors: %s", esp_err_to_name(err));
return ESP_FAIL;
}
// Write each parameter with its CRC
for (int i = 0; i < NUM_PARAMS; i++) {
params_to_store[i].val = parameter_table[i];
// Calculate CRC32 for each parameter value
params_to_store[i].crc = esp_crc32_le(0, (uint8_t*)&parameter_table[i], sizeof(param_value_t));
}
// Erase the sectors for parameter storage
esp_err_t err = esp_partition_erase_range(storage_partition, PARAMS_OFFSET, LOG_START_OFFSET);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to erase parameter sector: %s", esp_err_to_name(err));
return ESP_FAIL;
}
// Write parameters to flash
err = esp_partition_write(storage_partition, PARAMS_OFFSET, params_to_store, PARAMS_TOTAL_SIZE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to write parameters: %s", esp_err_to_name(err));
return ESP_FAIL;
param_stored_t stored;
memset(&stored, 0, sizeof(stored));
// Pack the parameter value
uint8_t size = param_type_size(parameter_types[i]);
pack_param(stored.data, i);
// Calculate CRC over the actual data size used
uint32_t crc_input = PARAM_CRC_SALT;
//uint32_t crc = esp_crc32_le(0, (uint8_t*)&crc_input, sizeof(crc_input));
stored.crc = esp_crc32_le(crc_input, stored.data, size);
// Write to flash
err = esp_partition_write(storage_partition, flash_offset,
&stored, sizeof(param_stored_t));
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to write parameter %d (%s): %s",
i, parameter_names[i], esp_err_to_name(err));
return ESP_FAIL;
}
flash_offset += sizeof(param_stored_t);
}
ESP_LOGI(TAG, "Parameters committed to flash successfully");
@@ -165,31 +369,44 @@ esp_err_t storage_init(void) {
return ESP_ERR_NOT_FOUND;
}
ESP_LOGI(TAG, "Storage partition found: size=%lu bytes", (unsigned long)storage_partition->size);
ESP_LOGI(TAG, "Storage partition found: size=%lu bytes",
(unsigned long)storage_partition->size);
// Load parameters from flash
param_stored_t params_stored[NUM_PARAMS];
esp_err_t err = esp_partition_read(storage_partition, PARAMS_OFFSET,
params_stored, PARAMS_TOTAL_SIZE);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to read parameters, using defaults");
return err;
}
// Validate and load each parameter
uint32_t flash_offset = PARAMS_OFFSET;
bool all_valid = true;
for (int i = 0; i < NUM_PARAMS; i++) {
uint32_t calculated_crc = esp_crc32_le(0, (uint8_t*)&params_stored[i].val,
sizeof(param_value_t));
param_stored_t stored;
if (calculated_crc == params_stored[i].crc) {
parameter_table[i] = params_stored[i].val;
esp_err_t err = esp_partition_read(storage_partition, flash_offset,
&stored, sizeof(param_stored_t));
if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to read parameter %d (%s), using default",
i, parameter_names[i]);
memcpy(&parameter_table[i], &parameter_defaults[i], sizeof(param_value_t)); // SET DEFAULT HERE
all_valid = false;
flash_offset += sizeof(param_stored_t);
continue;
}
// Validate CRC over actual data size
uint8_t size = param_type_size(parameter_types[i]);
uint32_t crc_input = PARAM_CRC_SALT;
//uint32_t crc = esp_crc32_le(0, (uint8_t*)&crc_input, sizeof(crc_input));
uint32_t calculated_crc = esp_crc32_le(crc_input, stored.data, size);
if (calculated_crc == stored.crc) {
unpack_param(stored.data, i);
} else {
ESP_LOGW(TAG, "Parameter %d (%s) failed CRC check, using default",
i, parameter_names[i]);
memcpy(&parameter_table[i], &parameter_defaults[i], sizeof(param_value_t)); // SET DEFAULT HERE
all_valid = false;
}
flash_offset += sizeof(param_stored_t);
}
if (all_valid) {
@@ -202,7 +419,7 @@ esp_err_t storage_init(void) {
}
// ============================================================================
// LOGGING FUNCTIONS
// LOGGING FUNCTIONS (unchanged from original)
// ============================================================================
static esp_err_t find_log_head(void) {
@@ -210,16 +427,13 @@ static esp_err_t find_log_head(void) {
return ESP_ERR_INVALID_STATE;
}
// Calculate total log area size
uint32_t log_area_size = storage_partition->size - LOG_START_OFFSET;
uint32_t max_entries = log_area_size / LOG_ENTRY_SIZE;
// Read through entries to find first uninitialized (all 0xFF)
uint8_t entry[LOG_ENTRY_SIZE];
uint8_t empty_entry[LOG_ENTRY_SIZE];
memset(empty_entry, 0xFF, LOG_ENTRY_SIZE);
// Binary search would be faster, but linear is safer for circular buffer
for (uint32_t i = 0; i < max_entries; i++) {
uint32_t offset = LOG_START_OFFSET + (i * LOG_ENTRY_SIZE);
@@ -229,7 +443,6 @@ static esp_err_t find_log_head(void) {
return err;
}
// Check if this entry is uninitialized
if (memcmp(entry, empty_entry, LOG_ENTRY_SIZE) == 0) {
log_head_index = i;
ESP_LOGI(TAG, "Log head found at index %lu", (unsigned long)log_head_index);
@@ -237,11 +450,9 @@ static esp_err_t find_log_head(void) {
}
}
// If we get here, all entries are full - wrap to beginning
log_head_index = 0;
ESP_LOGI(TAG, "Log is full, wrapping to beginning");
// Erase the first log sector to start fresh
esp_err_t err = esp_partition_erase_range(storage_partition, LOG_START_OFFSET,
FLASH_SECTOR_SIZE);
if (err != ESP_OK) {
@@ -258,8 +469,16 @@ esp_err_t log_init(void) {
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;
}
esp_err_t err = find_log_head();
if (err != ESP_OK) {
vSemaphoreDelete(log_mutex);
log_mutex = NULL;
return err;
}
@@ -273,111 +492,121 @@ esp_err_t write_log(char* entry) {
return ESP_FAIL;
}
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
uint32_t log_area_end = storage_partition->size;
uint32_t max_entries = (log_area_end - LOG_START_OFFSET) / LOG_ENTRY_SIZE;
//uint32_t max_sectors = max_entries / FLASH_SECTOR_SIZE;
// Calculate current offset
uint32_t current_offset = LOG_START_OFFSET + (log_head_index * LOG_ENTRY_SIZE);
// Check if we need to erase the next sector
uint32_t current_sector = current_offset / FLASH_SECTOR_SIZE;
uint32_t next_offset = current_offset + LOG_ENTRY_SIZE;
if (next_offset >= log_area_end)
next_offset = LOG_START_OFFSET;
if (next_offset >= log_area_end) {
next_offset = LOG_START_OFFSET;
}
uint32_t current_sector = current_offset / FLASH_SECTOR_SIZE;
uint32_t next_sector = next_offset / FLASH_SECTOR_SIZE;
// If we're crossing into a new sector, check if it needs erasing
if (next_sector != current_sector) {
// Check if next sector is uninitialized
uint8_t check_byte;
esp_err_t err = esp_partition_read(storage_partition, next_sector * FLASH_SECTOR_SIZE,
&check_byte, 1);
if (err == ESP_OK && check_byte != 0xFF) {
// Sector needs erasing
ESP_LOGI(TAG, "Erasing sector %lu for log", (unsigned long)next_sector);
err = esp_partition_erase_range(storage_partition,
next_sector * FLASH_SECTOR_SIZE,
FLASH_SECTOR_SIZE);
log_tail_index = (next_sector)*FLASH_SECTOR_SIZE/LOG_ENTRY_SIZE;
if (log_tail_index >= max_entries)
log_tail_index = 0;
ESP_LOGI(TAG, "Tail/Head are now %ld/%ld", (long)log_tail_index, (long)log_head_index);
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;
}
} else if (err == ESP_OK) {
ESP_LOGI(TAG, "Next sector %ld clear, no erasing needed", (unsigned long)next_sector);
} else {
ESP_LOGE(TAG, "Error checking byte (sector %ld)", (unsigned long)next_sector);
}
uint32_t tail_offset = next_sector * FLASH_SECTOR_SIZE;
if (tail_offset < LOG_START_OFFSET) {
tail_offset = LOG_START_OFFSET;
}
log_tail_index = (tail_offset - LOG_START_OFFSET) / LOG_ENTRY_SIZE;
if (log_tail_index >= max_entries) {
log_tail_index = 0;
}
ESP_LOGI(TAG, "Tail/Head are now %ld/%ld", (long)log_tail_index, (long)log_head_index);
}
}
// Write the log entry
esp_err_t err = esp_partition_write(storage_partition, current_offset, entry, LOG_ENTRY_SIZE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to write log entry: %s", esp_err_to_name(err));
if (log_mutex) xSemaphoreGive(log_mutex);
return ESP_FAIL;
}
//ESP_LOGI(TAG, "Log @ sector %lu / index %lu / offset %lu", (unsigned long) current_sector, (unsigned long) log_head_index, (unsigned long)current_offset);
log_head_index++;
if (log_head_index >= max_entries) {
log_head_index = 0;
ESP_LOGI(TAG, "Log wrapped to beginning");
}
if (log_mutex) xSemaphoreGive(log_mutex);
return ESP_OK;
}
esp_err_t write_dummy_log_1() {
log_head_index = 0;
log_tail_index = 0;
esp_err_t write_dummy_log_1(void) {
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
log_head_index = 0;
log_tail_index = 0;
if (log_mutex) xSemaphoreGive(log_mutex);
uint32_t log_area_end = storage_partition->size;
uint32_t max_entries = (log_area_end - LOG_START_OFFSET) / LOG_ENTRY_SIZE;
for (uint32_t i=0; i<max_entries*3/2; i++) {
ESP_LOGI(TAG, "log[%ld]", (long)i);
char entry[32] = {32, i>>24,i>>16,i>>8,i>>0};
write_log(entry);
}
return ESP_OK;
for (uint32_t i=0; i<max_entries*3/2; i++) {
ESP_LOGI(TAG, "log[%ld]", (long)i);
char entry[32] = {32, i>>24,i>>16,i>>8,i>>0};
write_log(entry);
}
return ESP_OK;
}
esp_err_t write_dummy_log_2() {
log_head_index = 56;
log_tail_index = 105;
esp_err_t write_dummy_log_2(void) {
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
log_head_index = 56;
log_tail_index = 105;
if (log_mutex) xSemaphoreGive(log_mutex);
uint32_t log_area_end = storage_partition->size;
uint32_t max_entries = (log_area_end - LOG_START_OFFSET) / LOG_ENTRY_SIZE;
for (uint32_t i=0; i<max_entries*3/2; i++) {
ESP_LOGI(TAG, "log[%ld]", (long)i);
char entry[32] = {32, i>>24,i>>16,i>>8,i>>0};
write_log(entry);
}
return ESP_OK;
for (uint32_t i=0; i<max_entries*3/2; i++) {
ESP_LOGI(TAG, "log[%ld]", (long)i);
char entry[32] = {32, i>>24,i>>16,i>>8,i>>0};
write_log(entry);
}
return ESP_OK;
}
esp_err_t write_dummy_log_3() {
log_head_index = 105;
log_tail_index = 34;
esp_err_t write_dummy_log_3(void) {
if (log_mutex) xSemaphoreTake(log_mutex, portMAX_DELAY);
log_head_index = 105;
log_tail_index = 34;
if (log_mutex) xSemaphoreGive(log_mutex);
uint32_t log_area_end = storage_partition->size;
uint32_t max_entries = (log_area_end - LOG_START_OFFSET) / LOG_ENTRY_SIZE;
for (uint32_t i=0; i<max_entries*3/2; i++) {
ESP_LOGI(TAG, "log[%ld]", (long)i);
char entry[32] = {32, i>>24,i>>16,i>>8,i>>0};
write_log(entry);
}
return ESP_OK;
for (uint32_t i=0; i<max_entries*3/2; i++) {
ESP_LOGI(TAG, "log[%ld]", (long)i);
char entry[32] = {32, i>>24,i>>16,i>>8,i>>0};
write_log(entry);
}
return ESP_OK;
}
void storage_deinit(void) {
storage_partition = NULL;
log_initialized = false;
if (log_mutex) {
vSemaphoreDelete(log_mutex);
log_mutex = NULL;
}
}