/* * filemgmt.c * * Created on: Nov 20, 2025 * Author: Thad */ #include "filemgmt.h" #include "endian.h" #include "esp_log.h" #include #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_system.h" #include "esp_mac.h" // for MACSTR #include "esp_wifi.h" #include "esp_event.h" #include "esp_err.h" #include "esp_log.h" #include "esp_vfs.h" #include "nvs_flash.h" #include "esp_vfs_fat.h" #include "esp_littlefs.h" #include "ftp.h" #include "control_fsm.h" #include "power_mgmt.h" #include "rtc.h" #include "sensors.h" const char* partition_label = "storage"; const char *TAG = "MAIN"; const char *mount_point = "/root"; RTC_DATA_ATTR bool find_new_filename = true; void start_new_log_file() { find_new_filename = true; } EventGroupHandle_t xEventTask; int FTP_TASK_FINISH_BIT = BIT2; esp_err_t start_filesystem() { ESP_LOGI(TAG, "Initializing LittleFS on Builtin SPI Flash Memory"); esp_vfs_littlefs_conf_t conf = { .base_path = mount_point, .partition_label = partition_label, .format_if_mount_failed = true, .dont_mount = false, }; // Use settings defined above to initialize and mount LittleFS filesystem. // Note: esp_vfs_littlefs_register is an all-in-one convenience function. esp_err_t ret = esp_vfs_littlefs_register(&conf); if (ret != ESP_OK) { if (ret == ESP_FAIL) { ESP_LOGE(TAG, "Failed to mount or format filesystem"); } else if (ret == ESP_ERR_NOT_FOUND) { ESP_LOGE(TAG, "Failed to find LittleFS partition"); } else { ESP_LOGE(TAG, "Failed to initialize LittleFS (%s)", esp_err_to_name(ret)); } return ret; } size_t total = 0, used = 0; ret = esp_littlefs_info(conf.partition_label, &total, &used); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to get LittleFS partition information (%s)", esp_err_to_name(ret)); //esp_littlefs_format(conf.partition_label); return ret; } ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); ESP_LOGI(TAG, "Mount LittleFS on %s", mount_point); return ret; } static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_id == WIFI_EVENT_AP_STACONNECTED) { wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data; ESP_LOGI(TAG, "station "MACSTR" join, AID=%d", MAC2STR(event->mac), event->aid); } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) { wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data; ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d", MAC2STR(event->mac), event->aid); } } extern void ftp_task (void *pvParameters); // Start WiFi AP and FTP Server // filemgmt.c esp_err_t start_ftp_server(void) { ESP_LOGI("FTP", "START"); close_current_log(); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); // THIS IS THE CORRECT WAY – recreate the default AP netif every time esp_netif_create_default_wifi_ap(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL)); wifi_config_t wifi_config = { .ap = { .ssid = ESP_WIFI_AP_SSID, .ssid_len = strlen(ESP_WIFI_AP_SSID), .password = ESP_WIFI_AP_PASSWORD, .max_connection = ESP_WIFI_AP_N_CONNECTIONS, .authmode = strlen(ESP_WIFI_AP_PASSWORD) ? WIFI_AUTH_WPA_WPA2_PSK : WIFI_AUTH_OPEN }, }; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); ESP_LOGI(TAG, "WiFi AP started – IP 192.168.4.1"); xEventTask = xEventGroupCreate(); xTaskCreate(ftp_task, "FTP", 1024*6, NULL, 2, NULL); return ESP_OK; } void stop_ftp_server(void) { ESP_LOGI("FTP", "OFF"); ftp_terminate(); vTaskDelay(pdMS_TO_TICKS(500)); // give task time to die esp_wifi_stop(); esp_wifi_deinit(); // This single call does everything correctly: // Destroy default Wi-Fi interface (handles AP) esp_netif_t *ap_netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"); if (ap_netif) { esp_netif_destroy_default_wifi(ap_netif); // Correct API for AP } esp_event_loop_delete_default(); esp_netif_deinit(); ESP_LOGI("FTP", "WiFi + FTP completely stopped – safe to restart"); } void stop_filesystem() { esp_vfs_littlefs_unregister(partition_label); ESP_LOGI(TAG, "LittleFS unmounted"); } #define LOG_ENTRY_SIZE 32 #define MAX_LOG_FILES 999 // rotate after N files #define ENTRIES_PER_FILE 8000 // ~256 KB per file → safe static FILE* logfile = NULL; RTC_DATA_ATTR char current_log_path[32] = "/root/log000.bin"; static uint32_t current_entry_count = 0; // Helper: close current file safely void close_current_log(void) { if (logfile) { fflush(logfile); fsync(fileno(logfile)); // CRITICAL: forces write to flash fclose(logfile); logfile = NULL; current_entry_count = 0; } } // Helper: open next log file (rotates 000→009) bool open_next_log_file(void) { close_current_log(); if (find_new_filename) { // Find next filename for (int i = 0; i < MAX_LOG_FILES; i++) { snprintf(current_log_path, sizeof(current_log_path), "/root/log%03d.bin", i); struct stat st; if (stat(current_log_path, &st) != 0) { break; // file doesn't exist → use this one } if (i == MAX_LOG_FILES-1) { ESP_LOGE("FILE", "RAN OUT OF FILES, GOING TO MAX"); // All files exist → overwrite oldest (log000.bin) strcpy(current_log_path, "/root/log999.bin"); } } logfile = fopen(current_log_path, "ab"); // binary append if (!logfile) { ESP_LOGE("FILE", "Failed to open %s", current_log_path); return false; } // Optional: truncate if too big (safety) fseek(logfile, 0, SEEK_END); if (ftell(logfile) > ENTRIES_PER_FILE * LOG_ENTRY_SIZE) { fclose(logfile); logfile = fopen(current_log_path, "wb"); // truncate } find_new_filename = false; } else { logfile = fopen(current_log_path, "ab"); } ESP_LOGI("FILE", "Logging to %s", current_log_path); return true; } void file_log() { if (!logfile && !open_next_log_file()) { return; } char entry[LOG_ENTRY_SIZE] = {0}; entry[0] = LOG_ENTRY_SIZE; // pack 64-bit timestamp into bytes 1-8 uint64_t be_timestamp = htobe64(rtc_time_ms()); memcpy(&entry[1], &be_timestamp, 8); // pack 32-bit voltages/currents into bytes 9-24 int32_t be_voltage = htobe32(get_battery_mV()); memcpy(&entry[9], &be_voltage, 4); int32_t be_current1 = htobe32(get_bridge_mA(BRIDGE_DRIVE)); memcpy(&entry[13], &be_current1, 4); int32_t be_current2 = htobe32(get_bridge_mA(BRIDGE_JACK)); memcpy(&entry[17], &be_current2, 4); int32_t be_current3 = htobe32(get_bridge_mA(BRIDGE_AUX)); memcpy(&entry[21], &be_current3, 4); int32_t be_counter = htobe32(get_sensor_counter(SENSOR_DRIVE)); memcpy(&entry[25], &be_counter, 4); entry[29] = get_sensor(SENSOR_DRIVE); entry[30] = get_sensor(SENSOR_JACK); entry[31] = fsm_get_state(); ESP_LOGI("FILE", "LOGGING TO %s: 0x %02x %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x %02x %02x", current_log_path, entry[0], entry[1], entry[2], entry[3], entry[4], entry[5], entry[6], entry[7], entry[8], entry[9], entry[10], entry[11], entry[12], entry[13], entry[14], entry[15], entry[16], entry[17], entry[18], entry[19], entry[20], entry[21], entry[22], entry[23], entry[24], entry[25], entry[26], entry[27], entry[28], entry[29], entry[30], entry[31]); /* SEND TO FILE */ size_t written = fwrite(entry, 1, LOG_ENTRY_SIZE, logfile); if (written != LOG_ENTRY_SIZE) { ESP_LOGE("FILE", "Partial write! Closing file."); close_current_log(); return; } current_entry_count++; // Periodic flush (every 50–100 entries) + full sync every 500 if (current_entry_count % 100 == 0) { fflush(logfile); } if (current_entry_count % 500 == 0) { fsync(fileno(logfile)); } // Rotate if getting large if (current_entry_count >= ENTRIES_PER_FILE) { ESP_LOGI("FILE", "Rotating log file"); open_next_log_file(); } }