feat: implement LittleFSHandler and FSGuard for improved file management
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user