feat: implement LittleFSHandler and FSGuard for improved file management

This commit is contained in:
GW_MC
2026-01-30 15:23:44 +08:00
parent b6c4477c46
commit 2a5088bec3
7 changed files with 821 additions and 85 deletions

View File

@@ -8,16 +8,27 @@
#include "common/semaphore_guard.h"
#include "cJSON.h"
static const char* TAG = "WifiHandler";
static const char* WIFI_SSID_KEY = "ssid";
static const char* WIFI_PASSWORD_STORE_KEY = "psw";
#define TAG "WifiHandler"
#define WIFI_CRED_FILE_PATH "wifi_credentials.json"
/*
* WiFi Credentials JSON Structure:
* {
* "current_ssid": "MyWiFi",
* "credentials": {
* "MyWiFi": {
* "password": "mypassword123"
* },
* "OtherNetwork": {
* "password": "otherpass456"
* }
* }
* }
*/
WifiHandler::WifiHandler(
// this handler is used to store/retrieve WiFi credentials
// should have a unique namespace for WiFi credentials
// it will be owned by WifiHandler and deleted in its destructor
std::unique_ptr<KVStorageHandler> kvs
) : kvs(std::move(kvs)) {
std::shared_ptr<LittleFSHandler> fs_handler_
) : fs_handler_(std::move(fs_handler_)) {
this->s_wifi_event_group = xEventGroupCreate();
if (!this->s_wifi_event_group) {
ESP_LOGE(TAG, "Failed to create WiFi event group");
@@ -34,10 +45,13 @@ WifiHandler::WifiHandler(
if (!this->credential_mutex) {
ESP_LOGE(TAG, "Failed to create credential mutex");
}
if (this->kvs == nullptr) {
ESP_LOGW(TAG, "KVStorageHandler is null, WiFi credentials will not be stored");
if (this->fs_handler_ == nullptr) {
ESP_LOGW(TAG, "FSHandler is null, WiFi credentials will not be stored");
} else {
this->kvs->init(nullptr);
esp_err_t err = this->fs_handler_->init("/littlefs");
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize FSHandler: %s", esp_err_to_name(err));
}
}
}
@@ -55,8 +69,8 @@ WifiHandler::~WifiHandler() {
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiHandler::wifi_event_handler);
esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &WifiHandler::wifi_event_handler);
this->initialized = false;
// unique_ptr will automatically delete the object
this->kvs = nullptr;
//
this->fs_handler_ = nullptr;
}
}
@@ -323,8 +337,8 @@ void WifiHandler::wifi_event_handler(void* arg, esp_event_base_t event_base, int
//
void WifiHandler::store_wifi_credentials(const std::string& ssid, const std::string& password) {
if (!kvs) {
ESP_LOGW(TAG, "KVStorageHandler not set, cannot store WiFi credentials");
if (!fs_handler_) {
ESP_LOGW(TAG, "FSHandler not set, cannot store WiFi credentials");
return;
}
SemaphoreGuard guard(this->credential_mutex);
@@ -334,36 +348,52 @@ void WifiHandler::store_wifi_credentials(const std::string& ssid, const std::str
return;
}
// Store current SSID
kvs->put(WIFI_SSID_KEY, ssid);
// Store the password according to the JSON structure
std::string password_key_store = kvs->get(WIFI_PASSWORD_STORE_KEY);
cJSON* json = nullptr;
if (password_key_store.empty()) {
// create new JSON object
json = cJSON_CreateObject();
} else {
// parse existing JSON
json = cJSON_Parse(password_key_store.c_str());
if (json == nullptr) {
ESP_LOGE(TAG, "Failed to parse existing WiFi password JSON, creating new");
json = cJSON_CreateObject();
// Try to read existing credentials file
if (fs_handler_->file_exists(WIFI_CRED_FILE_PATH)) {
// Read existing file
size_t file_size = 0;
esp_err_t err = fs_handler_->get_file_size(WIFI_CRED_FILE_PATH, file_size);
if (err == ESP_OK && file_size > 0) {
std::vector<uint8_t> file_data(file_size + 1); // +1 for null terminator
size_t bytes_read = 0;
err = fs_handler_->read_file(WIFI_CRED_FILE_PATH, file_size, file_data.data(), bytes_read);
if (err == ESP_OK) {
file_data[bytes_read] = '\0'; // Null terminate
json = cJSON_Parse(reinterpret_cast<const char*>(file_data.data()));
if (json == nullptr) {
ESP_LOGE(TAG, "Failed to parse existing WiFi credentials JSON, creating new");
}
}
}
}
// Create new JSON if parsing failed or file doesn't exist
if (json == nullptr) {
json = cJSON_CreateObject();
}
// Set current SSID
cJSON* current_ssid_item = cJSON_GetObjectItem(json, "current_ssid");
if (current_ssid_item != nullptr) {
cJSON_SetValuestring(current_ssid_item, ssid.c_str());
} else {
cJSON_AddStringToObject(json, "current_ssid", ssid.c_str());
}
// Get or create credentials object
cJSON* credentials = cJSON_GetObjectItem(json, "credentials");
if (credentials == nullptr || !cJSON_IsObject(credentials)) {
// create credentials object if it doesn't exist
credentials = cJSON_CreateObject();
cJSON_AddItemToObject(json, "credentials", credentials);
}
// Limit stored credentials to prevent NVS overflow (keep max 10 SSIDs)
// Limit stored credentials to prevent excessive file size (keep max 10 SSIDs)
int credential_count = cJSON_GetArraySize(credentials);
if (credential_count >= 10) {
ESP_LOGW(TAG, "Too many stored credentials (%d), clearing old ones", credential_count);
// Keep only the current SSID's credentials, clear others
cJSON_DeleteItemFromObject(credentials, ssid.c_str()); // Remove if exists
ESP_LOGW(TAG, "Too many stored credentials (%d), keeping only current SSID", credential_count);
// Keep only the current SSID's credentials
cJSON* new_credentials = cJSON_CreateObject();
cJSON_ReplaceItemInObject(json, "credentials", new_credentials);
credentials = new_credentials;
@@ -372,20 +402,27 @@ void WifiHandler::store_wifi_credentials(const std::string& ssid, const std::str
// Remove existing entry for this SSID to update it
cJSON_DeleteItemFromObject(credentials, ssid.c_str());
// create SSID object
// Create SSID object with password
cJSON* ssid_item = cJSON_CreateObject();
// add password field
cJSON_AddStringToObject(ssid_item, "password", password.c_str());
// add SSID object to credentials
cJSON_AddItemToObject(credentials, ssid.c_str(), ssid_item);
// store updated JSON string
char* updated_json_str = cJSON_PrintUnformatted(json);
if (updated_json_str) {
esp_err_t err = ESP_OK;
kvs->put(WIFI_PASSWORD_STORE_KEY, std::string(updated_json_str));
// Note: Error handling is done in nvs_handler.cpp put() method
cJSON_free(updated_json_str);
// Serialize and write to file
char* json_str = cJSON_PrintUnformatted(json);
if (json_str) {
size_t bytes_written = 0;
esp_err_t err = fs_handler_->write_file(
WIFI_CRED_FILE_PATH,
reinterpret_cast<const uint8_t*>(json_str),
strlen(json_str),
bytes_written
);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to write WiFi credentials to file: %s", esp_err_to_name(err));
} else {
ESP_LOGI(TAG, "Stored WiFi credentials for SSID: %s", ssid.c_str());
}
cJSON_free(json_str);
} else {
ESP_LOGE(TAG, "Failed to serialize WiFi credentials JSON");
}
@@ -393,59 +430,104 @@ void WifiHandler::store_wifi_credentials(const std::string& ssid, const std::str
}
void WifiHandler::get_wifi_credentials(std::string& out_ssid, std::string& out_password) {
if (!kvs) {
ESP_LOGW(TAG, "KVStorageHandler not set, cannot get WiFi credentials");
return;
}
SemaphoreGuard guard(this->credential_mutex);
// wait up to 5 seconds to take the mutex
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
ESP_LOGE(TAG, "Failed to take credential mutex");
return;
}
out_ssid = kvs->get(WIFI_SSID_KEY);
if (out_ssid.empty()) {
if (!fs_handler_) {
ESP_LOGW(TAG, "FSHandler not set, cannot get WiFi credentials");
out_ssid = "";
out_password = "";
return;
}
// password is from KV storage, may be nullptr
std::string password_key_store = kvs->get(WIFI_PASSWORD_STORE_KEY);
if (password_key_store.empty()) {
SemaphoreGuard guard(this->credential_mutex);
// wait up to 5 seconds to take the mutex
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
ESP_LOGE(TAG, "Failed to take credential mutex");
out_ssid = "";
out_password = "";
return;
}
// parse from json
cJSON* json = cJSON_Parse(password_key_store.c_str());
// Check if credentials file exists
if (!fs_handler_->file_exists(WIFI_CRED_FILE_PATH)) {
ESP_LOGD(TAG, "WiFi credentials file does not exist");
out_ssid = "";
out_password = "";
return;
}
// Read credentials file
size_t file_size = 0;
esp_err_t err = fs_handler_->get_file_size(WIFI_CRED_FILE_PATH, file_size);
if (err != ESP_OK || file_size == 0) {
ESP_LOGE(TAG, "Failed to get WiFi credentials file size");
out_ssid = "";
out_password = "";
return;
}
std::vector<uint8_t> file_data(file_size + 1); // +1 for null terminator
size_t bytes_read = 0;
err = fs_handler_->read_file(WIFI_CRED_FILE_PATH, file_size, file_data.data(), bytes_read);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read WiFi credentials file: %s", esp_err_to_name(err));
out_ssid = "";
out_password = "";
return;
}
file_data[bytes_read] = '\0'; // Null terminate
// Parse JSON
cJSON* json = cJSON_Parse(reinterpret_cast<const char*>(file_data.data()));
if (json == nullptr) {
ESP_LOGE(TAG, "Failed to parse WiFi password JSON");
ESP_LOGE(TAG, "Failed to parse WiFi credentials JSON");
out_ssid = "";
out_password = "";
return;
}
// Get current SSID
cJSON* current_ssid_item = cJSON_GetObjectItem(json, "current_ssid");
if (current_ssid_item == nullptr || !cJSON_IsString(current_ssid_item)) {
ESP_LOGE(TAG, "WiFi credentials JSON does not contain valid 'current_ssid' field");
out_ssid = "";
out_password = "";
cJSON_Delete(json);
return;
}
out_ssid = current_ssid_item->valuestring;
// Get credentials object
cJSON* credentials = cJSON_GetObjectItem(json, "credentials");
if (credentials == nullptr || !cJSON_IsObject(credentials)) {
ESP_LOGE(TAG, "WiFi password JSON does not contain valid 'credentials' object");
ESP_LOGE(TAG, "WiFi credentials JSON does not contain valid 'credentials' object");
out_ssid = "";
out_password = "";
cJSON_Delete(json);
return;
}
// get the ssid value
// Get SSID entry
cJSON* ssid_item = cJSON_GetObjectItem(credentials, out_ssid.c_str());
if (ssid_item == nullptr || !cJSON_IsObject(ssid_item)) {
ESP_LOGE(TAG, "WiFi password JSON does not contain valid SSID field for SSID: %s", out_ssid.c_str());
ESP_LOGE(TAG, "WiFi credentials JSON does not contain entry for SSID: %s", out_ssid.c_str());
out_ssid = "";
out_password = "";
cJSON_Delete(json);
return;
}
cJSON* password = cJSON_GetObjectItem(ssid_item, "password");
if (password == nullptr || !cJSON_IsString(password)) {
ESP_LOGE(TAG, "WiFi password JSON does not contain valid 'password' field for SSID: %s", out_ssid.c_str());
// Get password
cJSON* password_item = cJSON_GetObjectItem(ssid_item, "password");
if (password_item == nullptr || !cJSON_IsString(password_item)) {
ESP_LOGE(TAG, "WiFi credentials JSON does not contain valid 'password' field for SSID: %s", out_ssid.c_str());
out_ssid = "";
out_password = "";
cJSON_Delete(json);
return;
}
out_password = password->valuestring;
out_password = password_item->valuestring;
cJSON_Delete(json);
ESP_LOGD(TAG, "Retrieved WiFi credentials for SSID: %s", out_ssid.c_str());
}
EventBits_t WifiHandler::wait_for_connection(TickType_t ticks_to_wait) {