mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2026-04-06 05:07:38 +03:00
Start of 5.X work
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
idf_component_register(SRC_DIRS .
|
||||
INCLUDE_DIRS .
|
||||
PRIV_REQUIRES tools newlib console esp_common freertos tools services
|
||||
REQUIRES spiffs
|
||||
PRIV_REQUIRES newlib console esp_common freertos
|
||||
REQUIRES spiffs tools services esp_http_server
|
||||
)
|
||||
add_dependencies(${COMPONENT_LIB} generate_plugins_files)
|
||||
266
components/platform_config/Config.cpp
Normal file
266
components/platform_config/Config.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_INFO
|
||||
#include "Config.h"
|
||||
#include "PBW.h"
|
||||
#include "WifiList.h"
|
||||
#include "bootstate.h"
|
||||
#include "DAC.pb.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "pb_common.h" // Nanopb header for encoding (serialization)
|
||||
#include "pb_decode.h" // Nanopb header for decoding (deserialization)
|
||||
#include "pb_encode.h" // Nanopb header for encoding (serialization)
|
||||
#include "tools.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string.h>
|
||||
|
||||
static const char* TAG = "Configurator";
|
||||
|
||||
using namespace System;
|
||||
__attribute__((section(".ext_ram.bss"))) sys_config* platform;
|
||||
__attribute__((section(".ext_ram.bss"))) sys_state_data* sys_state;
|
||||
__attribute__((section(".ext_ram.bss"))) sys_dac_default_sets* default_dac_sets;
|
||||
|
||||
__attribute__((section(".ext_ram.bss"))) System::PB<sys_config> configWrapper("config", &sys_config_msg, sizeof(sys_config_msg));
|
||||
__attribute__((section(".ext_ram.bss"))) System::PB<sys_state_data> stateWrapper("state", &sys_state_data_msg, sizeof(sys_state_data_msg));
|
||||
__attribute__((section(".ext_ram.bss"))) System::PB<sys_dac_default_sets> defaultSets("default_sets", &sys_dac_default_sets_msg, sizeof(sys_dac_default_sets_msg));
|
||||
|
||||
const int MaxDelay = 1000;
|
||||
bool config_update_mac_string(const pb_msgdesc_t* desc, uint32_t field_tag, void* message) {
|
||||
pb_field_iter_t iter;
|
||||
if (pb_field_iter_begin(&iter, desc, message) && pb_field_iter_find(&iter, field_tag)) {
|
||||
if (!iter.pData) {
|
||||
ESP_LOGW(TAG, "Unable to check mac string member. Data not initialized");
|
||||
return false;
|
||||
}
|
||||
if (iter.pData) {
|
||||
auto curvalue = std::string((char*)iter.pData);
|
||||
if (curvalue.find(get_mac_str()) != std::string::npos) {
|
||||
ESP_LOGD(TAG, "Entry already has mac string: %s", curvalue.c_str());
|
||||
return true;
|
||||
}
|
||||
if (curvalue.find("@@init_from_mac@@") == std::string::npos) {
|
||||
ESP_LOGW(TAG, "Member not configured for mac address or was overwritten: %s", curvalue.c_str());
|
||||
return false;
|
||||
}
|
||||
auto newval = std::string("squeezelite-") + get_mac_str();
|
||||
if (PB_ATYPE(iter.type) == PB_ATYPE_POINTER) {
|
||||
ESP_LOGD(TAG, "Field is a pointer. Freeing previous value if any: %s", STR_OR_BLANK((char*)iter.pField));
|
||||
FREE_AND_NULL(*(char**)iter.pField);
|
||||
ESP_LOGD(TAG, "Field is a pointer. Setting new value as %s", newval.c_str());
|
||||
*(char**)iter.pField = strdup_psram(newval.c_str());
|
||||
} else if (PB_ATYPE(iter.type) == PB_ATYPE_STATIC) {
|
||||
ESP_LOGD(TAG, "Static string. Setting new value as %s from %s", newval.c_str(), STR_OR_BLANK((char*)iter.pData));
|
||||
memset((char*)iter.pData, 0x00, iter.data_size);
|
||||
strncpy((char*)iter.pData, newval.c_str(), iter.data_size);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Set mac string failed: member should be initialized with default");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool set_pb_string_from_mac(pb_ostream_t* stream, const pb_field_t* field, void* const* arg) {
|
||||
if (!stream) {
|
||||
// This is a size calculation pass, return true to indicate field presence
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generate the string based on MAC and prefix
|
||||
const char* prefix = reinterpret_cast<const char*>(*arg);
|
||||
char* value = alloc_get_string_with_mac(prefix && strlen(prefix) > 0 ? prefix : "squeezelite-");
|
||||
|
||||
// Write the string to the stream
|
||||
if (!pb_encode_string(stream, (uint8_t*)value, strlen(value))) {
|
||||
free(value); // Free memory if encoding fails
|
||||
return false;
|
||||
}
|
||||
|
||||
free(value); // Free memory after encoding
|
||||
return true;
|
||||
}
|
||||
|
||||
bool config_erase_config() {
|
||||
// make sure the config object doesn't have
|
||||
// any pending changes to commit
|
||||
ESP_LOGW(TAG, "Erasing configuration object and rebooting");
|
||||
configWrapper.ResetModified();
|
||||
// Erase the file and reboot
|
||||
erase_path(configWrapper.GetFileName().c_str(), false);
|
||||
guided_factory();
|
||||
return true;
|
||||
}
|
||||
void set_mac_string() {
|
||||
auto expected = std::string("squeezelite-") + get_mac_str();
|
||||
bool changed = false;
|
||||
auto config = configWrapper.get();
|
||||
changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_device_tag, &config->names) || changed;
|
||||
changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_airplay_tag, &config->names) || changed;
|
||||
changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_spotify_tag, &config->names) || changed;
|
||||
changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_bluetooth_tag, &config->names) || changed;
|
||||
changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_squeezelite_tag, &config->names) || changed;
|
||||
changed = config_update_mac_string(&sys_names_config_msg, sys_names_config_wifi_ap_name_tag, &config->names) || changed;
|
||||
|
||||
if (changed) {
|
||||
ESP_LOGI(TAG, "One or more name was changed. Committing");
|
||||
configWrapper.RaiseChangedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
void config_load() {
|
||||
ESP_LOGI(TAG, "Loading configuration.");
|
||||
bool restart = false;
|
||||
sys_state = stateWrapper.get();
|
||||
platform = configWrapper.get();
|
||||
default_dac_sets = defaultSets.get();
|
||||
|
||||
assert(platform != nullptr);
|
||||
assert(sys_state != nullptr);
|
||||
assert(default_dac_sets != nullptr);
|
||||
|
||||
configWrapper.get()->net.credentials = reinterpret_cast<sys_net_wifi_entry*>(new WifiList("wifi"));
|
||||
assert(configWrapper.get()->net.credentials != nullptr);
|
||||
|
||||
if (!stateWrapper.FileExists()) {
|
||||
ESP_LOGI(TAG, "State file not found or is empty. ");
|
||||
stateWrapper.Reinitialize(true);
|
||||
stateWrapper.SetTarget(CONFIG_FW_PLATFORM_NAME);
|
||||
} else {
|
||||
stateWrapper.LoadFile();
|
||||
}
|
||||
if (!configWrapper.FileExists()) {
|
||||
ESP_LOGI(TAG, "Configuration file not found or is empty. ");
|
||||
configWrapper.Reinitialize(true);
|
||||
ESP_LOGI(TAG, "Current device name after load: %s", platform->names.device);
|
||||
|
||||
configWrapper.SetTarget(CONFIG_FW_PLATFORM_NAME,true);
|
||||
set_mac_string();
|
||||
ESP_LOGW(TAG, "Restart required after initializing configuration");
|
||||
restart = true;
|
||||
|
||||
} else {
|
||||
configWrapper.LoadFile();
|
||||
if (configWrapper.GetTargetName().empty() && !std::string(CONFIG_FW_PLATFORM_NAME).empty()) {
|
||||
ESP_LOGW(TAG, "Config target is empty. Updating to %s", CONFIG_FW_PLATFORM_NAME);
|
||||
configWrapper.Reinitialize(false,true,std::string(CONFIG_FW_PLATFORM_NAME));
|
||||
ESP_LOGW(TAG, "Restart required due to target change");
|
||||
restart = true;
|
||||
}
|
||||
}
|
||||
if (!defaultSets.FileExists()) {
|
||||
ESP_LOGE(TAG, "Default Sets file not found or is empty. (%s)", defaultSets.GetFileName().c_str());
|
||||
} else {
|
||||
defaultSets.LoadFile();
|
||||
}
|
||||
if (restart) {
|
||||
network_async_reboot(OTA);
|
||||
}
|
||||
}
|
||||
void config_dump_config() {
|
||||
auto serialized = configWrapper.Encode();
|
||||
dump_data(serialized.data(), serialized.size());
|
||||
}
|
||||
|
||||
void config_set_target(const char* target_name, bool reset) {
|
||||
std::string new_target = std::string(target_name);
|
||||
bool restart = false;
|
||||
if (configWrapper.GetTargetName() != new_target) {
|
||||
ESP_LOGI(TAG, "Setting configuration target name to %s, %s", target_name, reset ? "full reset" : "reapply only");
|
||||
if (reset) {
|
||||
ESP_LOGD(TAG, "Reinitializing Config structure");
|
||||
configWrapper.Reinitialize(false,true,new_target);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Loading Config target values for %s", target_name);
|
||||
configWrapper.SetTarget(target_name,true);
|
||||
configWrapper.LoadTargetValues();
|
||||
configWrapper.RaiseChangedAsync();
|
||||
}
|
||||
restart = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Target name has no change");
|
||||
}
|
||||
if (stateWrapper.GetTargetName() != new_target) {
|
||||
ESP_LOGI(TAG, "Setting state target name to %s, %s", target_name, reset ? "full reset" : "reapply only");
|
||||
restart=true;
|
||||
if (reset) {
|
||||
ESP_LOGD(TAG, "Reinitializing State structure");
|
||||
stateWrapper.Reinitialize(false,true,new_target);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Loading State target values for %s", target_name);
|
||||
stateWrapper.SetTarget(target_name,true);
|
||||
stateWrapper.LoadTargetValues();
|
||||
stateWrapper.RaiseChangedAsync();
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "Done updating target to %s", target_name);
|
||||
if(restart){
|
||||
network_async_reboot(RESTART);
|
||||
}
|
||||
}
|
||||
void config_set_target_no_reset(const char* target_name) { config_set_target(target_name, false); }
|
||||
void config_set_target_reset(const char* target_name) { config_set_target(target_name, true); }
|
||||
void config_raise_changed(bool sync) {
|
||||
if (sync) {
|
||||
configWrapper.CommitChanges();
|
||||
} else {
|
||||
configWrapper.RaiseChangedAsync();
|
||||
}
|
||||
}
|
||||
void config_commit_protowrapper(void* protoWrapper) {
|
||||
ESP_LOGD(TAG, "Committing synchronously");
|
||||
PBHelper::SyncCommit(protoWrapper);
|
||||
}
|
||||
|
||||
void config_commit_state() { stateWrapper.CommitChanges(); }
|
||||
void config_raise_state_changed() { stateWrapper.RaiseChangedAsync(); }
|
||||
|
||||
bool config_has_changes() { return configWrapper.HasChanges() || stateWrapper.HasChanges(); }
|
||||
|
||||
bool config_waitcommit() { return configWrapper.WaitForCommit(2) && stateWrapper.WaitForCommit(2); }
|
||||
|
||||
bool config_http_send_config(httpd_req_t* req) {
|
||||
try {
|
||||
auto data = configWrapper.Encode();
|
||||
httpd_resp_send(req, (const char*)data.data(), data.size());
|
||||
return true;
|
||||
} catch (const std::runtime_error& e) {
|
||||
std::string errdesc = (std::string("Unable to get configuration: ") + e.what());
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, errdesc.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool system_set_string(const pb_msgdesc_t* desc, uint32_t field_tag, void* message, const char* value) {
|
||||
pb_field_iter_t iter;
|
||||
ESP_LOGD(TAG,"system_set_string. Getting new value");
|
||||
std::string newval = std::string(STR_OR_BLANK(value));
|
||||
ESP_LOGD(TAG,"system_set_string. Done getting new value");
|
||||
ESP_LOGD(TAG, "Setting value [%s] in message field tag %d", newval.c_str(), field_tag);
|
||||
if (pb_field_iter_begin(&iter, desc, message) && pb_field_iter_find(&iter, field_tag)) {
|
||||
std::string old= std::string((char*)iter.pData);
|
||||
if (iter.pData && old == newval) {
|
||||
ESP_LOGW(TAG, "No change, from and to values are the same: [%s]", newval.c_str());
|
||||
return false;
|
||||
}
|
||||
if (PB_ATYPE(iter.type) == PB_ATYPE_POINTER) {
|
||||
ESP_LOGD(TAG, "Field is a pointer. Freeing previous value if any: %s", STR_OR_BLANK((char*)iter.pField));
|
||||
FREE_AND_NULL(*(char**)iter.pField);
|
||||
ESP_LOGD(TAG, "Field is a pointer. Setting new value ");
|
||||
if (!newval.empty()) {
|
||||
*(char**)iter.pField = strdup_psram(newval.c_str());
|
||||
}
|
||||
|
||||
} else if (PB_ATYPE(iter.type) == PB_ATYPE_STATIC) {
|
||||
ESP_LOGD(TAG, "Static string. Setting new value. Existing value: %s", (char*)iter.pData);
|
||||
memset((char*)iter.pData, 0x00, iter.data_size);
|
||||
if (!newval.empty()) {
|
||||
strncpy((char*)iter.pData, newval.c_str(), iter.data_size);
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "Done setting value ");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
49
components/platform_config/Config.h
Normal file
49
components/platform_config/Config.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "State.pb.h"
|
||||
#include "Status.pb.h"
|
||||
#include "configuration.pb.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "status.pb.h"
|
||||
#include "accessors.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "PBW.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern System::PB<sys_state_data> stateWrapper;
|
||||
extern System::PB<sys_config> configWrapper;
|
||||
extern "C" {
|
||||
#endif
|
||||
#define PLATFORM_GET_PTR(base, sname) \
|
||||
{ \
|
||||
(base && (base)->##has_##(sname) ? &(base)->sname : NULL)
|
||||
#define PLATFORM_DEVICES PLATFORM_GET_PTR(platform)
|
||||
void config_load();
|
||||
bool config_waitcommit();
|
||||
bool config_has_changes();
|
||||
void config_raise_changed(bool sync);
|
||||
void config_raise_state_changed();
|
||||
bool config_has_changes();
|
||||
bool config_waitcommit();
|
||||
void config_set_target(const char* target_name);
|
||||
void config_set_target_no_reset(const char* target_name);
|
||||
void config_set_target_reset(const char* target_name);
|
||||
|
||||
void config_commit_protowrapper(void * protoWrapper);
|
||||
bool config_http_send_config(httpd_req_t* req);
|
||||
bool config_erase_config();
|
||||
void set_mac_string();
|
||||
void config_dump_config();
|
||||
bool system_set_string(
|
||||
const pb_msgdesc_t* desc, uint32_t field_tag, void* message, const char* value);
|
||||
extern sys_config* platform;
|
||||
extern sys_state_data* sys_state;
|
||||
extern sys_config* presets;
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,480 +0,0 @@
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
|
||||
#include "Configurator.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "pb_common.h" // Nanopb header for encoding (serialization)
|
||||
#include "pb_decode.h" // Nanopb header for decoding (deserialization)
|
||||
#include "pb_encode.h" // Nanopb header for encoding (serialization)
|
||||
// #include "sys_options.h"
|
||||
#include "tools.h"
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
static const char* TAG = "Configurator";
|
||||
static const char* targets_folder = "targets";
|
||||
static const char* config_file_name = "settings.bin";
|
||||
static const char* state_file_name = "state.bin";
|
||||
__attribute__((section(".ext_ram.bss"))) PlatformConfig::Configurator configurator;
|
||||
sys_Config* platform = NULL;
|
||||
sys_State* sys_state = NULL;
|
||||
|
||||
bool set_pb_string_from_mac(pb_ostream_t* stream, const pb_field_t* field, void* const* arg) {
|
||||
if (!stream) {
|
||||
// This is a size calculation pass, return true to indicate field presence
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generate the string based on MAC and prefix
|
||||
const char* prefix = reinterpret_cast<const char*>(*arg);
|
||||
char* value = alloc_get_string_with_mac(prefix && strlen(prefix) > 0 ? prefix : "squeezelite-");
|
||||
|
||||
// Write the string to the stream
|
||||
if (!pb_encode_string(stream, (uint8_t*)value, strlen(value))) {
|
||||
free(value); // Free memory if encoding fails
|
||||
return false;
|
||||
}
|
||||
|
||||
free(value); // Free memory after encoding
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace PlatformConfig {
|
||||
|
||||
EXT_RAM_ATTR static const int NO_COMMIT_PENDING = BIT0;
|
||||
EXT_RAM_ATTR static const int LOAD_BIT = BIT1;
|
||||
EXT_RAM_ATTR static const int NO_STATE_COMMIT_PENDING = BIT2;
|
||||
|
||||
const int Configurator::MaxDelay = 1000;
|
||||
const int Configurator::LockMaxWait = 20 * Configurator::MaxDelay;
|
||||
|
||||
EXT_RAM_ATTR TimerHandle_t Configurator::_timer;
|
||||
EXT_RAM_ATTR SemaphoreHandle_t Configurator::_mutex;
|
||||
EXT_RAM_ATTR SemaphoreHandle_t Configurator::_state_mutex;
|
||||
EXT_RAM_ATTR EventGroupHandle_t Configurator::_group;
|
||||
|
||||
static void ConfiguratorCallback(TimerHandle_t xTimer) {
|
||||
static int cnt = 0, scnt = 0;
|
||||
if (configurator.HasChanges()) {
|
||||
ESP_LOGI(TAG, "Configuration has some uncommitted entries");
|
||||
configurator.CommitChanges();
|
||||
} else {
|
||||
if (++cnt >= 15) {
|
||||
ESP_LOGV(TAG, "commit timer: commit flag not set");
|
||||
cnt = 0;
|
||||
}
|
||||
}
|
||||
if (configurator.HasStateChanges()) {
|
||||
ESP_LOGI(TAG, "State has some uncommitted changes");
|
||||
configurator.CommitState();
|
||||
} else {
|
||||
if (++scnt >= 15) {
|
||||
ESP_LOGV(TAG, "commit timer: commit flag not set");
|
||||
cnt = 0;
|
||||
}
|
||||
}
|
||||
xTimerReset(xTimer, 10);
|
||||
}
|
||||
void Configurator::RaiseStateModified() { SetGroupBit(NO_STATE_COMMIT_PENDING, false); }
|
||||
void Configurator::RaiseModified() { SetGroupBit(NO_COMMIT_PENDING, false); }
|
||||
void Configurator::ResetModified() {
|
||||
ESP_LOGV(TAG, "Resetting the global commit flag.");
|
||||
SetGroupBit(NO_COMMIT_PENDING, false);
|
||||
}
|
||||
void Configurator::ResetStateModified() {
|
||||
ESP_LOGV(TAG, "Resetting the state commit flag.");
|
||||
SetGroupBit(NO_STATE_COMMIT_PENDING, false);
|
||||
}
|
||||
bool Configurator::SetGroupBit(int bit_num, bool flag) {
|
||||
bool result = true;
|
||||
int curFlags = xEventGroupGetBits(_group);
|
||||
if ((curFlags & LOAD_BIT) && bit_num == NO_COMMIT_PENDING) {
|
||||
ESP_LOGD(TAG, "Loading config, ignoring changes");
|
||||
result = false;
|
||||
}
|
||||
if (result) {
|
||||
bool curBit = (xEventGroupGetBits(_group) & bit_num);
|
||||
if (curBit == flag) {
|
||||
ESP_LOGV(TAG, "Flag %d already %s", bit_num, flag ? "Set" : "Cleared");
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
if (result) {
|
||||
ESP_LOGV(TAG, "%s Flag %d ", flag ? "Setting" : "Clearing", bit_num);
|
||||
if (!flag) {
|
||||
xEventGroupClearBits(_group, bit_num);
|
||||
} else {
|
||||
xEventGroupSetBits(_group, bit_num);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool Configurator::Lock() {
|
||||
ESP_LOGV(TAG, "Locking Configurator");
|
||||
if (xSemaphoreTake(_mutex, LockMaxWait) == pdTRUE) {
|
||||
ESP_LOGV(TAG, "Configurator locked!");
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Semaphore take failed. Unable to lock Configurator");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool Configurator::LockState() {
|
||||
ESP_LOGV(TAG, "Locking State");
|
||||
if (xSemaphoreTake(_state_mutex, LockMaxWait) == pdTRUE) {
|
||||
ESP_LOGV(TAG, "State locked!");
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Semaphore take failed. Unable to lock State");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void* Configurator::AllocGetConfigBuffer(size_t* sz, sys_Config* config) {
|
||||
size_t datasz;
|
||||
pb_byte_t* data = NULL;
|
||||
|
||||
if (!pb_get_encoded_size(&datasz, sys_Config_fields, (const void*)platform) || datasz <= 0) {
|
||||
return data;
|
||||
}
|
||||
data = (pb_byte_t*)malloc_init_external(datasz * sizeof(pb_byte_t));
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(data, datasz);
|
||||
pb_encode(&stream, sys_Config_fields, (const void*)platform);
|
||||
if (sz) {
|
||||
*sz = datasz * sizeof(pb_byte_t);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
void* Configurator::AllocGetConfigBuffer(size_t* sz) {
|
||||
return AllocGetConfigBuffer(sz, &this->_root);
|
||||
}
|
||||
bool Configurator::WaitForCommit() {
|
||||
bool commit_pending = (xEventGroupGetBits(_group) & NO_COMMIT_PENDING) == 0;
|
||||
while (commit_pending) {
|
||||
ESP_LOGW(TAG, "Waiting for config commit ...");
|
||||
commit_pending = (xEventGroupWaitBits(_group, NO_COMMIT_PENDING | NO_STATE_COMMIT_PENDING, pdFALSE, pdTRUE,
|
||||
(MaxDelay * 2) / portTICK_PERIOD_MS) &
|
||||
( NO_COMMIT_PENDING | NO_STATE_COMMIT_PENDING)) == 0;
|
||||
if (commit_pending) {
|
||||
ESP_LOGW(TAG, "Timeout waiting for config commit.");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Config committed!");
|
||||
}
|
||||
}
|
||||
return !commit_pending;
|
||||
}
|
||||
void Configurator::CommitChanges() {
|
||||
esp_err_t err = ESP_OK;
|
||||
ESP_LOGI(TAG, "Committing configuration to flash. Locking config object.");
|
||||
if (!Lock()) {
|
||||
ESP_LOGE(TAG, "Unable to lock config for commit ");
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "Config Locked. Committing");
|
||||
Commit(&_root);
|
||||
ResetModified();
|
||||
Unlock();
|
||||
ESP_LOGI(TAG, "Done Committing configuration to flash.");
|
||||
}
|
||||
|
||||
bool Configurator::CommitState() {
|
||||
esp_err_t err = ESP_OK;
|
||||
ESP_LOGI(TAG, "Committing configuration to flash. Locking config object.");
|
||||
if (!LockState()) {
|
||||
ESP_LOGE(TAG, "Unable to lock config for commit ");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGV(TAG, "Config Locked. Committing");
|
||||
CommitState(&_sys_state);
|
||||
ResetStateModified();
|
||||
Unlock();
|
||||
ESP_LOGI(TAG, "Done Committing configuration to flash.");
|
||||
return true;
|
||||
}
|
||||
bool Configurator::HasChanges() { return (xEventGroupGetBits(_group) & NO_COMMIT_PENDING); }
|
||||
bool Configurator::HasStateChanges() { return (xEventGroupGetBits(_group) & NO_STATE_COMMIT_PENDING); }
|
||||
void Configurator::Unlock() {
|
||||
ESP_LOGV(TAG, "Unlocking Configurator!");
|
||||
xSemaphoreGive(_mutex);
|
||||
}
|
||||
void Configurator::UnlockState() {
|
||||
ESP_LOGV(TAG, "Unlocking State!");
|
||||
xSemaphoreGive(_state_mutex);
|
||||
}
|
||||
|
||||
void Configurator::ResetStructure(sys_Config* config) {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
sys_Config blankconfig = sys_Config_init_default;
|
||||
memcpy(config, &blankconfig, sizeof(blankconfig));
|
||||
}
|
||||
bool Configurator::LoadDecodeBuffer(void* buffer, size_t buffer_size) {
|
||||
size_t msgsize = 0;
|
||||
size_t newsize = 0;
|
||||
sys_Config config = sys_Config_init_default;
|
||||
bool result = Configurator::LoadDecode(buffer, buffer_size, &config);
|
||||
if (result) {
|
||||
Configurator::ApplyTargetSettings(&config);
|
||||
}
|
||||
if (result) {
|
||||
void* currentbuffer = AllocGetConfigBuffer(&msgsize);
|
||||
void* newbuffer = AllocGetConfigBuffer(&newsize, &config);
|
||||
if (msgsize != newsize || !memcmp(currentbuffer, newbuffer, msgsize)) {
|
||||
ESP_LOGI(TAG, "Config change detected.");
|
||||
// todo: here we are assuming that all strings and repeated elements have fixed size
|
||||
// and therefore size should always be the same.
|
||||
result = Configurator::LoadDecode(buffer, buffer_size, &this->_root);
|
||||
RaiseModified();
|
||||
}
|
||||
free(currentbuffer);
|
||||
free(newbuffer);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool Configurator::LoadDecodeState() {
|
||||
bool result = true;
|
||||
sys_State blank_state = sys_State_init_default;
|
||||
FILE* file = open_file("rb", state_file_name);
|
||||
if (file == nullptr) {
|
||||
ESP_LOGD(TAG,"No state file found. Initializing ");
|
||||
pb_release(&sys_State_msg,(void *)&_sys_state);
|
||||
memcpy(&_sys_state, &blank_state, sizeof(sys_State));
|
||||
ESP_LOGD(TAG,"Done Initializing state");
|
||||
return true;
|
||||
}
|
||||
ESP_LOGD(TAG, "Creating binding");
|
||||
pb_istream_t filestream = {&in_file_binding,NULL,0};
|
||||
ESP_LOGD(TAG, "Starting encode");
|
||||
if (!pb_decode(&filestream, &sys_State_msg, (void*)&_sys_state)) {
|
||||
ESP_LOGE(TAG, "Decoding failed: %s\n", PB_GET_ERROR(&filestream));
|
||||
result = false;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
configurator_raise_state_changed();
|
||||
ESP_LOGD(TAG, "State loaded");
|
||||
return true;
|
||||
}
|
||||
bool Configurator::LoadDecode(
|
||||
void* buffer, size_t buffer_size, sys_Config* conf_root, bool noinit) {
|
||||
if (!conf_root || !buffer) {
|
||||
ESP_LOGE(TAG, "Invalid arguments passed to Load");
|
||||
}
|
||||
bool result = true;
|
||||
// Prepare to read the data into the 'config' structure
|
||||
pb_istream_t stream = pb_istream_from_buffer((uint8_t*)buffer, buffer_size);
|
||||
|
||||
// Decode the Protocol Buffers message
|
||||
if (noinit) {
|
||||
ESP_LOGD(TAG, "Decoding WITHOUT initialization");
|
||||
result = pb_decode_noinit(&stream, &sys_Config_msg, conf_root);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Decoding WITH initialization");
|
||||
result = pb_decode(&stream, &sys_Config_msg, conf_root);
|
||||
}
|
||||
if (!result) {
|
||||
ESP_LOGE(TAG, "Failed to decode settings: %s", PB_GET_ERROR(&stream));
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Settings decoded");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Configurator::Commit(sys_Config* config) {
|
||||
if (!config) {
|
||||
ESP_LOGE(TAG, "Invalid configuration structure!");
|
||||
return false;
|
||||
}
|
||||
FILE* file = open_file("wb", config_file_name);
|
||||
bool result = true;
|
||||
if (file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Creating binding");
|
||||
pb_ostream_t filestream = {&out_file_binding, file, SIZE_MAX, 0};
|
||||
ESP_LOGD(TAG, "Starting encode");
|
||||
if (!pb_encode(&filestream, sys_Config_fields, (void*)config)) {
|
||||
ESP_LOGE(TAG, "Encoding failed: %s\n", PB_GET_ERROR(&filestream));
|
||||
result = false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Encoded size: %d", filestream.bytes_written);
|
||||
if (filestream.bytes_written == 0) {
|
||||
ESP_LOGE(TAG, "Empty configuration!");
|
||||
ESP_LOGD(TAG, "Device name: %s", config->names.device);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Configurator::CommitState(sys_State* state) {
|
||||
if (!state) {
|
||||
ESP_LOGE(TAG, "Invalid state structure!");
|
||||
return false;
|
||||
}
|
||||
FILE* file = open_file("wb", state_file_name);
|
||||
bool result = true;
|
||||
if (file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Creating binding for state commit");
|
||||
pb_ostream_t filestream = {&out_file_binding, file, SIZE_MAX, 0};
|
||||
ESP_LOGD(TAG, "Starting state encode");
|
||||
if (!pb_encode(&filestream, sys_Config_fields, (void*)state)) {
|
||||
ESP_LOGE(TAG, "Encoding failed: %s\n", PB_GET_ERROR(&filestream));
|
||||
result = false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Encoded size: %d", filestream.bytes_written);
|
||||
if (filestream.bytes_written == 0) {
|
||||
ESP_LOGE(TAG, "Empty state!");
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void Configurator::InitLoadConfig(const char* filename) {
|
||||
return Configurator::InitLoadConfig(filename, &this->_root);
|
||||
}
|
||||
void Configurator::InitLoadConfig(const char* filename, sys_Config* conf_root, bool noinit) {
|
||||
esp_err_t err = ESP_OK;
|
||||
size_t data_length = 0;
|
||||
bool result = false;
|
||||
ESP_LOGI(TAG, "Loading settings from %s", filename);
|
||||
void* data = load_file(&data_length, filename);
|
||||
if (!data) {
|
||||
ESP_LOGW(TAG, "Config file %s was empty. ", filename);
|
||||
return;
|
||||
} else {
|
||||
result = LoadDecode(data, data_length, conf_root, noinit);
|
||||
free(data);
|
||||
}
|
||||
if (ApplyTargetSettings(conf_root)) {
|
||||
result = true;
|
||||
}
|
||||
if (result) {
|
||||
_timer = xTimerCreate(
|
||||
"configTimer", MaxDelay / portTICK_RATE_MS, pdFALSE, NULL, ConfiguratorCallback);
|
||||
if (xTimerStart(_timer, MaxDelay / portTICK_RATE_MS) != pdPASS) {
|
||||
ESP_LOGE(TAG, "config commitment timer failed to start.");
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
bool Configurator::ApplyTargetSettings() { return ApplyTargetSettings(&this->_root); }
|
||||
bool Configurator::ApplyTargetSettings(sys_Config* conf_root) {
|
||||
size_t data_length = 0;
|
||||
bool result = false;
|
||||
std::string target_name = conf_root->target;
|
||||
std::string target_file;
|
||||
|
||||
#ifdef CONFIG_FW_PLATFORM_NAME
|
||||
if( target_name.empty()){
|
||||
target_name = CONFIG_FW_PLATFORM_NAME;
|
||||
}
|
||||
#endif
|
||||
target_file = target_name+ std::string(".bin");
|
||||
|
||||
std::transform(target_file.begin(), target_file.end(), target_file.begin(),
|
||||
[](unsigned char c){ return std::tolower(c); });
|
||||
if (target_file.empty() || !get_file_info(NULL, targets_folder, target_file.c_str())) {
|
||||
ESP_LOGD(TAG, "Platform settings file not found: %s", target_file.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Applying target %s settings", target_name.c_str());
|
||||
void* data = load_file(&data_length, targets_folder, target_file.c_str());
|
||||
if (!data) {
|
||||
ESP_LOGE(TAG, "File read fail");
|
||||
return false;
|
||||
} else {
|
||||
|
||||
result = LoadDecode(data, data_length, conf_root, true);
|
||||
if (result) {
|
||||
ESP_LOGI(TAG, "Target %s settings loaded", target_name.c_str());
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}; // namespace PlatformConfig
|
||||
|
||||
void configurator_reset_configuration() {
|
||||
ESP_LOGI(TAG, "Creating default configuration file. ");
|
||||
sys_Config config = sys_Config_init_default;
|
||||
ESP_LOGD(TAG, "Device name before target settings: %s", config.names.device);
|
||||
PlatformConfig::Configurator::ApplyTargetSettings(&config);
|
||||
ESP_LOGD(TAG, "Device name after target settings: %s", config.names.device);
|
||||
ESP_LOGD(TAG, "Committing new structure");
|
||||
PlatformConfig::Configurator::Commit(&config);
|
||||
}
|
||||
|
||||
void configurator_load() {
|
||||
struct stat fileInformation;
|
||||
ESP_LOGI(TAG, "Loading system settings file");
|
||||
ESP_LOGD(TAG, "Checking if file %s exists", config_file_name);
|
||||
bool found = get_file_info(&fileInformation, config_file_name);
|
||||
if (!found || fileInformation.st_size == 0) {
|
||||
ESP_LOGI(TAG, "Configuration file not found or is empty. ");
|
||||
configurator_reset_configuration();
|
||||
}
|
||||
configurator.InitLoadConfig(config_file_name);
|
||||
ESP_LOGD(TAG, "Assigning global config pointer");
|
||||
platform = configurator.Root();
|
||||
configurator.LoadDecodeState();
|
||||
sys_state = configurator.RootState();
|
||||
}
|
||||
bool configurator_lock() { return configurator.Lock(); }
|
||||
|
||||
void configurator_unlock() { configurator.Unlock(); }
|
||||
void configurator_raise_changed() { configurator.RaiseModified(); }
|
||||
void configurator_raise_state_changed() { configurator.RaiseStateModified(); }
|
||||
|
||||
bool configurator_has_changes() { return configurator.HasChanges(); }
|
||||
|
||||
bool configurator_waitcommit() { return configurator.WaitForCommit(); }
|
||||
|
||||
void* configurator_alloc_get_config(size_t* sz) { return configurator.AllocGetConfigBuffer(sz); }
|
||||
bool configurator_parse_config(void* buffer, size_t buffer_size) {
|
||||
// Load and decode buffer. The method also applies any overlay if needed.
|
||||
return configurator.LoadDecodeBuffer(buffer, buffer_size);
|
||||
}
|
||||
pb_type_t configurator_get_field_type(const pb_msgdesc_t* desc, uint32_t tag) {
|
||||
pb_field_iter_t iter;
|
||||
if (pb_field_iter_begin(&iter, desc, NULL) && pb_field_iter_find(&iter, tag)) {
|
||||
/* Found our field. */
|
||||
return iter.type;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
bool configurator_set_string(
|
||||
const pb_msgdesc_t* desc, uint32_t field_tag, void* message, const char* value) {
|
||||
pb_field_iter_t iter;
|
||||
const char * newval = STR_OR_BLANK(value);
|
||||
ESP_LOGD(TAG, "Setting value [%s] in message field tag %d",newval , field_tag);
|
||||
if (pb_field_iter_begin(&iter, desc, message) && pb_field_iter_find(&iter, field_tag)) {
|
||||
if (iter.pData && !strcmp((char*)iter.pData, newval)) {
|
||||
ESP_LOGW(TAG, "No change, from and to values are the same: [%s]", STR_OR_BLANK(newval));
|
||||
return false;
|
||||
}
|
||||
if (PB_ATYPE(iter.type) == PB_ATYPE_POINTER) {
|
||||
ESP_LOGD(TAG, "Field is a pointer. Freeing previous value if any");
|
||||
FREE_AND_NULL(iter.pData);
|
||||
ESP_LOGD(TAG, "Field is a pointer. Setting new value ");
|
||||
if(newval && strlen(newval)>0){
|
||||
iter.pData = strdup_psram(newval);
|
||||
}
|
||||
|
||||
} else if (PB_ATYPE(iter.type) == PB_ATYPE_STATIC) {
|
||||
ESP_LOGD(TAG, "Static string. Setting new value");
|
||||
memset(iter.pData,0x00,iter.data_size);
|
||||
if(newval && strlen(newval)>0){
|
||||
strncpy((char*)iter.pData, newval, iter.data_size);
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "Done setting value ");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
#pragma once
|
||||
#include "State.pb.h"
|
||||
#include "configuration.pb.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "status.pb.h"
|
||||
#include <strings.h>
|
||||
#include "accessors.h"
|
||||
#ifdef __cplusplus
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
#endif
|
||||
#define PLATFORM_GET_PTR(base, sname) \
|
||||
{ \
|
||||
(base && (base)->##has_##(sname) ? &(base)->sname : NULL)
|
||||
#define PLATFORM_DEVICES PLATFORM_GET_PTR(platform)
|
||||
void configurator_load();
|
||||
bool configurator_waitcommit();
|
||||
bool configurator_has_changes();
|
||||
bool configurator_lock();
|
||||
void configurator_unlock();
|
||||
void configurator_raise_changed();
|
||||
void configurator_raise_state_changed();
|
||||
bool configurator_has_changes();
|
||||
bool configurator_waitcommit();
|
||||
void* configurator_alloc_get_config(size_t* sz);
|
||||
bool configurator_parse_config(void* buffer, size_t buffer_size);
|
||||
void configurator_reset_configuration();
|
||||
pb_type_t configurator_get_field_type(const pb_msgdesc_t* desc, uint32_t tag);
|
||||
bool configurator_set_string(
|
||||
const pb_msgdesc_t* desc, uint32_t field_tag, void* message, const char* value);
|
||||
extern sys_Config* platform;
|
||||
extern sys_State* sys_state;
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
namespace PlatformConfig {
|
||||
|
||||
class Configurator {
|
||||
private:
|
||||
static const int MaxDelay;
|
||||
static const int LockMaxWait;
|
||||
|
||||
EXT_RAM_ATTR static TimerHandle_t _timer;
|
||||
EXT_RAM_ATTR static SemaphoreHandle_t _mutex;
|
||||
EXT_RAM_ATTR static SemaphoreHandle_t _state_mutex;
|
||||
EXT_RAM_ATTR static EventGroupHandle_t _group;
|
||||
bool SetGroupBit(int bit_num, bool flag);
|
||||
void ResetModified();
|
||||
void ResetStateModified();
|
||||
sys_Config _root;
|
||||
sys_State _sys_state;
|
||||
|
||||
public:
|
||||
sys_Config* Root() { return &_root; }
|
||||
sys_State* RootState() { return &_sys_state; }
|
||||
bool WaitForCommit();
|
||||
bool Lock();
|
||||
bool LockState();
|
||||
void Unlock();
|
||||
void UnlockState();
|
||||
|
||||
void* AllocGetConfigBuffer(size_t* sz);
|
||||
static void* AllocGetConfigBuffer(size_t* sz, sys_Config* config);
|
||||
static void ResetStructure(sys_Config* config);
|
||||
bool LoadDecodeState();
|
||||
|
||||
void CommitChanges();
|
||||
bool Commit();
|
||||
static bool Commit(sys_Config* config);
|
||||
|
||||
bool CommitState();
|
||||
static bool CommitState(sys_State* state);
|
||||
sys_Config* AllocDefaultStruct();
|
||||
|
||||
static bool ApplyTargetSettings(sys_Config* conf_root);
|
||||
bool ApplyTargetSettings();
|
||||
bool HasStateChanges();
|
||||
|
||||
bool LoadDecodeBuffer(void* buffer, size_t buffer_size);
|
||||
void InitLoadConfig(const char* filename);
|
||||
void InitLoadConfig(const char* filename, sys_Config* conf_root, bool noinit = false);
|
||||
static bool LoadDecode(
|
||||
void* buffer, size_t buffer_size, sys_Config* conf_root, bool noinit = false);
|
||||
|
||||
Configurator() {
|
||||
_mutex = xSemaphoreCreateMutex();
|
||||
_state_mutex = xSemaphoreCreateMutex();
|
||||
_group = xEventGroupCreate();
|
||||
}
|
||||
void RaiseStateModified();
|
||||
void RaiseModified();
|
||||
bool HasChanges();
|
||||
~Configurator() {}
|
||||
};
|
||||
} // namespace PlatformConfig
|
||||
extern PlatformConfig::Configurator configurator;
|
||||
#endif
|
||||
45
components/platform_config/Locking.cpp
Normal file
45
components/platform_config/Locking.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
#include "Locking.h"
|
||||
#include "esp_log.h"
|
||||
#include "tools.h"
|
||||
static const char* TAG = "Locking";
|
||||
|
||||
using namespace System;
|
||||
|
||||
const int Locking::MaxDelay = 1000;
|
||||
const int Locking::LockMaxWait = 20 * Locking::MaxDelay;
|
||||
|
||||
// C++ methods
|
||||
Locking* Locking::Create(std::string name) { return new Locking(name); }
|
||||
|
||||
void Locking::Destroy(Locking* lock) { delete lock; }
|
||||
|
||||
LockingHandle* Locking_Create(const char* name) {
|
||||
return reinterpret_cast<LockingHandle*>(Locking::Create(std::string(name)));
|
||||
}
|
||||
|
||||
void Locking_Destroy(LockingHandle* lock) { Locking::Destroy(reinterpret_cast<Locking*>(lock)); }
|
||||
|
||||
bool Locking_Lock(LockingHandle* lock, TickType_t maxWait_ms) {
|
||||
return reinterpret_cast<Locking*>(lock)->Lock(maxWait_ms);
|
||||
}
|
||||
|
||||
void Locking_Unlock(LockingHandle* lock) { reinterpret_cast<Locking*>(lock)->Unlock(); }
|
||||
|
||||
bool Locking_IsLocked(LockingHandle* lock) { return reinterpret_cast<Locking*>(lock)->IsLocked(); }
|
||||
|
||||
bool Locking::Lock(TickType_t maxWait_ms) {
|
||||
assert(_mutex != nullptr);
|
||||
ESP_LOGV(TAG, "Locking %s", _name.c_str());
|
||||
if (xSemaphoreTakeRecursive(_mutex, pdMS_TO_TICKS(maxWait_ms)) == pdTRUE) {
|
||||
ESP_LOGV(TAG, "locked %s", _name.c_str());
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unable to lock %s", _name.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void Locking::Unlock() {
|
||||
ESP_LOGV(TAG, "Unlocking %s", _name.c_str());
|
||||
xSemaphoreGiveRecursive(_mutex);
|
||||
}
|
||||
71
components/platform_config/Locking.h
Normal file
71
components/platform_config/Locking.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
*
|
||||
* Sebastien L. 2023, sle118@hotmail.com
|
||||
* Philippe G. 2023, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* License Overview:
|
||||
* ----------------
|
||||
* The MIT License is a permissive open source license. As a user of this software, you are free to:
|
||||
* - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
|
||||
* - Use the software for private, commercial, or any other purposes.
|
||||
*
|
||||
* Conditions:
|
||||
* - You must include the above copyright notice and this permission notice in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* The MIT License offers a high degree of freedom and is well-suited for both open source and
|
||||
* commercial applications. It places minimal restrictions on how the software can be used,
|
||||
* modified, and redistributed. For more details on the MIT License, please refer to the link above.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "esp_attr.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct LockingHandle LockingHandle;
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
|
||||
#include <string>
|
||||
namespace System {
|
||||
class Locking {
|
||||
private:
|
||||
SemaphoreHandle_t _mutex;
|
||||
static const int MaxDelay;
|
||||
static const int LockMaxWait;
|
||||
std::string _name;
|
||||
|
||||
public:
|
||||
Locking(std::string name) : _mutex(xSemaphoreCreateRecursiveMutex()), _name(name) {}
|
||||
bool Lock(TickType_t maxWait_ms = LockMaxWait);
|
||||
void Unlock();
|
||||
bool IsLocked() { return xSemaphoreGetMutexHolder(_mutex) != nullptr; }
|
||||
~Locking() { vSemaphoreDelete(_mutex); }
|
||||
static Locking* Create(std::string name);
|
||||
static void Destroy(Locking* lock);
|
||||
};
|
||||
|
||||
} // namespace PlatformConfig
|
||||
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
LockingHandle* Locking_Create(const char* name);
|
||||
void Locking_Destroy(LockingHandle* lock);
|
||||
bool Locking_Lock(LockingHandle* lock, TickType_t maxWait_ms);
|
||||
void Locking_Unlock(LockingHandle* lock);
|
||||
bool Locking_IsLocked(LockingHandle* lock);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
272
components/platform_config/PBW.cpp
Normal file
272
components/platform_config/PBW.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_INFO
|
||||
#include "Locking.h"
|
||||
#include "cpp_tools.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#include "PBW.h"
|
||||
#include "accessors.h"
|
||||
#include "esp_log.h"
|
||||
#include "network_manager.h"
|
||||
#include "pb.h"
|
||||
#include "pb_decode.h" // Nanopb header for decoding (deserialization)
|
||||
#include "pb_encode.h" // Nanopb header for encoding (serialization)
|
||||
#include "tools.h"
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace System {
|
||||
const char* PBHelper::PROTOTAG = "PB";
|
||||
|
||||
void PBHelper::ResetModified() {
|
||||
ESP_LOGD(PROTOTAG, "Resetting the global commit flag for %s.", name.c_str());
|
||||
SetGroupBit(Flags::COMMITTED, true);
|
||||
}
|
||||
|
||||
bool PBHelper::SetGroupBit(Flags flags, bool flag) {
|
||||
bool result = true;
|
||||
int bit_num = static_cast<int>(flags);
|
||||
int curFlags = xEventGroupGetBits(_group);
|
||||
if ((curFlags & static_cast<int>(Flags::LOAD)) && flags == Flags::COMMITTED) {
|
||||
ESP_LOGD(PROTOTAG, "Loading %s, ignoring changes", name.c_str());
|
||||
result = false;
|
||||
}
|
||||
if (result) {
|
||||
if ((curFlags & bit_num) == flag) {
|
||||
ESP_LOGV(PROTOTAG, "Flag %d already %s", bit_num, flag ? "Set" : "Cleared");
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
if (result) {
|
||||
ESP_LOGV(PROTOTAG, "%s Flag %d ", flag ? "Setting" : "Clearing", bit_num);
|
||||
if (!flag) {
|
||||
xEventGroupClearBits(_group, bit_num);
|
||||
} else {
|
||||
xEventGroupSetBits(_group, bit_num);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void PBHelper::SyncCommit(void* protoWrapper) {
|
||||
IPBBase* protoWrapperBase = reinterpret_cast<IPBBase*>(protoWrapper);
|
||||
if (protoWrapperBase) {
|
||||
protoWrapperBase->CommitChanges();
|
||||
}
|
||||
}
|
||||
std::vector<pb_byte_t> PBHelper::EncodeData(
|
||||
const pb_msgdesc_t* fields, const void* src_struct) {
|
||||
size_t datasz;
|
||||
ESP_LOGV(PROTOTAG, "EncodeData: getting size");
|
||||
if (!pb_get_encoded_size(&datasz, fields, src_struct)) {
|
||||
throw std::runtime_error("Failed to get encoded size.");
|
||||
}
|
||||
ESP_LOGV(PROTOTAG, "EncodeData: size: %d. Encoding", datasz);
|
||||
std::vector<pb_byte_t> data(datasz);
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(data.data(), datasz);
|
||||
if (!pb_encode(&stream, fields, src_struct)) {
|
||||
throw std::runtime_error("Failed to encode data.");
|
||||
}
|
||||
ESP_LOGV(PROTOTAG, "EncodeData: Done");
|
||||
return data;
|
||||
}
|
||||
|
||||
void PBHelper::DecodeData(
|
||||
std::vector<pb_byte_t> data, const pb_msgdesc_t* fields, void* target, bool noinit) {
|
||||
pb_istream_t stream = pb_istream_from_buffer((uint8_t*)data.data(), data.size());
|
||||
bool result = false;
|
||||
|
||||
// Decode the Protocol Buffers message
|
||||
if (noinit) {
|
||||
ESP_LOGD(PROTOTAG, "Decoding WITHOUT initialization");
|
||||
result = pb_decode_noinit(&stream, fields, data.data());
|
||||
} else {
|
||||
ESP_LOGD(PROTOTAG, "Decoding WITH initialization");
|
||||
result = pb_decode(&stream, fields, target);
|
||||
}
|
||||
if (!result) {
|
||||
throw std::runtime_error(
|
||||
std::string("Failed to decode settings: %s", PB_GET_ERROR(&stream)));
|
||||
}
|
||||
ESP_LOGD(PROTOTAG, "Data decoded");
|
||||
}
|
||||
void PBHelper::CommitFile(
|
||||
const std::string& filename, const pb_msgdesc_t* fields, const void* src_struct) {
|
||||
size_t datasz = 0;
|
||||
ESP_LOGD(PROTOTAG, "Committing data to file File %s", filename.c_str());
|
||||
|
||||
|
||||
if (!pb_get_encoded_size(&datasz, fields, src_struct)) {
|
||||
throw std::runtime_error("Failed to get encoded size.");
|
||||
}
|
||||
|
||||
if (datasz == 0) {
|
||||
ESP_LOGW(PROTOTAG, "File %s not written. Data size is zero", filename.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(PROTOTAG, "Committing to file %s", filename.c_str());
|
||||
if (!src_struct) {
|
||||
throw std::runtime_error("Null pointer received.");
|
||||
}
|
||||
FILE* file = fopen(filename.c_str(), "wb");
|
||||
if (file == nullptr) {
|
||||
throw std::runtime_error(std::string("Error opening file ") + filename.c_str());
|
||||
}
|
||||
pb_ostream_t filestream = PB_OSTREAM_SIZING;
|
||||
filestream.callback = &out_file_binding;
|
||||
filestream.state = file;
|
||||
filestream.max_size = SIZE_MAX;
|
||||
ESP_LOGD(PROTOTAG, "Starting file encode for %s", filename.c_str());
|
||||
if (!pb_encode(&filestream, fields, (void*)src_struct)) {
|
||||
fclose(file);
|
||||
throw std::runtime_error("Encoding file failed");
|
||||
}
|
||||
ESP_LOGD(PROTOTAG, "Encoded size: %d", filestream.bytes_written);
|
||||
if (filestream.bytes_written == 0) {
|
||||
ESP_LOGW(PROTOTAG, "Empty structure for file %s", filename.c_str());
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
bool PBHelper::IsDataDifferent(
|
||||
const pb_msgdesc_t* fields, const void* src_struct, const void* other_struct) {
|
||||
bool changed = false;
|
||||
try {
|
||||
ESP_LOGV(PROTOTAG, "Encoding Source data");
|
||||
auto src_data = EncodeData(fields, src_struct);
|
||||
ESP_LOGV(PROTOTAG, "Encoding Compared data");
|
||||
auto other_data = EncodeData(fields, other_struct);
|
||||
if (src_data.size() != other_data.size()) {
|
||||
ESP_LOGD(PROTOTAG, "IsDataDifferent: source and target size different: [%d!=%d]",
|
||||
src_data.size(), other_data.size());
|
||||
changed = true;
|
||||
} else if (src_data != other_data) {
|
||||
ESP_LOGD(PROTOTAG, "IsDataDifferent: source and target not the same");
|
||||
changed = true;
|
||||
}
|
||||
if (changed && esp_log_level_get(PROTOTAG) >= ESP_LOG_DEBUG) {
|
||||
ESP_LOGD(PROTOTAG, "Source data: ");
|
||||
dump_data((const uint8_t*)src_data.data(), src_data.size());
|
||||
ESP_LOGD(PROTOTAG, "Compared data: ");
|
||||
dump_data((const uint8_t*)other_data.data(), src_data.size());
|
||||
}
|
||||
} catch (const std::runtime_error& e) {
|
||||
throw std::runtime_error(std::string("Comparison failed: ") + e.what());
|
||||
}
|
||||
ESP_LOGD(PROTOTAG, "IsDataDifferent: %s", changed ? "TRUE" : "FALSE");
|
||||
return changed;
|
||||
}
|
||||
|
||||
void PBHelper::CopyStructure(
|
||||
const void* src_data, const pb_msgdesc_t* fields, void* target_data) {
|
||||
try {
|
||||
auto src = EncodeData(fields, src_data);
|
||||
ESP_LOGD(PROTOTAG, "Encoded structure to copy has %d bytes", src.size());
|
||||
DecodeData(src, fields, target_data, false);
|
||||
} catch (const std::runtime_error& e) {
|
||||
throw std::runtime_error(std::string("Copy failed: ") + e.what());
|
||||
}
|
||||
}
|
||||
void PBHelper::LoadFile(
|
||||
const std::string& filename, const pb_msgdesc_t* fields, void* target_data, bool noinit) {
|
||||
struct stat fileInformation;
|
||||
if (!get_file_info(&fileInformation, filename.c_str()) || fileInformation.st_size == 0) {
|
||||
throw FileNotFoundException("filename");
|
||||
}
|
||||
|
||||
FILE* file = fopen(filename.c_str(), "rb");
|
||||
ESP_LOGI(PROTOTAG, "Loading file %s", filename.c_str());
|
||||
if (file == nullptr) {
|
||||
int errNum = errno;
|
||||
ESP_LOGE(
|
||||
PROTOTAG, "Unable to open file: %s. Error: %s", filename.c_str(), strerror(errNum));
|
||||
throw std::runtime_error(
|
||||
"Unable to open file: " + filename + ". Error: " + strerror(errNum));
|
||||
}
|
||||
pb_istream_t filestream = PB_ISTREAM_EMPTY;
|
||||
filestream.callback = &in_file_binding;
|
||||
filestream.state = file;
|
||||
filestream.bytes_left = fileInformation.st_size;
|
||||
|
||||
ESP_LOGV(PROTOTAG, "Starting decode.");
|
||||
bool result = false;
|
||||
if (noinit) {
|
||||
ESP_LOGV(PROTOTAG, "Decoding WITHOUT initialization");
|
||||
result = pb_decode_noinit(&filestream, fields, target_data);
|
||||
} else {
|
||||
ESP_LOGV(PROTOTAG, "Decoding WITH initialization");
|
||||
result = pb_decode(&filestream, fields, target_data);
|
||||
}
|
||||
fclose(file);
|
||||
if (!result) {
|
||||
throw System::DecodeError(PB_GET_ERROR(&filestream));
|
||||
}
|
||||
ESP_LOGV(PROTOTAG, "Decode done.");
|
||||
}
|
||||
|
||||
bool PBHelper::FileExists(std::string filename) {
|
||||
struct stat fileInformation;
|
||||
ESP_LOGD(PROTOTAG, "Checking if file %s exists", filename.c_str());
|
||||
return get_file_info(&fileInformation, filename.c_str()) && fileInformation.st_size > 0;
|
||||
}
|
||||
bool PBHelper::FileExists() { return FileExists(filename); }
|
||||
bool PBHelper::IsLoading() { return xEventGroupGetBits(_group) & static_cast<int>(Flags::LOAD); }
|
||||
void PBHelper::SetLoading(bool active) { SetGroupBit(Flags::LOAD, active); }
|
||||
|
||||
bool PBHelper::WaitForCommit(uint8_t retries=2) {
|
||||
auto remain= retries;
|
||||
auto bits = xEventGroupGetBits(_group);
|
||||
bool commit_pending = HasChanges();
|
||||
ESP_LOGD(PROTOTAG, "Entering WaitForCommit bits: %d, changes? %s", bits,
|
||||
commit_pending ? "YES" : "NO");
|
||||
while (commit_pending && remain-->0) {
|
||||
ESP_LOGD(PROTOTAG, "Waiting for config commit ...");
|
||||
auto bits = xEventGroupWaitBits(
|
||||
_group, static_cast<int>(Flags::COMMITTED), pdFALSE, pdTRUE, (MaxDelay * 2) / portTICK_PERIOD_MS);
|
||||
commit_pending = !(bits & static_cast<int>(Flags::COMMITTED));
|
||||
ESP_LOGD(
|
||||
PROTOTAG, "WaitForCommit bits: %d, changes? %s", bits, commit_pending ? "YES" : "NO");
|
||||
if (commit_pending) {
|
||||
ESP_LOGW(PROTOTAG, "Timeout waiting for config commit for [%s]", name.c_str());
|
||||
} else {
|
||||
ESP_LOGI(PROTOTAG, "Changes to %s committed", name.c_str());
|
||||
}
|
||||
}
|
||||
return !commit_pending;
|
||||
}
|
||||
|
||||
void PBHelper::RaiseChangedAsync() {
|
||||
if(_no_save){
|
||||
ESP_LOGD(PROTOTAG,"Ignoring changes for %s, as it is marked not to be saved", name.c_str());
|
||||
}
|
||||
ESP_LOGI(PROTOTAG, "Changes made to %s", name.c_str());
|
||||
if (IsLoading()) {
|
||||
ESP_LOGD(PROTOTAG, "Ignoring raise modified during load of %s", name.c_str());
|
||||
} else {
|
||||
SetGroupBit(Flags::COMMITTED, false);
|
||||
network_async_commit_protowrapper(
|
||||
static_cast<void*>(static_cast<IPBBase*>(this)));
|
||||
}
|
||||
}
|
||||
const std::string& PBHelper::GetFileName() { return filename; }
|
||||
bool PBHelper::HasChanges() { return !(xEventGroupGetBits(_group) & static_cast<int>(Flags::COMMITTED)); }
|
||||
} // namespace PlatformConfig
|
||||
|
||||
bool proto_load_file(
|
||||
const char* filename, const pb_msgdesc_t* fields, void* target_data, bool noinit = false) {
|
||||
try {
|
||||
ESP_LOGI(System::PBHelper::PROTOTAG,"Loading file %s",filename);
|
||||
System::PBHelper::LoadFile(std::string(filename), fields, target_data, noinit);
|
||||
} catch (const std::exception& e) {
|
||||
if(!noinit){
|
||||
// initialize the structure
|
||||
pb_istream_t stream = pb_istream_from_buffer(nullptr, 0);
|
||||
pb_decode(&stream, fields, target_data);
|
||||
}
|
||||
ESP_LOGE(System::PBHelper::PROTOTAG, "Error loading file %s: %s", filename, e.what());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
331
components/platform_config/PBW.h
Normal file
331
components/platform_config/PBW.h
Normal file
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
*
|
||||
* Sebastien L. 2023, sle118@hotmail.com
|
||||
* Philippe G. 2023, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* License Overview:
|
||||
* ----------------
|
||||
* The MIT License is a permissive open source license. As a user of this software, you are free to:
|
||||
* - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
|
||||
* - Use the software for private, commercial, or any other purposes.
|
||||
*
|
||||
* Conditions:
|
||||
* - You must include the above copyright notice and this permission notice in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* The MIT License offers a high degree of freedom and is well-suited for both open source and
|
||||
* commercial applications. It places minimal restrictions on how the software can be used,
|
||||
* modified, and redistributed. For more details on the MIT License, please refer to the link above.
|
||||
*/
|
||||
#pragma once
|
||||
#ifndef LOG_LOCAL_LEVEL
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
|
||||
#include "Locking.h"
|
||||
#include "MessageDefinition.pb.h"
|
||||
#include "accessors.h"
|
||||
#include "configuration.pb.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "network_manager.h"
|
||||
#include "pb.h"
|
||||
#include "pb_decode.h" // Nanopb header for decoding (deserialization)
|
||||
#include "pb_encode.h" // Nanopb header for encoding (serialization)
|
||||
#include "tools.h"
|
||||
#include "tools_spiffs_utils.h"
|
||||
#include "bootstate.h"
|
||||
#include "State.pb.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
// #include <functional>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace System {
|
||||
template <typename T> struct has_target_implementation : std::false_type {};
|
||||
|
||||
class FileNotFoundException : public std::runtime_error {
|
||||
public:
|
||||
explicit FileNotFoundException(const std::string& message) : std::runtime_error(message) {}
|
||||
};
|
||||
class DecodeError : public std::runtime_error {
|
||||
public:
|
||||
explicit DecodeError(const std::string& message) : std::runtime_error(message) {}
|
||||
};
|
||||
class IPBBase {
|
||||
public:
|
||||
virtual void CommitChanges() = 0;
|
||||
virtual ~IPBBase() {}
|
||||
};
|
||||
class PBHelper : public IPBBase {
|
||||
|
||||
protected:
|
||||
const pb_msgdesc_t* _fields;
|
||||
EventGroupHandle_t _group;
|
||||
std::string name;
|
||||
std::string filename;
|
||||
static const int MaxDelay = 1000;
|
||||
Locking _lock;
|
||||
size_t _datasize;
|
||||
bool _no_save;
|
||||
|
||||
public:
|
||||
enum class Flags { COMMITTED = BIT0, LOAD = BIT1 };
|
||||
bool SetGroupBit(Flags flags, bool flag);
|
||||
static const char* PROTOTAG;
|
||||
std::string& GetName() { return name; }
|
||||
const char* GetCName() { return name.c_str(); }
|
||||
static std::string GetDefFileName(std::string name){
|
||||
return std::string(spiffs_base_path) + "/data/def_" + name + ".bin";
|
||||
}
|
||||
std::string GetDefFileName(){
|
||||
return GetDefFileName(this->name);
|
||||
}
|
||||
size_t GetDataSize(){
|
||||
return _datasize;
|
||||
}
|
||||
PBHelper(std::string name, const pb_msgdesc_t* fields, size_t defn_size, size_t datasize, bool no_save = false)
|
||||
: _fields(fields), _group(xEventGroupCreate()), name(std::move(name)), _lock(this->name),_datasize(datasize), _no_save(no_save) {
|
||||
sys_message_def definition = sys_message_def_init_default;
|
||||
bool savedef = false;
|
||||
ESP_LOGD(PROTOTAG,"Getting definition file name");
|
||||
auto deffile = GetDefFileName();
|
||||
ESP_LOGD(PROTOTAG,"Instantiating with definition size %d and data size %d", defn_size,datasize);
|
||||
|
||||
try {
|
||||
PBHelper::LoadFile(deffile, &sys_message_def_msg, static_cast<void*>(&definition));
|
||||
if (definition.data->size != defn_size || definition.datasize != _datasize) {
|
||||
ESP_LOGW(PROTOTAG, "Structure definition %s has changed", this->name.c_str());
|
||||
if (!is_recovery_running) {
|
||||
savedef = true;
|
||||
pb_release(&sys_message_def_msg, &definition);
|
||||
} else {
|
||||
ESP_LOGW(PROTOTAG, "Using existing definition for recovery");
|
||||
_fields = reinterpret_cast<const pb_msgdesc_t*>(definition.data->bytes);
|
||||
_datasize = definition.datasize;
|
||||
}
|
||||
}
|
||||
} catch (const FileNotFoundException& e) {
|
||||
savedef = true;
|
||||
}
|
||||
|
||||
if (savedef) {
|
||||
ESP_LOGW(PROTOTAG, "Saving definition for structure %s", this->name.c_str());
|
||||
auto data = (pb_bytes_array_t*)malloc_init_external(sizeof(pb_bytes_array_t)+defn_size);
|
||||
memcpy(&data->bytes, fields, defn_size);
|
||||
data->size = defn_size;
|
||||
definition.data = data;
|
||||
definition.datasize = _datasize;
|
||||
ESP_LOGD(PROTOTAG,"Committing structure with %d bytes ",data->size);
|
||||
PBHelper::CommitFile(deffile, &sys_message_def_msg, &definition);
|
||||
ESP_LOGD(PROTOTAG,"Releasing memory");
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
void ResetModified();
|
||||
static void SyncCommit(void* protoWrapper);
|
||||
static void CommitFile(const std::string& filename, const pb_msgdesc_t* fields, const void* src_struct);
|
||||
static bool IsDataDifferent(const pb_msgdesc_t* fields, const void* src_struct, const void* other_struct);
|
||||
|
||||
static void CopyStructure(const void* src_data, const pb_msgdesc_t* fields, void* target_data);
|
||||
static void LoadFile(const std::string& filename, const pb_msgdesc_t* fields, void* target_data, bool noinit = false);
|
||||
static std::vector<pb_byte_t> EncodeData(const pb_msgdesc_t* fields, const void* src_struct);
|
||||
static void DecodeData(std::vector<pb_byte_t> data, const pb_msgdesc_t* fields, void* target, bool noinit = false);
|
||||
bool FileExists(std::string filename);
|
||||
bool FileExists();
|
||||
bool IsLoading();
|
||||
void SetLoading(bool active);
|
||||
bool WaitForCommit(uint8_t retries );
|
||||
void RaiseChangedAsync();
|
||||
const std::string& GetFileName();
|
||||
bool HasChanges();
|
||||
};
|
||||
template <typename T> class PB : public PBHelper {
|
||||
private:
|
||||
T *_root;
|
||||
|
||||
// Generic _setTarget implementation
|
||||
void _setTarget(std::string target, std::false_type) { ESP_LOGE(PROTOTAG, "Setting target not implemented for %s", name.c_str()); }
|
||||
|
||||
// Special handling for sys_config
|
||||
void _setTarget(std::string target, std::true_type) {
|
||||
if (_root->target) {
|
||||
free(_root->target);
|
||||
}
|
||||
_root->target = strdup_psram(target.c_str());
|
||||
}
|
||||
std::string _getTargetName(std::false_type) { return ""; }
|
||||
std::string _getTargetName(std::true_type) { return STR_OR_BLANK(_root->target); }
|
||||
|
||||
public:
|
||||
// Accessor for the underlying structure
|
||||
T& Root() { return *_root; }
|
||||
// Const accessor for the underlying structure
|
||||
|
||||
const T& Root() const { return *_root; }
|
||||
T* get() { return _root; }
|
||||
const T* get() const { return (const T*)_root; }
|
||||
|
||||
// Constructor
|
||||
explicit PB(std::string name, const pb_msgdesc_t* fields, size_t defn_size, bool no_save = false) :
|
||||
PBHelper(std::move(name), fields,defn_size, sizeof(T), no_save) {
|
||||
ESP_LOGD(PROTOTAG, "Instantiating PB class for %s with data size %d", this->name.c_str(), sizeof(T));
|
||||
ResetModified();
|
||||
filename = std::string(spiffs_base_path) + "/data/" + this->name + ".bin";
|
||||
_root = (T*)(malloc_init_external(_datasize));
|
||||
memset(_root, 0x00, sizeof(_datasize));
|
||||
}
|
||||
|
||||
std::string GetTargetName() { return _getTargetName(has_target_implementation<T>{}); }
|
||||
void SetTarget(std::string targetname, bool skip_commit = false) {
|
||||
std::string newtarget = trim(targetname);
|
||||
std::string currenttarget = trim(GetTargetName());
|
||||
ESP_LOGD(PROTOTAG, "SetTarget called with %s", newtarget.c_str());
|
||||
if (newtarget == currenttarget && !newtarget.empty()) {
|
||||
ESP_LOGD(PROTOTAG, "Target name %s not changed for %s", currenttarget.c_str(), name.c_str());
|
||||
} else if (newtarget.empty() && !currenttarget.empty()) {
|
||||
ESP_LOGW(PROTOTAG, "Target name %s was removed for %s ", currenttarget.c_str(), name.c_str());
|
||||
}
|
||||
ESP_LOGI(PROTOTAG, "Setting target %s for %s", newtarget.c_str(), name.c_str());
|
||||
_setTarget(newtarget, has_target_implementation<T>{});
|
||||
if (!skip_commit) {
|
||||
ESP_LOGD(PROTOTAG, "Raising changed flag to commit new target name.");
|
||||
RaiseChangedAsync();
|
||||
} else {
|
||||
SetGroupBit(Flags::COMMITTED, false);
|
||||
}
|
||||
}
|
||||
std::string GetTargetFileName() {
|
||||
if (GetTargetName().empty()) {
|
||||
return "";
|
||||
}
|
||||
auto target_name = GetTargetName();
|
||||
return std::string(spiffs_base_path) + "/targets/" + toLowerStr(target_name) + "/" + name + ".bin";
|
||||
}
|
||||
void Reinitialize(bool skip_target = false, bool commit = false, std::string target_name = "") {
|
||||
ESP_LOGW(PROTOTAG, "Initializing %s", name.c_str());
|
||||
pb_istream_t stream = PB_ISTREAM_EMPTY;
|
||||
// initialize blank structure by
|
||||
// decoding a dummy stream
|
||||
pb_decode(&stream, _fields, _root);
|
||||
SetLoading(true);
|
||||
try {
|
||||
std::string fullpath = std::string(spiffs_base_path) + "/defaults/" + this->name + ".bin";
|
||||
ESP_LOGD(PROTOTAG, "Attempting to load defaults file for %s", fullpath.c_str());
|
||||
PBHelper::LoadFile(fullpath.c_str(), _fields, static_cast<void*>(_root), true);
|
||||
} catch (FileNotFoundException&) {
|
||||
ESP_LOGW(PROTOTAG, "No defaults found for %s", name.c_str());
|
||||
} catch (std::runtime_error& e) {
|
||||
ESP_LOGE(PROTOTAG, "Error loading Target %s overrides file: %s", GetTargetName().c_str(), e.what());
|
||||
}
|
||||
SetLoading(false);
|
||||
if (!skip_target) {
|
||||
if (!target_name.empty()) {
|
||||
SetTarget(target_name, true);
|
||||
}
|
||||
LoadTargetValues();
|
||||
}
|
||||
if (commit) {
|
||||
CommitChanges();
|
||||
}
|
||||
}
|
||||
void LoadFile(bool skip_target = false, bool noinit = false) {
|
||||
SetLoading(true);
|
||||
PBHelper::LoadFile(filename, _fields, static_cast<void*>(_root), noinit);
|
||||
SetLoading(false);
|
||||
if (!skip_target) {
|
||||
LoadTargetValues();
|
||||
}
|
||||
}
|
||||
void LoadTargetValues() {
|
||||
ESP_LOGD(PROTOTAG, "Loading target %s values for %s", GetTargetName().c_str(), name.c_str());
|
||||
if (GetTargetFileName().empty()) {
|
||||
ESP_LOGD(PROTOTAG, "No target file to load for %s", name.c_str());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// T old;
|
||||
// CopyTo(old);
|
||||
ESP_LOGI(PROTOTAG, "Loading target %s values for %s", GetTargetName().c_str(), name.c_str());
|
||||
PBHelper::LoadFile(GetTargetFileName(), _fields, static_cast<void*>(_root), true);
|
||||
// don't commit the values here, as it doesn't work well with
|
||||
// repeated values
|
||||
// if (*this != old) {
|
||||
// ESP_LOGI(PROTOTAG, "Changes detected from target values.");
|
||||
// RaiseChangedAsync();
|
||||
// }
|
||||
SetGroupBit(Flags::COMMITTED, false);
|
||||
|
||||
} catch (FileNotFoundException&) {
|
||||
ESP_LOGD(PROTOTAG, "Target %s overrides file not found for %s", GetTargetName().c_str(), name.c_str());
|
||||
} catch (std::runtime_error& e) {
|
||||
ESP_LOGE(PROTOTAG, "Error loading Target %s overrides file: %s", GetTargetName().c_str(), e.what());
|
||||
}
|
||||
}
|
||||
void CommitChanges() override {
|
||||
ESP_LOGI(PROTOTAG, "Committing %s to flash.", name.c_str());
|
||||
if (!_lock.Lock()) {
|
||||
ESP_LOGE(PROTOTAG, "Unable to lock config for commit ");
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(PROTOTAG, "Config Locked. Committing");
|
||||
try {
|
||||
CommitFile(filename, _fields, _root);
|
||||
} catch (...) {
|
||||
}
|
||||
ResetModified();
|
||||
_lock.Unlock();
|
||||
ESP_LOGI(PROTOTAG, "Done committing %s to flash.", name.c_str());
|
||||
}
|
||||
bool Lock() { return _lock.Lock(); }
|
||||
void Unlock() { return _lock.Unlock(); }
|
||||
std::vector<pb_byte_t> Encode() {
|
||||
auto data = std::vector<pb_byte_t>();
|
||||
if (!_lock.Lock()) {
|
||||
throw std::runtime_error("Unable to lock object");
|
||||
}
|
||||
data = EncodeData(_fields, this->_root);
|
||||
_lock.Unlock();
|
||||
return data;
|
||||
}
|
||||
|
||||
void CopyTo(T& target_data) {
|
||||
if (!_lock.Lock()) {
|
||||
ESP_LOGE(PROTOTAG, "Lock failed for %s", name.c_str());
|
||||
throw std::runtime_error("Lock failed ");
|
||||
}
|
||||
CopyStructure(_root, _fields, &target_data);
|
||||
_lock.Unlock();
|
||||
}
|
||||
void CopyFrom(const T& source_data) {
|
||||
if (!_lock.Lock()) {
|
||||
ESP_LOGE(PROTOTAG, "Lock failed for %s", name.c_str());
|
||||
throw std::runtime_error("Lock failed ");
|
||||
}
|
||||
CopyStructure(&source_data, _fields, _root);
|
||||
_lock.Unlock();
|
||||
}
|
||||
|
||||
bool operator!=(const T& other) const { return IsDataDifferent(_fields, _root, &other); }
|
||||
bool operator==(const T& other) const { return !IsDataDifferent(_fields, _root, &other); }
|
||||
void DecodeData(const std::vector<pb_byte_t> data, bool noinit = false) { PBHelper::DecodeData(data, _fields, (void*)_root, noinit); }
|
||||
|
||||
~PB() { vEventGroupDelete(_group); };
|
||||
};
|
||||
template <> struct has_target_implementation<sys_config> : std::true_type {};
|
||||
|
||||
template <> struct has_target_implementation<sys_state_data> : std::true_type {};
|
||||
|
||||
} // namespace PlatformConfig
|
||||
extern "C" {
|
||||
#endif
|
||||
bool proto_load_file(const char* filename, const pb_msgdesc_t* fields, void* target_data, bool noinit);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
845
components/platform_config/WifiList.cpp
Normal file
845
components/platform_config/WifiList.cpp
Normal file
@@ -0,0 +1,845 @@
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#include "WifiList.h"
|
||||
#include "Config.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include <memory>
|
||||
|
||||
static const char* TAG_CRED_MANAGER = "credentials_manager";
|
||||
bool sys_status_wifi_callback(pb_istream_t* istream, pb_ostream_t* ostream, const pb_field_iter_t* field) {
|
||||
return sys_net_config_callback(istream, ostream, field);
|
||||
}
|
||||
bool sys_net_config_callback(pb_istream_t* istream, pb_ostream_t* ostream, const pb_field_iter_t* field) {
|
||||
WifiList** managerPtr = static_cast<WifiList**>(field->pData);
|
||||
WifiList* manager = *managerPtr;
|
||||
|
||||
if (istream != NULL && (field->tag == sys_net_config_credentials_tag || field->tag == sys_status_wifi_scan_result_tag)) {
|
||||
if (manager == nullptr) {
|
||||
ESP_LOGE(TAG_CRED_MANAGER, "Invalid pointer to wifi list manager");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "Decoding credentials");
|
||||
sys_net_wifi_entry entry = sys_net_wifi_entry_init_default;
|
||||
if (!pb_decode(istream, &sys_net_wifi_entry_msg, &entry)) return false;
|
||||
printf("\nFound ssid %s, password %s\n", entry.ssid, entry.password);
|
||||
try {
|
||||
manager->AddUpdate(entry); // Add to the manager
|
||||
} catch (const std::exception& e) {
|
||||
ESP_LOGE(TAG_CRED_MANAGER, "decode exception: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "Credentials decoding completed");
|
||||
} else if (ostream != NULL && (field->tag == sys_net_config_credentials_tag || field->tag == sys_status_wifi_scan_result_tag)) {
|
||||
if (manager == nullptr) {
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "No wifi entries manager instance. nothing to encode");
|
||||
return true;
|
||||
}
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "Encoding %d access points", manager->GetCount());
|
||||
|
||||
for (int i = 0; i < manager->GetCount(); i++) {
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "Encoding credential #%d: SSID: %s, PASS: %s", i, manager->GetIndex(i)->ssid, manager->GetIndex(i)->password);
|
||||
if (!pb_encode_tag_for_field(ostream, field)) {
|
||||
return false;
|
||||
}
|
||||
if (!pb_encode_submessage(ostream, &sys_net_wifi_entry_msg, manager->GetIndex(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "Credentials encoding completed");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
std::string WifiList::GetBSSID(const wifi_event_sta_connected_t* evt) {
|
||||
char buffer[18]={};
|
||||
FormatBSSID(buffer, sizeof(buffer), evt->bssid);
|
||||
ESP_LOGD(TAG_CRED_MANAGER, "Formatted BSSID: %s", buffer);
|
||||
return std::string(buffer);
|
||||
}
|
||||
bool WifiList::OffsetTimeStamp(google_protobuf_Timestamp* ts) {
|
||||
timeval tts;
|
||||
google_protobuf_Timestamp gts;
|
||||
gettimeofday((struct timeval*)&tts, NULL);
|
||||
gts.nanos = tts.tv_usec * 1000;
|
||||
gts.seconds = tts.tv_sec;
|
||||
if (tts.tv_sec < 1704143717) {
|
||||
ESP_LOGE(TAG_CRED_MANAGER, "Error updating time stamp. Clock doesn't seem right");
|
||||
return false;
|
||||
}
|
||||
if (ts && ts->seconds < 1704143717) {
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "Updating time stamp based on new clock value");
|
||||
ts->seconds = gts.seconds - ts->seconds;
|
||||
ts->nanos = gts.nanos - ts->nanos;
|
||||
return true;
|
||||
}
|
||||
ESP_LOGD(TAG_CRED_MANAGER, "Time stamp already updated. Skipping");
|
||||
return false;
|
||||
}
|
||||
bool WifiList::UpdateTimeStamp(google_protobuf_Timestamp* ts, bool& has_flag_val) {
|
||||
ESP_RETURN_ON_FALSE(ts != nullptr, false, TAG_CRED_MANAGER, "Null pointer!");
|
||||
bool changed = false;
|
||||
timeval tts;
|
||||
google_protobuf_Timestamp gts;
|
||||
gettimeofday((struct timeval*)&tts, NULL);
|
||||
gts.nanos = tts.tv_usec * 1000;
|
||||
gts.seconds = tts.tv_sec;
|
||||
if (!has_flag_val || gts.nanos != ts->nanos || gts.seconds != ts->seconds) {
|
||||
ts->seconds = gts.seconds;
|
||||
ts->nanos = gts.nanos;
|
||||
has_flag_val = true;
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool WifiList::isEmpty(const char* str, size_t len) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (str[i] != '\0') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WifiList::Update(const wifi_ap_record_t* ap, bool connected) {
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
auto existing = Get(ap);
|
||||
if (!existing) {
|
||||
return false;
|
||||
}
|
||||
auto updated = ToSTAEntry(ap);
|
||||
updated.connected = connected;
|
||||
bool changed = Update(*existing, updated);
|
||||
Release(&updated);
|
||||
Unlock();
|
||||
return changed;
|
||||
}
|
||||
bool WifiList::Update(sys_net_wifi_entry& existingEntry, sys_net_wifi_entry& updated) {
|
||||
|
||||
// Check if any relevant fields have changed
|
||||
bool hasChanged = false;
|
||||
if (!isEmpty(updated.ssid, sizeof(updated.ssid)) && memcmp(existingEntry.ssid, updated.ssid, sizeof(existingEntry.ssid)) != 0) {
|
||||
memcpy(existingEntry.ssid, updated.ssid, sizeof(existingEntry.ssid));
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
// Check and copy BSSID if the compared BSSID is not empty
|
||||
if (!isEmpty(updated.bssid, sizeof(updated.bssid)) && strcmp(updated.bssid, "00:00:00:00:00:00") != 0 &&
|
||||
memcmp(existingEntry.bssid, updated.bssid, sizeof(existingEntry.bssid)) != 0) {
|
||||
memcpy(existingEntry.bssid, updated.bssid, sizeof(existingEntry.bssid));
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
// Check and copy password if the compared password is not empty
|
||||
if (!isEmpty(updated.password, sizeof(updated.password)) &&
|
||||
memcmp(existingEntry.password, updated.password, sizeof(existingEntry.password)) != 0) {
|
||||
memcpy(existingEntry.password, updated.password, sizeof(existingEntry.password));
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (existingEntry.channel != updated.channel && updated.channel > 0) {
|
||||
existingEntry.channel = updated.channel;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (existingEntry.auth_type != updated.auth_type && updated.auth_type != sys_net_auth_types_AUTH_UNKNOWN) {
|
||||
existingEntry.auth_type = updated.auth_type;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (areRadioTypesDifferent(existingEntry.radio_type, existingEntry.radio_type_count, updated.radio_type, updated.radio_type_count) &&
|
||||
updated.radio_type_count > 0 && updated.radio_type[0] != sys_net_radio_types_UNKNOWN) {
|
||||
|
||||
if (existingEntry.radio_type != nullptr) {
|
||||
// Free the old radio_type array if it exists
|
||||
delete[] existingEntry.radio_type;
|
||||
}
|
||||
// Allocate new memory and copy the updated radio types
|
||||
existingEntry.radio_type_count = updated.radio_type_count;
|
||||
existingEntry.radio_type = new sys_net_radio_types[updated.radio_type_count];
|
||||
std::copy(updated.radio_type, updated.radio_type + updated.radio_type_count, existingEntry.radio_type);
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (updated.has_last_try) {
|
||||
if (memcmp(&existingEntry.last_try, &updated.last_try, sizeof(existingEntry.last_try)) != 0) {
|
||||
memcpy(&existingEntry.last_try, &updated.last_try, sizeof(existingEntry.last_try));
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
if (updated.has_last_seen) {
|
||||
if (memcmp(&existingEntry.last_seen, &updated.last_seen, sizeof(existingEntry.last_seen)) != 0) {
|
||||
memcpy(&existingEntry.last_seen, &updated.last_seen, sizeof(existingEntry.last_seen));
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
if (existingEntry.has_last_seen != updated.has_last_seen && updated.has_last_seen) {
|
||||
existingEntry.has_last_seen = updated.has_last_seen;
|
||||
hasChanged = true;
|
||||
}
|
||||
if (existingEntry.has_last_try != updated.has_last_try && updated.has_last_try) {
|
||||
existingEntry.has_last_try = updated.has_last_try;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (existingEntry.connected != updated.connected && updated.connected) {
|
||||
existingEntry.connected = updated.connected;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (existingEntry.rssi != updated.rssi && updated.rssi != 0) {
|
||||
existingEntry.rssi = updated.rssi;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
std::string WifiList::formatRadioTypes(const sys_net_radio_types* radioTypes, pb_size_t count) {
|
||||
std::string result;
|
||||
|
||||
for (pb_size_t i = 0; i < count; ++i) {
|
||||
switch (radioTypes[i]) {
|
||||
case sys_net_radio_types_PHY_11B:
|
||||
result += "B";
|
||||
break;
|
||||
case sys_net_radio_types_PHY_11G:
|
||||
result += "G";
|
||||
break;
|
||||
case sys_net_radio_types_PHY_11N:
|
||||
result += "N";
|
||||
break;
|
||||
case sys_net_radio_types_LR:
|
||||
result += "L";
|
||||
break;
|
||||
case sys_net_radio_types_WPS:
|
||||
result += "W";
|
||||
break;
|
||||
case sys_net_radio_types_FTM_RESPONDER:
|
||||
result += "FR";
|
||||
break;
|
||||
case sys_net_radio_types_FTM_INITIATOR:
|
||||
result += "FI";
|
||||
break;
|
||||
case sys_net_radio_types_UNKNOWN:
|
||||
default:
|
||||
result += "U";
|
||||
break;
|
||||
}
|
||||
if (i < count - 1) {
|
||||
result += ",";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WifiList::Update(const wifi_sta_config_t* sta, bool connected) {
|
||||
if (!sta) {
|
||||
return false; // Invalid input
|
||||
}
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
sys_net_wifi_entry* existingEntry = Get(sta);
|
||||
|
||||
// If the entry does not exist, nothing to update
|
||||
if (!existingEntry) {
|
||||
Unlock();
|
||||
return false;
|
||||
}
|
||||
auto updated = ToSTAEntry(sta);
|
||||
// Check if any relevant fields have changed
|
||||
bool hasChanged = false;
|
||||
|
||||
if (strlen(updated.ssid) > 0 && memcmp(existingEntry->ssid, updated.ssid, sizeof(existingEntry->ssid)) != 0) {
|
||||
memcpy(existingEntry->ssid, updated.ssid, sizeof(existingEntry->ssid));
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (strlen(updated.bssid) > 0 && strcmp(updated.bssid, "00:00:00:00:00:00") != 0 &&
|
||||
memcmp(existingEntry->bssid, updated.bssid, sizeof(existingEntry->bssid)) != 0) {
|
||||
memcpy(existingEntry->bssid, updated.bssid, sizeof(existingEntry->bssid));
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (existingEntry->channel != updated.channel) {
|
||||
existingEntry->channel = updated.channel;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (existingEntry->auth_type != updated.auth_type && updated.auth_type != sys_net_auth_types_AUTH_UNKNOWN) {
|
||||
existingEntry->auth_type = updated.auth_type;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (areRadioTypesDifferent(existingEntry->radio_type, existingEntry->radio_type_count, updated.radio_type, updated.radio_type_count) &&
|
||||
updated.radio_type_count > 0 && updated.radio_type[0] != sys_net_radio_types_UNKNOWN) {
|
||||
// Free the old radio_type array if it exists
|
||||
delete[] existingEntry->radio_type;
|
||||
|
||||
// Allocate new memory and copy the updated radio types
|
||||
existingEntry->radio_type_count = updated.radio_type_count;
|
||||
existingEntry->radio_type = new sys_net_radio_types[updated.radio_type_count];
|
||||
std::copy(updated.radio_type, updated.radio_type + updated.radio_type_count, existingEntry->radio_type);
|
||||
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (updated.has_last_try) {
|
||||
if (memcmp(&existingEntry->last_try, &updated.last_try, sizeof(existingEntry->last_try)) != 0) {
|
||||
memcpy(&existingEntry->last_try, &updated.last_try, sizeof(existingEntry->last_try));
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated.has_last_seen) {
|
||||
if (memcmp(&existingEntry->last_seen, &updated.last_seen, sizeof(existingEntry->last_seen)) != 0) {
|
||||
memcpy(&existingEntry->last_seen, &updated.last_seen, sizeof(existingEntry->last_seen));
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
if (existingEntry->has_last_try != updated.has_last_try) {
|
||||
existingEntry->has_last_try = updated.has_last_try;
|
||||
hasChanged = true;
|
||||
}
|
||||
if (existingEntry->has_last_seen != updated.has_last_seen) {
|
||||
existingEntry->has_last_seen = updated.has_last_seen;
|
||||
hasChanged = true;
|
||||
}
|
||||
if (existingEntry->connected != (connected | updated.connected)) {
|
||||
existingEntry->connected = connected | updated.connected;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (strlen(updated.password) == 0 && strlen(existingEntry->password) > 0) {
|
||||
ESP_LOGW(TAG_CRED_MANAGER, "Updated password is empty, while existing password is %s for %s. Ignoring.", existingEntry->password,
|
||||
existingEntry->ssid);
|
||||
} else {
|
||||
if (memcmp(existingEntry->password, updated.password, sizeof(existingEntry->password)) != 0) {
|
||||
memcpy(existingEntry->password, updated.password, sizeof(existingEntry->password));
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingEntry->rssi != updated.rssi && updated.rssi != 0) {
|
||||
existingEntry->rssi = updated.rssi;
|
||||
hasChanged = true;
|
||||
}
|
||||
Release(&updated);
|
||||
Unlock();
|
||||
return hasChanged;
|
||||
}
|
||||
sys_net_wifi_entry WifiList::ToSTAEntry(const sys_net_wifi_entry* sta) {
|
||||
if (!sta) {
|
||||
throw std::runtime_error("Null STA entry provided");
|
||||
}
|
||||
sys_net_wifi_entry result = *sta;
|
||||
if (result.radio_type_count > 0) {
|
||||
std::unique_ptr<sys_net_radio_types[]> newRadioTypes(new sys_net_radio_types[result.radio_type_count]);
|
||||
if (!newRadioTypes) {
|
||||
throw std::runtime_error("Failed to allocate memory for radio types");
|
||||
}
|
||||
memcpy(newRadioTypes.get(), sta->radio_type, sizeof(sys_net_radio_types) * result.radio_type_count);
|
||||
result.radio_type = newRadioTypes.release();
|
||||
} else {
|
||||
result.radio_type = nullptr;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG_CRED_MANAGER, "ToSTAEntry: SSID: %s, PASS: %s", result.ssid, result.password);
|
||||
return result;
|
||||
}
|
||||
sys_net_wifi_entry WifiList::ToSTAEntry(const wifi_sta_config_t* sta, sys_net_auth_types auth_type) {
|
||||
return ToSTAEntry(sta, GetRadioTypes(nullptr), auth_type);
|
||||
}
|
||||
sys_net_wifi_entry WifiList::ToSTAEntry(
|
||||
const wifi_sta_config_t* sta, const std::list<sys_net_radio_types>& radio_types, sys_net_auth_types auth_type) {
|
||||
sys_net_wifi_entry item = sys_net_wifi_entry_init_default;
|
||||
ESP_LOGD(TAG_CRED_MANAGER,"%s (sta_config)","toSTAEntry");
|
||||
auto result = ToSTAEntry(sta, item, radio_types);
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "ToSTAEntry: SSID: %s, PASS: %s", result.ssid, result.password);
|
||||
return result;
|
||||
}
|
||||
sys_net_wifi_entry& WifiList::ToSTAEntry(const wifi_ap_record_t* ap, sys_net_wifi_entry& item) {
|
||||
if (ap) {
|
||||
auto radioTypes = GetRadioTypes(ap);
|
||||
item.radio_type_count=radioTypes.size();
|
||||
item.radio_type = new sys_net_radio_types[item.radio_type_count];
|
||||
int i = 0;
|
||||
for (const auto& type : radioTypes) {
|
||||
item.radio_type[i++] = type;
|
||||
}
|
||||
item.auth_type = GetAuthType(ap);
|
||||
FormatBSSID(ap, item);
|
||||
item.channel = ap->primary;
|
||||
item.rssi = ap->rssi;
|
||||
strncpy(item.ssid, GetSSID(ap).c_str(), sizeof(item.ssid));
|
||||
}
|
||||
return item;
|
||||
}
|
||||
sys_net_wifi_entry WifiList::ToSTAEntry(const wifi_ap_record_t* ap) {
|
||||
sys_net_wifi_entry item = sys_net_wifi_entry_init_default;
|
||||
return ToSTAEntry(ap, item);
|
||||
}
|
||||
sys_net_wifi_entry& WifiList::ToSTAEntry(
|
||||
const wifi_sta_config_t* sta, sys_net_wifi_entry& item, const std::list<sys_net_radio_types>& radio_types, sys_net_auth_types auth_type) {
|
||||
if (!sta) {
|
||||
ESP_LOGE(TAG_CRED_MANAGER, "Invalid access point entry");
|
||||
return item;
|
||||
}
|
||||
|
||||
std::string ssid = GetSSID(sta); // Convert SSID to std::string
|
||||
std::string password = GetPassword(sta); // Convert password to std::string
|
||||
|
||||
if (ssid.empty()) {
|
||||
ESP_LOGE(TAG_CRED_MANAGER, "Invalid access point ssid");
|
||||
return item;
|
||||
}
|
||||
memset(item.ssid, 0x00, sizeof(item.ssid));
|
||||
memset(item.password, 0x00, sizeof(item.password));
|
||||
strncpy(item.ssid, ssid.c_str(), sizeof(item.ssid)); // Copy SSID
|
||||
strncpy(item.password, password.c_str(), sizeof(item.password)); // Copy password
|
||||
if (LOG_LOCAL_LEVEL > ESP_LOG_DEBUG) {
|
||||
WifiList::FormatBSSID(item.bssid, sizeof(item.bssid), sta->bssid); // Format BSSID
|
||||
}
|
||||
item.channel = sta->channel;
|
||||
|
||||
item.auth_type = auth_type;
|
||||
|
||||
// Handle the radio_type array
|
||||
if (item.radio_type != nullptr) {
|
||||
delete[] item.radio_type; // Free existing array if any
|
||||
item.radio_type_count = 0;
|
||||
}
|
||||
item.radio_type_count = radio_types.size();
|
||||
item.radio_type = new sys_net_radio_types[item.radio_type_count];
|
||||
int i = 0;
|
||||
for (const auto& type : radio_types) {
|
||||
item.radio_type[i++] = type;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "ToSTAEntry wifi : %s, password: %s", item.ssid, item.password);
|
||||
return item;
|
||||
}
|
||||
bool WifiList::RemoveCredential(const wifi_sta_config_t* sta) { return RemoveCredential(GetSSID(sta)); }
|
||||
bool WifiList::RemoveCredential(const std::string& ssid) {
|
||||
auto it = credentials_.find(ssid);
|
||||
if (it != credentials_.end()) {
|
||||
// Release any dynamically allocated fields in the structure
|
||||
Release(&it->second);
|
||||
// Erase the entry from the map
|
||||
credentials_.erase(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WifiList::Clear() {
|
||||
if (Lock()) {
|
||||
for (auto& e : credentials_) {
|
||||
Release( &e.second);
|
||||
}
|
||||
credentials_.clear();
|
||||
Unlock();
|
||||
}
|
||||
}
|
||||
bool WifiList::ResetRSSI() {
|
||||
if (!Lock()) {
|
||||
ESP_LOGE(TAG_CRED_MANAGER, "Unable to lock structure %s", name_.c_str());
|
||||
return false;
|
||||
}
|
||||
for (auto& e : credentials_) {
|
||||
e.second.rssi = 0;
|
||||
}
|
||||
Unlock();
|
||||
return true;
|
||||
}
|
||||
bool WifiList::ResetConnected() {
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
for (auto& e : credentials_) {
|
||||
e.second.connected = false;
|
||||
}
|
||||
Unlock();
|
||||
return true;
|
||||
}
|
||||
sys_net_wifi_entry* WifiList::Get(const std::string& ssid) {
|
||||
auto it = credentials_.find(ssid);
|
||||
if (it != credentials_.end()) {
|
||||
return &(it->second);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
const sys_net_wifi_entry* WifiList::GetConnected() {
|
||||
if (!Lock()) {
|
||||
ESP_LOGE(TAG_CRED_MANAGER, "Unable to lock structure %s", name_.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
for (auto& e : credentials_) {
|
||||
if (e.second.connected) {
|
||||
return &e.second;
|
||||
}
|
||||
}
|
||||
Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
bool WifiList::SetConnected(const wifi_event_sta_connected_t* evt, bool connected) {
|
||||
auto ssid = GetSSID(evt);
|
||||
auto bssid = GetBSSID(evt);
|
||||
auto existing = Get(ssid);
|
||||
if (existing) {
|
||||
if (bssid != existing->bssid || existing->connected != connected || existing->auth_type != GetAuthType(evt->authmode) ||
|
||||
existing->channel != evt->channel) {
|
||||
ResetConnected();
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
strncpy(existing->bssid, bssid.c_str(), sizeof(existing->bssid));
|
||||
existing->connected = connected;
|
||||
existing->auth_type = GetAuthType(evt->authmode);
|
||||
existing->channel = evt->channel;
|
||||
config_raise_changed(false);
|
||||
Unlock();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG_CRED_MANAGER, "Cannot set unknown ssid %s as connected", ssid.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
sys_net_wifi_entry& WifiList::AddUpdate(const wifi_sta_config_t* sta, sys_net_auth_types auth_type) {
|
||||
return AddUpdate(sta, GetRadioTypes(nullptr), auth_type);
|
||||
}
|
||||
sys_net_wifi_entry& WifiList::AddUpdate(
|
||||
const wifi_sta_config_t* sta, const std::list<sys_net_radio_types>& radio_types, sys_net_auth_types auth_type) {
|
||||
auto ssid = GetSSID(sta);
|
||||
|
||||
if (!Exists(sta)) {
|
||||
auto entry = ToSTAEntry(sta, radio_types, auth_type);
|
||||
ESP_LOGD(TAG_CRED_MANAGER, "Added new entry %s to list %s", ssid.c_str(), name_.c_str());
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
credentials_[ssid] = entry;
|
||||
Unlock();
|
||||
} else {
|
||||
Update(sta);
|
||||
}
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "AddUpdate: SSID: %s, PASS: %s", ssid.c_str(), credentials_[ssid].password);
|
||||
return credentials_[ssid];
|
||||
}
|
||||
bool WifiList::UpdateFromClock() {
|
||||
bool changed = false;
|
||||
if (Lock()) {
|
||||
|
||||
for (auto iter = credentials_.begin(); iter != credentials_.end(); ++iter) {
|
||||
bool entrychanged = false;
|
||||
if (iter->second.has_last_seen) {
|
||||
entrychanged |= OffsetTimeStamp(&iter->second.last_seen);
|
||||
}
|
||||
if (iter->second.has_last_try) {
|
||||
entrychanged |= OffsetTimeStamp(&iter->second.last_try);
|
||||
}
|
||||
if (entrychanged) {
|
||||
ESP_LOGD(TAG_CRED_MANAGER, "Updated from clock");
|
||||
PrintWifiSTAEntry(iter->second);
|
||||
}
|
||||
changed |= entrychanged;
|
||||
}
|
||||
|
||||
Unlock();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
void WifiList::PrintTimeStamp(const google_protobuf_Timestamp* timestamp) {
|
||||
if (timestamp == NULL) {
|
||||
printf("Timestamp is NULL\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[80];
|
||||
|
||||
// Check for special case of time == 0
|
||||
if (timestamp->seconds == 0) {
|
||||
if (timestamp->nanos != 0) {
|
||||
printf("nanos not empty!");
|
||||
}
|
||||
snprintf(buffer, sizeof(buffer), "%-26s", "N/A");
|
||||
}
|
||||
// Check for timestamps less than 1704143717 (2024-01-01)
|
||||
else if (timestamp->seconds > 0 && timestamp->seconds < 1704143717) {
|
||||
// Convert seconds to time_t for use with localtime
|
||||
time_t rawtime = (time_t)timestamp->seconds;
|
||||
struct tm* timeinfo = localtime(&rawtime);
|
||||
|
||||
strftime(buffer, sizeof(buffer), "%H:%M:%S", timeinfo);
|
||||
} else {
|
||||
// Convert seconds to time_t for use with localtime
|
||||
time_t rawtime = (time_t)timestamp->seconds;
|
||||
struct tm* timeinfo = localtime(&rawtime);
|
||||
|
||||
strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", timeinfo);
|
||||
}
|
||||
|
||||
printf("%-26s", buffer);
|
||||
}
|
||||
bool WifiList::UpdateLastTry(const std::string ssid) {
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
auto entry = Get(ssid);
|
||||
ESP_RETURN_ON_FALSE(entry != nullptr, false, TAG_CRED_MANAGER, "Unknown ssid %s", ssid.c_str());
|
||||
ESP_RETURN_ON_FALSE(entry->ssid != nullptr, false, TAG_CRED_MANAGER, "Invalid pointer!");
|
||||
bool changed = UpdateLastTry(entry);
|
||||
Unlock();
|
||||
return changed;
|
||||
}
|
||||
bool WifiList::UpdateLastTry(sys_net_wifi_entry* entry) {
|
||||
ESP_RETURN_ON_FALSE(entry != nullptr, false, TAG_CRED_MANAGER, "Invalid pointer!");
|
||||
ESP_RETURN_ON_FALSE(entry->ssid != nullptr, false, TAG_CRED_MANAGER, "Invalid pointer!");
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "Updating last try for %s", entry->ssid);
|
||||
return UpdateTimeStamp(&entry->last_try, entry->has_last_try);
|
||||
}
|
||||
bool WifiList::UpdateLastTry(const wifi_sta_config_t* sta) { return UpdateLastTry(GetSSID(sta)); }
|
||||
bool WifiList::UpdateLastTry(const wifi_ap_record_t* ap) { return UpdateLastTry(GetSSID(ap)); }
|
||||
bool WifiList::UpdateLastSeen(const wifi_sta_config_t* sta) { return UpdateLastSeen(GetSSID(sta)); }
|
||||
bool WifiList::UpdateLastSeen(const wifi_ap_record_t* ap) { return UpdateLastSeen(GetSSID(ap)); }
|
||||
bool WifiList::UpdateLastSeen(const std::string ssid) {
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
auto entry = Get(ssid);
|
||||
bool changed = false;
|
||||
if (entry != nullptr) {
|
||||
changed = UpdateLastSeen(entry);
|
||||
}
|
||||
Unlock();
|
||||
return changed;
|
||||
}
|
||||
bool WifiList::UpdateLastSeen(sys_net_wifi_entry* entry) {
|
||||
ESP_RETURN_ON_FALSE(entry != nullptr, false, TAG_CRED_MANAGER, "Invalid pointer!");
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "Updating last seen for %s", entry->ssid);
|
||||
return UpdateTimeStamp(&entry->last_seen, entry->has_last_seen);
|
||||
}
|
||||
sys_net_wifi_entry& WifiList::AddUpdate(const wifi_ap_record_t* scan_rec) {
|
||||
auto ssid = GetSSID(scan_rec);
|
||||
if (!Exists(scan_rec)) {
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "Added new entry %s to list %s", ssid.c_str(), name_.c_str());
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
credentials_[ssid] = ToSTAEntry(scan_rec);
|
||||
Unlock();
|
||||
} else {
|
||||
Update(scan_rec);
|
||||
}
|
||||
return credentials_[ssid];
|
||||
}
|
||||
sys_net_wifi_entry& WifiList::AddUpdate(const char* ssid, const char* password) {
|
||||
if (ssid == nullptr || password == nullptr) {
|
||||
throw std::invalid_argument("SSID and password cannot be null");
|
||||
}
|
||||
// Ensure that the SSID and password are not too long
|
||||
if (std::strlen(ssid) >= sizeof(sys_net_wifi_entry::ssid) || std::strlen(password) >= sizeof(sys_net_wifi_entry::password)) {
|
||||
throw std::length_error("SSID or password is too long");
|
||||
}
|
||||
if (!Exists(ssid)) {
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
sys_net_wifi_entry newEntry = sys_net_wifi_entry_init_default;
|
||||
// Copy the SSID and password into the new entry, ensuring null termination
|
||||
std::strncpy(newEntry.ssid, ssid, sizeof(newEntry.ssid) - 1);
|
||||
newEntry.ssid[sizeof(newEntry.ssid) - 1] = '\0';
|
||||
std::strncpy(newEntry.password, password, sizeof(newEntry.password) - 1);
|
||||
newEntry.password[sizeof(newEntry.password) - 1] = '\0';
|
||||
ESP_LOGV(TAG_CRED_MANAGER, "Added new entry %s to list %s", ssid, name_.c_str());
|
||||
credentials_[ssid] = newEntry;
|
||||
Unlock();
|
||||
} else {
|
||||
auto existing = Get(ssid);
|
||||
if (strncmp(existing->password, password, sizeof(existing->password)) != 0) {
|
||||
strncpy(existing->password, password, sizeof(existing->password));
|
||||
existing->password[sizeof(existing->password) - 1] = '\0';
|
||||
}
|
||||
}
|
||||
return credentials_[ssid];
|
||||
}
|
||||
sys_net_wifi_entry& WifiList::AddUpdate(const sys_net_wifi_entry* sta, const char* password) {
|
||||
if (sta == nullptr) {
|
||||
throw std::invalid_argument("Entry pointer cannot be null");
|
||||
}
|
||||
auto converted = ToSTAEntry(sta);
|
||||
strncpy(converted.password, password, sizeof(converted.password));
|
||||
if (!Exists(sta->ssid)) {
|
||||
ESP_LOGD(TAG_CRED_MANAGER, "Added new entry %s to list %s", sta->ssid, name_.c_str());
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
credentials_[sta->ssid] = converted;
|
||||
Unlock();
|
||||
} else {
|
||||
auto existing = Get(sta->ssid);
|
||||
Update(*existing, converted);
|
||||
// release the converted structure now
|
||||
Release(&converted);
|
||||
}
|
||||
return credentials_[sta->ssid];
|
||||
}
|
||||
void WifiList::PrintString(const char* pData, size_t length, const char* format) {
|
||||
std::string buffer;
|
||||
for (size_t i = 0; i < length && pData[i]; i++) {
|
||||
if (isprint((char)pData[i])) {
|
||||
buffer += (char)pData[i]; // Print as a character
|
||||
} else {
|
||||
buffer += '?';
|
||||
}
|
||||
}
|
||||
printf(format, buffer.c_str());
|
||||
}
|
||||
void WifiList::PrintWifiSTAEntryTitle() {
|
||||
printf("-----------------------------------------------------------------------------------------------------------------------------------------"
|
||||
"--------------------\n");
|
||||
printf("CONN SSID PASSWORD BSSID RSSI CHAN AUTH RADIO LAST TRY LAST "
|
||||
"SEEN\n");
|
||||
printf("-----------------------------------------------------------------------------------------------------------------------------------------"
|
||||
"--------------------\n");
|
||||
}
|
||||
|
||||
void WifiList::PrintWifiSTAEntry(const sys_net_wifi_entry& entry) {
|
||||
google_protobuf_Timestamp gts = google_protobuf_Timestamp_init_default;
|
||||
printf("%-5c", entry.connected ? 'X' : ' ');
|
||||
printf("%-20s", entry.ssid);
|
||||
PrintString(entry.password, sizeof(entry.password), "%-25s");
|
||||
PrintString(entry.bssid, sizeof(entry.bssid), "%-20s");
|
||||
printf("%-4ddB", entry.rssi);
|
||||
printf("%3u ", static_cast<unsigned>(entry.channel));
|
||||
printf("%-14s", sys_net_auth_types_name(entry.auth_type));
|
||||
printf("%-9s", formatRadioTypes(entry.radio_type, entry.radio_type_count).c_str());
|
||||
if (entry.has_last_try) {
|
||||
PrintTimeStamp(&entry.last_try);
|
||||
} else {
|
||||
PrintTimeStamp(>s);
|
||||
}
|
||||
if (entry.has_last_seen) {
|
||||
PrintTimeStamp(&entry.last_seen);
|
||||
} else {
|
||||
PrintTimeStamp(>s);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
sys_net_wifi_entry* WifiList::GetIndex(size_t index) {
|
||||
if (index >= credentials_.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto it = credentials_.begin();
|
||||
std::advance(it, index);
|
||||
return &(it->second);
|
||||
}
|
||||
sys_net_wifi_entry& WifiList::GetStrongestSTA() {
|
||||
if (credentials_.empty()) {
|
||||
throw std::runtime_error("No credentials available");
|
||||
}
|
||||
|
||||
auto strongestIter = credentials_.begin();
|
||||
for (auto iter = credentials_.begin(); iter != credentials_.end(); ++iter) {
|
||||
if (iter->second.rssi > strongestIter->second.rssi) {
|
||||
strongestIter = iter;
|
||||
}
|
||||
}
|
||||
|
||||
return strongestIter->second;
|
||||
}
|
||||
|
||||
std::list<sys_net_radio_types> WifiList::GetRadioTypes(const wifi_ap_record_t* sta) {
|
||||
std::list<sys_net_radio_types> result;
|
||||
if (sta == nullptr) {
|
||||
result.push_back(sys_net_radio_types_UNKNOWN);
|
||||
} else {
|
||||
|
||||
// Check each bit field and return the corresponding enum value
|
||||
if (sta->phy_11b) {
|
||||
result.push_back(sys_net_radio_types_PHY_11B);
|
||||
}
|
||||
if (sta->phy_11g) {
|
||||
result.push_back(sys_net_radio_types_PHY_11G);
|
||||
}
|
||||
if (sta->phy_11n) {
|
||||
result.push_back(sys_net_radio_types_PHY_11N);
|
||||
}
|
||||
if (sta->phy_lr) {
|
||||
result.push_back(sys_net_radio_types_LR);
|
||||
}
|
||||
if (sta->wps) {
|
||||
result.push_back(sys_net_radio_types_WPS);
|
||||
}
|
||||
if (sta->ftm_responder) {
|
||||
result.push_back(sys_net_radio_types_FTM_RESPONDER);
|
||||
}
|
||||
if (sta->ftm_initiator) {
|
||||
result.push_back(sys_net_radio_types_FTM_INITIATOR);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
wifi_auth_mode_t WifiList::GetESPAuthMode(sys_net_auth_types auth_type) {
|
||||
switch (auth_type) {
|
||||
case sys_net_auth_types_OPEN:
|
||||
return WIFI_AUTH_OPEN;
|
||||
case sys_net_auth_types_WEP:
|
||||
return WIFI_AUTH_WEP;
|
||||
case sys_net_auth_types_WPA_PSK:
|
||||
return WIFI_AUTH_WPA_PSK;
|
||||
case sys_net_auth_types_WPA2_PSK:
|
||||
return WIFI_AUTH_WPA2_PSK;
|
||||
case sys_net_auth_types_WPA_WPA2_PSK:
|
||||
return WIFI_AUTH_WPA_WPA2_PSK;
|
||||
case sys_net_auth_types_WPA2_ENTERPRISE:
|
||||
return WIFI_AUTH_WPA2_ENTERPRISE;
|
||||
case sys_net_auth_types_WPA3_PSK:
|
||||
return WIFI_AUTH_WPA3_PSK;
|
||||
case sys_net_auth_types_WPA2_WPA3_PSK:
|
||||
return WIFI_AUTH_WPA2_WPA3_PSK;
|
||||
case sys_net_auth_types_WAPI_PSK:
|
||||
return WIFI_AUTH_WAPI_PSK;
|
||||
default:
|
||||
return WIFI_AUTH_OPEN; // Default case
|
||||
}
|
||||
}
|
||||
sys_net_auth_types WifiList::GetAuthType(const wifi_ap_record_t* ap) {
|
||||
return ap ? GetAuthType(ap->authmode) : sys_net_auth_types_AUTH_UNKNOWN;
|
||||
}
|
||||
sys_net_auth_types WifiList::GetAuthType(const wifi_auth_mode_t mode) {
|
||||
|
||||
switch (mode) {
|
||||
case WIFI_AUTH_OPEN:
|
||||
return sys_net_auth_types_OPEN;
|
||||
case WIFI_AUTH_WEP:
|
||||
return sys_net_auth_types_WEP;
|
||||
case WIFI_AUTH_WPA_PSK:
|
||||
return sys_net_auth_types_WPA_PSK;
|
||||
case WIFI_AUTH_WPA2_PSK:
|
||||
return sys_net_auth_types_WPA2_PSK;
|
||||
case WIFI_AUTH_WPA_WPA2_PSK:
|
||||
return sys_net_auth_types_WPA_WPA2_PSK;
|
||||
case WIFI_AUTH_WPA2_ENTERPRISE:
|
||||
return sys_net_auth_types_WPA2_ENTERPRISE;
|
||||
case WIFI_AUTH_WPA3_PSK:
|
||||
return sys_net_auth_types_WPA3_PSK;
|
||||
case WIFI_AUTH_WPA2_WPA3_PSK:
|
||||
return sys_net_auth_types_WPA2_WPA3_PSK;
|
||||
case WIFI_AUTH_WAPI_PSK:
|
||||
return sys_net_auth_types_WAPI_PSK;
|
||||
case WIFI_AUTH_MAX:
|
||||
return sys_net_auth_types_OPEN;
|
||||
}
|
||||
return sys_net_auth_types_AUTH_UNKNOWN;
|
||||
}
|
||||
185
components/platform_config/WifiList.h
Normal file
185
components/platform_config/WifiList.h
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
*
|
||||
* Sebastien L. 2023, sle118@hotmail.com
|
||||
* Philippe G. 2023, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* License Overview:
|
||||
* ----------------
|
||||
* The MIT License is a permissive open source license. As a user of this software, you are free to:
|
||||
* - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
|
||||
* - Use the software for private, commercial, or any other purposes.
|
||||
*
|
||||
* Conditions:
|
||||
* - You must include the above copyright notice and this permission notice in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* The MIT License offers a high degree of freedom and is well-suited for both open source and
|
||||
* commercial applications. It places minimal restrictions on how the software can be used,
|
||||
* modified, and redistributed. For more details on the MIT License, please refer to the link above.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Network.pb.h"
|
||||
#include "Status.pb.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "PBW.h"
|
||||
#include "pb_decode.h"
|
||||
#include "pb_encode.h"
|
||||
#ifdef __cplusplus
|
||||
#include "Locking.h"
|
||||
#include <cstring>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class WifiList : public System::Locking {
|
||||
public:
|
||||
WifiList(std::string name) : System::Locking(name), name_(name) {}
|
||||
~WifiList(){
|
||||
Clear();
|
||||
}
|
||||
static std::string toString(const uint8_t* data, size_t max_length) {
|
||||
// Find the actual length of the string up to max_length
|
||||
size_t length = strnlen(reinterpret_cast<const char*>(data), max_length);
|
||||
// Construct a std::string using the data and its length
|
||||
auto p = std::string(reinterpret_cast<const char*>(data), length);
|
||||
return p;
|
||||
}
|
||||
static void Release(sys_net_wifi_entry& entry){
|
||||
Release(&entry);
|
||||
}
|
||||
static void Release(sys_net_wifi_entry* entry){
|
||||
pb_release(&sys_net_wifi_entry_msg,entry);
|
||||
}
|
||||
static std::list<sys_net_radio_types> GetRadioTypes(const wifi_ap_record_t* sta);
|
||||
static wifi_auth_mode_t GetESPAuthMode(sys_net_auth_types auth_type);
|
||||
static sys_net_auth_types GetAuthType(const wifi_ap_record_t* ap);
|
||||
static sys_net_auth_types GetAuthType(const wifi_auth_mode_t mode);
|
||||
static bool areRadioTypesDifferent(const sys_net_radio_types* types1, pb_size_t count1, const sys_net_radio_types* types2, pb_size_t count2) {
|
||||
if (count1 != count2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (pb_size_t i = 0; i < count1; ++i) {
|
||||
if (types1[i] != types2[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
static std::string GetPassword(const wifi_sta_config_t* config) {
|
||||
auto p = toString(config->password, sizeof(config->password));
|
||||
return p;
|
||||
}
|
||||
|
||||
static std::string GetSSID(const wifi_event_sta_connected_t* evt) { return toString(evt->ssid, sizeof(evt->ssid)); }
|
||||
static std::string GetSSID(const wifi_sta_config_t* config) { return toString(config->ssid, sizeof(config->ssid)); }
|
||||
static std::string GetSSID(const wifi_ap_record_t* ap) { return toString(ap->ssid, sizeof(ap->ssid)); }
|
||||
static void FormatBSSID(char* buffer, size_t len, const uint8_t* bssid) {
|
||||
memset(buffer,0x00,len);
|
||||
snprintf(buffer, len, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
|
||||
}
|
||||
static void FormatBSSID(const wifi_ap_record_t* ap, sys_net_wifi_entry& entry) {
|
||||
memset(entry.bssid,0x00,sizeof(entry.bssid));
|
||||
if (!ap) return;
|
||||
FormatBSSID(entry.bssid, sizeof(entry.bssid), ap->bssid);
|
||||
}
|
||||
static std::string GetBSSID(const wifi_event_sta_connected_t* evt);
|
||||
static bool isEmpty(const char* str, size_t len);
|
||||
static void PrintString(const char* pData, size_t length, const char* format);
|
||||
static void PrintWifiSTAEntryTitle();
|
||||
|
||||
sys_net_wifi_entry& GetStrongestSTA();
|
||||
bool RemoveCredential(const std::string& ssid);
|
||||
bool RemoveCredential(const wifi_sta_config_t* sta);
|
||||
void Clear();
|
||||
|
||||
static sys_net_wifi_entry ToSTAEntry(const sys_net_wifi_entry* sta);
|
||||
static sys_net_wifi_entry ToSTAEntry(const wifi_sta_config_t* sta, const std::list<sys_net_radio_types>& radio_types,
|
||||
sys_net_auth_types auth_type = sys_net_auth_types_AUTH_UNKNOWN);
|
||||
static sys_net_wifi_entry ToSTAEntry(const wifi_sta_config_t* sta, sys_net_auth_types auth_type = sys_net_auth_types_AUTH_UNKNOWN);
|
||||
static sys_net_wifi_entry& ToSTAEntry(const wifi_ap_record_t* ap, sys_net_wifi_entry& item);
|
||||
static sys_net_wifi_entry ToSTAEntry(const wifi_ap_record_t* ap);
|
||||
static sys_net_wifi_entry& ToSTAEntry(const wifi_sta_config_t* sta, sys_net_wifi_entry& item, const std::list<sys_net_radio_types>& radio_types,
|
||||
sys_net_auth_types auth_type = sys_net_auth_types_AUTH_UNKNOWN);
|
||||
|
||||
static void PrintTimeStamp(const google_protobuf_Timestamp* timestamp);
|
||||
static void PrintWifiSTAEntry(const sys_net_wifi_entry& entry);
|
||||
static std::string formatRadioTypes(const sys_net_radio_types* radioTypes, pb_size_t count);
|
||||
|
||||
static bool OffsetTimeStamp(google_protobuf_Timestamp * ts);
|
||||
static bool UpdateTimeStamp(google_protobuf_Timestamp* ts, bool& has_flag_val);
|
||||
|
||||
bool ResetRSSI();
|
||||
bool ResetConnected();
|
||||
const sys_net_wifi_entry* GetConnected();
|
||||
|
||||
bool SetConnected(const wifi_event_sta_connected_t* evt, bool connected = true);
|
||||
size_t GetCount() const { return credentials_.size(); }
|
||||
sys_net_wifi_entry* GetIndex(size_t index);
|
||||
|
||||
bool Exists(const std::string& ssid) { return Get(ssid) != nullptr; }
|
||||
bool Exists(const char* ssid) { return Get(ssid) != nullptr; }
|
||||
bool Exists(const wifi_ap_record_t* ap) { return ap != nullptr && Get(GetSSID(ap)) != nullptr; }
|
||||
bool Exists(const wifi_sta_config_t* sta) { return Exists(GetSSID(sta)); }
|
||||
|
||||
bool Update(const wifi_sta_config_t* sta, bool connected = false);
|
||||
bool Update(const wifi_ap_record_t* ap, bool connected = false);
|
||||
static bool Update(sys_net_wifi_entry& existingEntry, sys_net_wifi_entry& compared);
|
||||
|
||||
sys_net_wifi_entry* Get(const wifi_sta_config_t* sta) { return Get(GetSSID(sta)); }
|
||||
sys_net_wifi_entry* Get(const wifi_ap_record_t* ap) { return Get(GetSSID(ap)); }
|
||||
sys_net_wifi_entry* Get(const char* ssid) { return Get(std::string(ssid)); }
|
||||
sys_net_wifi_entry* Get(const std::string& ssid);
|
||||
|
||||
bool UpdateFromClock();
|
||||
bool UpdateLastTry(const wifi_sta_config_t* sta);
|
||||
bool UpdateLastTry(const wifi_ap_record_t* ap);
|
||||
bool UpdateLastTry(const std::string ssid);
|
||||
bool UpdateLastTry(sys_net_wifi_entry* entry);
|
||||
bool UpdateLastSeen(const wifi_sta_config_t* sta);
|
||||
bool UpdateLastSeen(const wifi_ap_record_t* ap);
|
||||
bool UpdateLastSeen(const std::string ssid);
|
||||
bool UpdateLastSeen(sys_net_wifi_entry* entry);
|
||||
sys_net_wifi_entry& AddUpdate(const wifi_sta_config_t* sta, sys_net_auth_types auth_type = sys_net_auth_types_AUTH_UNKNOWN);
|
||||
sys_net_wifi_entry& AddUpdate(const wifi_sta_config_t* sta, const std::list<sys_net_radio_types>& radio_types,
|
||||
sys_net_auth_types auth_type = sys_net_auth_types_AUTH_UNKNOWN);
|
||||
sys_net_wifi_entry& AddUpdate(const wifi_ap_record_t* scan_rec);
|
||||
sys_net_wifi_entry& AddUpdate(const char* ssid = "", const char* password = "");
|
||||
sys_net_wifi_entry& AddUpdate(const sys_net_wifi_entry* existing, const char* password = "");
|
||||
// this one below is used by pb_decode
|
||||
void AddUpdate(const sys_net_wifi_entry& entry) {
|
||||
if (!Lock()) {
|
||||
throw std::runtime_error("Lock failed");
|
||||
}
|
||||
credentials_[entry.ssid] = entry;
|
||||
Unlock();
|
||||
}
|
||||
using Iterator = std::map<std::string, sys_net_wifi_entry>::iterator;
|
||||
using ConstIterator = std::map<std::string, sys_net_wifi_entry>::const_iterator;
|
||||
Iterator begin() { return credentials_.begin(); }
|
||||
ConstIterator begin() const { return credentials_.begin(); }
|
||||
Iterator end() { return credentials_.end(); }
|
||||
ConstIterator end() const { return credentials_.end(); }
|
||||
|
||||
private:
|
||||
static std::string FormatTimestamp(const google_protobuf_Timestamp& timestamp) {
|
||||
// Format the timestamp as needed
|
||||
// This is a placeholder implementation.
|
||||
return std::to_string(timestamp.seconds) + "s";
|
||||
}
|
||||
std::map<std::string, sys_net_wifi_entry> credentials_;
|
||||
std::string name_; // Name of the WifiCredentialsManager
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
#endif
|
||||
typedef struct WifiList WifiList;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
20
components/platform_config/test/CMakeLists.txt
Normal file
20
components/platform_config/test/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
idf_component_register(SRC_DIRS .
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES tools platform_config unity )
|
||||
# target_compile_options(__idf_platform_config PRIVATE --coverage)
|
||||
target_include_directories(${COMPONENT_LIB} PUBLIC SYSTEM ${CMAKE_SOURCE_DIR}/test_main)
|
||||
|
||||
message(STATUS "** PLATFORM PROTOBUF")
|
||||
include(../../../tools/protoc_utils/protobuf_utils.cmake)
|
||||
configure_env()
|
||||
file(GLOB PROTO_FILES *.proto)
|
||||
set(NANOPB_OPTIONS "-I${CMAKE_CURRENT_SOURCE_DIR}" "-I${PROJECT_ROOT_DIR}/protobuf/proto")
|
||||
nanopb_generate_cpp(PROTO_SRCS PROTO_HDRS RELPATH ${CMAKE_CURRENT_SOURCE_DIR} ${PROTO_FILES})
|
||||
|
||||
|
||||
# Create a custom target to generate the proto files
|
||||
set_source_files_properties(${PROTO_SRCS} ${PROTO_HDRS} PROPERTIES GENERATED TRUE)
|
||||
add_custom_target(generate_test_proto ALL DEPENDS ${PROTO_SRCS} ${PROTO_HDRS})
|
||||
target_sources(${COMPONENT_LIB} PRIVATE ${PROTO_SRCS})
|
||||
add_dependencies(${COMPONENT_LIB} generate_test_proto)
|
||||
target_include_directories(${COMPONENT_LIB} PUBLIC "include" ${CMAKE_CURRENT_BINARY_DIR} ${NANOPB_INCLUDE_DIRS} ${EXTRA_INCLUDES})
|
||||
29
components/platform_config/test/DAC_test_extra.proto
Normal file
29
components/platform_config/test/DAC_test_extra.proto
Normal file
@@ -0,0 +1,29 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package sys.dac.extra;
|
||||
import "DAC.proto";
|
||||
import "GPIO.proto";
|
||||
import "I2CBus.proto";
|
||||
import "DacControlSet.proto";
|
||||
import "customoptions.proto";
|
||||
import "nanopb.proto";
|
||||
option (nanopb_fileopt).enum_to_string = true;
|
||||
|
||||
message config {
|
||||
option (nanopb_msgopt).packed_struct = true;
|
||||
option (nanopb_msgopt).msgid = 80008;
|
||||
int32 bck = 1 [(cust_field).v_int32=-1];
|
||||
int32 ws = 2 [(cust_field).v_int32=-1];
|
||||
int32 dout = 3 [(cust_field).v_int32=-1];
|
||||
MCK mck = 4;
|
||||
gpio.pinmute = 5 [(cust_field).v_msg='{"pin":-1,"level":"LOW"}'];
|
||||
Models model = 6;
|
||||
I2CBus i2c = 7;
|
||||
dac.control.Set daccontrolset = 8;
|
||||
bool jack_mutes_amp = 9;
|
||||
uint32 addr = 10;
|
||||
int32 din = 11 [(cust_field).v_int32=-1];
|
||||
int32 dummy1 = 20;
|
||||
int64 dummy2 = 21;
|
||||
gpio.pindummy3 = 22;
|
||||
}
|
||||
30
components/platform_config/test/DAC_test_extra2.proto
Normal file
30
components/platform_config/test/DAC_test_extra2.proto
Normal file
@@ -0,0 +1,30 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package sys.dac.extra2;
|
||||
import "DAC.proto";
|
||||
import "GPIO.proto";
|
||||
import "I2CBus.proto";
|
||||
import "DacControlSet.proto";
|
||||
import "customoptions.proto";
|
||||
import "nanopb.proto";
|
||||
option (nanopb_fileopt).enum_to_string = true;
|
||||
|
||||
message config {
|
||||
option (nanopb_msgopt).packed_struct = true;
|
||||
option (nanopb_msgopt).msgid = 90008;
|
||||
int32 bck = 1 [(cust_field).v_int32=-1];
|
||||
int32 ws = 2 [(cust_field).v_int32=-1];
|
||||
int32 dout = 3 [(cust_field).v_int32=-1];
|
||||
MCK mck = 4;
|
||||
gpio.config mute = 5 [(cust_field).v_msg='{"pin":-1,"level":"LOW"}'];
|
||||
Models model = 6;
|
||||
I2CBus i2c = 7;
|
||||
dac.control.Set daccontrolset = 8;
|
||||
bool jack_mutes_amp = 9;
|
||||
uint32 addr = 10;
|
||||
int32 din = 11 [(cust_field).v_int32=-1];
|
||||
int32 dummy1 = 20;
|
||||
int64 dummy2 = 21;
|
||||
gpio.config dummy3 = 22;
|
||||
gpio.config dummy4 = 23;
|
||||
}
|
||||
956
components/platform_config/test/test_platform_config.cpp
Normal file
956
components/platform_config/test/test_platform_config.cpp
Normal file
@@ -0,0 +1,956 @@
|
||||
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
|
||||
#include "Config.h"
|
||||
#include "DAC.pb.h"
|
||||
#include "DAC_test_extra.pb.h"
|
||||
#include "DAC_test_extra2.pb.h"
|
||||
#include "Locking.h"
|
||||
#include "PBW.h"
|
||||
#include "State.pb.h"
|
||||
#include "WifiList.h"
|
||||
#include "configuration.pb.h"
|
||||
#include "esp_log.h"
|
||||
#include "test_common_init.h"
|
||||
#include "tools.h"
|
||||
#include "unity.h"
|
||||
|
||||
using namespace System;
|
||||
|
||||
// Helper macro to stringify the expanded value of a macro
|
||||
#define STRINGIFY(x) #x
|
||||
#define TOSTRING(x) STRINGIFY(x)
|
||||
|
||||
// Use the helper macro to stringify LOG_LOCAL_LEVEL
|
||||
#pragma message("The current log local level value is " TOSTRING(LOG_LOCAL_LEVEL))
|
||||
#define AUTH_MODE_INDEX(i) ((start_auth_mode + i) % WIFI_AUTH_MAX == 0 ? (wifi_auth_mode_t)1 : (wifi_auth_mode_t)(start_auth_mode + i))
|
||||
static const char* config_file_name = "settings.bin";
|
||||
|
||||
static const uint8_t bssid[6] = {0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56};
|
||||
uint8_t fill_last = 0x56;
|
||||
uint8_t start_rssi = 70;
|
||||
uint8_t start_channel = 2;
|
||||
auto start_auth_mode = WIFI_AUTH_WEP;
|
||||
uint8_t fill_bssid[6] = {0xAB, 0xCD, 0xEF, 0x12, 0x34, fill_last};
|
||||
static const char* char_bssid = "AB:CD:EF:12:34:56";
|
||||
static const char* TAG = "test_platform_config";
|
||||
const char* password = "TestPassword";
|
||||
bool HasMemoryUsageIncreased(int round) {
|
||||
static const size_t thresholdInternal = 500; // Example threshold for internal memory
|
||||
static const size_t thresholdSPIRAM = 1000; // Example threshold for SPI RAM
|
||||
size_t postTestFreeInternal = 0;
|
||||
size_t postTestFreeSPIRAM = 0;
|
||||
static size_t initialFreeInternal = 0;
|
||||
static size_t initialFreeSPIRAM = 0;
|
||||
auto minFreeInternal = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
|
||||
auto minFreeSPIRAM = heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM);
|
||||
if (round > 0) {
|
||||
postTestFreeInternal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
postTestFreeSPIRAM = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||
minFreeInternal = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
|
||||
minFreeSPIRAM = heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM);
|
||||
|
||||
printf("Memory usage summary (after round %d): "
|
||||
"Internal: %zu->%zu bytes. Min free: %zu. Increase: %d bytes. "
|
||||
"SPIRAM: %zu->%zu bytes. Min free: %zu. Increase: %d bytes\n",
|
||||
round, initialFreeInternal, postTestFreeInternal, minFreeInternal, initialFreeInternal - postTestFreeInternal, initialFreeSPIRAM,
|
||||
postTestFreeSPIRAM, minFreeSPIRAM, initialFreeSPIRAM - postTestFreeSPIRAM);
|
||||
int32_t diffInternal = initialFreeInternal > postTestFreeInternal ? initialFreeInternal - postTestFreeInternal : 0;
|
||||
int32_t diffSPIRAM = initialFreeSPIRAM > postTestFreeSPIRAM ? initialFreeSPIRAM - postTestFreeSPIRAM : 0;
|
||||
if (diffSPIRAM > 0 || diffInternal > 0) {
|
||||
ESP_LOGW(TAG, "Internal increase: %d, SPIRAM: %d", diffInternal, diffSPIRAM);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
initialFreeInternal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
initialFreeSPIRAM = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||
printf("Memory usage at start: "
|
||||
"Internal: %zu bytes. Min free: %zu. "
|
||||
"SPIRAM: %zu bytes. Min free: %zu.\n",
|
||||
initialFreeInternal, minFreeInternal, initialFreeSPIRAM, minFreeSPIRAM);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void advanceTime(int seconds) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
tv.tv_sec += seconds;
|
||||
tv.tv_usec += seconds * 100;
|
||||
settimeofday((const timeval*)&tv, 0);
|
||||
}
|
||||
wifi_event_sta_connected_t getMockConnectedEvent(int i = 0) {
|
||||
wifi_event_sta_connected_t mock = {};
|
||||
mock.authmode = WIFI_AUTH_WPA3_PSK;
|
||||
memset(mock.ssid, 0x00, sizeof(mock.ssid));
|
||||
memset(mock.bssid, 0x00, sizeof(mock.bssid));
|
||||
snprintf((char*)mock.ssid, sizeof(mock.ssid), "SSID_%d", i);
|
||||
fill_bssid[5] = fill_last + i;
|
||||
memcpy(mock.bssid, fill_bssid, sizeof(mock.bssid));
|
||||
mock.channel = 6 + i;
|
||||
mock.ssid_len = strlen((char*)mock.ssid);
|
||||
return mock;
|
||||
}
|
||||
wifi_ap_record_t getMockAPRec(int i = 0) {
|
||||
wifi_ap_record_t mock = {};
|
||||
mock.primary = start_channel + i; // Set some channel
|
||||
mock.rssi = start_rssi + i; // Set some RSSI value
|
||||
memset(mock.ssid, 0x00, sizeof(mock.ssid));
|
||||
memset(mock.bssid, 0x00, sizeof(mock.bssid));
|
||||
snprintf((char*)mock.ssid, sizeof(mock.ssid), "SSID_%d", i);
|
||||
fill_bssid[5] = fill_last + i;
|
||||
memcpy(mock.bssid, fill_bssid, sizeof(mock.bssid));
|
||||
mock.second = WIFI_SECOND_CHAN_ABOVE;
|
||||
mock.authmode = AUTH_MODE_INDEX(i); /**< authmode of AP */
|
||||
mock.pairwise_cipher = WIFI_CIPHER_TYPE_AES_GMAC128; /**< pairwise cipher of AP */
|
||||
mock.group_cipher = WIFI_CIPHER_TYPE_TKIP_CCMP; /**< group cipher of AP */
|
||||
mock.ant = WIFI_ANT_ANT1; /**< antenna used to receive beacon from AP */
|
||||
mock.phy_11b = 1; /**< bit: 0 flag to identify if 11b mode is enabled or not */
|
||||
mock.phy_11g = 0; /**< bit: 1 flag to identify if 11g mode is enabled or not */
|
||||
mock.phy_11n = 1; /**< bit: 2 flag to identify if 11n mode is enabled or not */
|
||||
mock.phy_lr = 0; /**< bit: 3 flag to identify if low rate is enabled or not */
|
||||
mock.wps = 1; /**< bit: 4 flag to identify if WPS is supported or not */
|
||||
mock.ftm_responder = 0; /**< bit: 5 flag to identify if FTM is supported in responder mode */
|
||||
mock.ftm_initiator = 1; /**< bit: 6 flag to identify if FTM is supported in initiator mode */
|
||||
return mock;
|
||||
}
|
||||
wifi_sta_config_t getMockSTA(int i = 0) {
|
||||
wifi_sta_config_t mock = {};
|
||||
memset(mock.ssid, 0x00, sizeof(mock.ssid));
|
||||
memset(mock.bssid, 0x00, sizeof(mock.bssid));
|
||||
snprintf((char*)mock.password, sizeof(mock.password), "Password_%d", i);
|
||||
snprintf((char*)mock.ssid, sizeof(mock.ssid), "SSID_%d", i);
|
||||
fill_bssid[5] = fill_last + i;
|
||||
memcpy(mock.bssid, fill_bssid, sizeof(mock.bssid));
|
||||
mock.channel = start_channel + i;
|
||||
mock.failure_retry_cnt = 2 + i;
|
||||
mock.listen_interval = 4 + i;
|
||||
mock.mbo_enabled = 1;
|
||||
mock.scan_method = WIFI_ALL_CHANNEL_SCAN;
|
||||
return mock;
|
||||
}
|
||||
sys_net_wifi_entry getMockEntry(int i = 0) {
|
||||
sys_net_wifi_entry mock = sys_net_wifi_entry_init_default;
|
||||
mock.auth_type = WifiList::GetAuthType(AUTH_MODE_INDEX(i));
|
||||
snprintf((char*)mock.password, sizeof(mock.password), "Password_%d", i);
|
||||
snprintf((char*)mock.ssid, sizeof(mock.ssid), "SSID_%d", i);
|
||||
fill_bssid[5] = fill_last + i;
|
||||
WifiList::FormatBSSID(mock.bssid, sizeof(mock.bssid), fill_bssid);
|
||||
mock.channel = start_channel + i;
|
||||
mock.connected = false;
|
||||
mock.has_last_seen = false;
|
||||
mock.has_last_try = false;
|
||||
mock.radio_type_count = 3;
|
||||
mock.radio_type = new sys_net_radio_types[mock.radio_type_count];
|
||||
mock.radio_type[0] = sys_net_radio_types_PHY_11B;
|
||||
mock.radio_type[1] = sys_net_radio_types_PHY_11G;
|
||||
mock.radio_type[2] = sys_net_radio_types_PHY_11N;
|
||||
mock.rssi = start_rssi + i;
|
||||
return mock;
|
||||
}
|
||||
|
||||
void FillSSIDs(WifiList& manager, int count, int start=1) {
|
||||
for (int i = start; i <= count; i++) {
|
||||
auto mock = getMockSTA(i);
|
||||
auto& entry = manager.AddUpdate(&mock);
|
||||
entry.rssi = 70 + i;
|
||||
entry.connected = true;
|
||||
}
|
||||
}
|
||||
void FillSSIDFromAPRec(WifiList& manager, int count, int start=1) {
|
||||
for (int i = start; i <= count; i++) {
|
||||
auto mock = getMockAPRec(i);
|
||||
auto& entry = manager.AddUpdate(&mock);
|
||||
entry.rssi = 70 + i;
|
||||
entry.connected = true;
|
||||
}
|
||||
}
|
||||
void FillSSIDFromMockEntry(WifiList& manager, int count, int start=1) {
|
||||
for (int i = start; i <= count; i++) {
|
||||
auto mock = getMockEntry(i);
|
||||
auto& entry = manager.AddUpdate(&mock);
|
||||
entry.rssi = 70 + i;
|
||||
entry.connected = true;
|
||||
WifiList::Release(mock);
|
||||
}
|
||||
}
|
||||
void eraseConfigFile() {
|
||||
PB<sys_config> wrapper(std::string("config"), &sys_config_msg, sizeof(sys_config_msg), false);
|
||||
erase_path(wrapper.GetFileName().c_str(), false);
|
||||
}
|
||||
|
||||
TEST_CASE("Test empty target settings empty", "[platform_config]") {
|
||||
PB<sys_config> wrapper(std::string("Config"), &sys_config_msg, sizeof(sys_config_msg), false);
|
||||
sys_config* conf = wrapper.get();
|
||||
assert(conf != nullptr);
|
||||
conf->target = strdup_psram("");
|
||||
#ifdef CONFIG_FW_PLATFORM_NAME
|
||||
system_set_string(&sys_config_msg, sys_config_target_tag, conf, CONFIG_FW_PLATFORM_NAME);
|
||||
TEST_ASSERT_TRUE(strcmp(conf->target, CONFIG_FW_PLATFORM_NAME) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Test init from default config", "[platform_config]") {
|
||||
struct stat fileInformation;
|
||||
PB<sys_config> wrapper(std::string("config"), &sys_config_msg, sizeof(sys_config_msg), false);
|
||||
sys_config* confprt = wrapper.get();
|
||||
auto filename = wrapper.GetFileName();
|
||||
erase_path(filename.c_str(), false);
|
||||
TEST_ASSERT_FALSE(get_file_info(&fileInformation, config_file_name));
|
||||
TEST_ASSERT_TRUE(strlen(STR_OR_BLANK(confprt->target)) == 0);
|
||||
TEST_ASSERT_FALSE(confprt->has_dev);
|
||||
}
|
||||
const sys_config* GetTestConfig() {
|
||||
static sys_config test_config;
|
||||
memset(&test_config, 0x00, sizeof(test_config));
|
||||
|
||||
// Assuming test_config is an instance of sys_config or a similar structure
|
||||
test_config.has_dev = true;
|
||||
test_config.dev.has_spi = true;
|
||||
test_config.dev.spi.mosi = 4;
|
||||
test_config.dev.spi.clk = 5;
|
||||
test_config.dev.spi.dc = 18;
|
||||
test_config.dev.spi.host = sys_dev_common_hosts_Host1;
|
||||
|
||||
test_config.dev.has_dac = true;
|
||||
test_config.dev.dac.bck = 25;
|
||||
test_config.dev.dac.ws = 26;
|
||||
test_config.dev.dac.dout = 33;
|
||||
test_config.dev.dac.model = sys_dac_models_WM8978;
|
||||
|
||||
test_config.dev.has_display = true;
|
||||
test_config.dev.display.has_common = true;
|
||||
test_config.dev.display.common.width = 256;
|
||||
test_config.dev.display.common.height = 64;
|
||||
test_config.dev.display.common.HFlip = false;
|
||||
test_config.dev.display.common.VFlip = false;
|
||||
test_config.dev.display.common.rotate = false;
|
||||
test_config.dev.display.common.driver = sys_display_drivers_SSD1322;
|
||||
test_config.dev.display.common.reset = 21;
|
||||
test_config.dev.display.which_dispType = sys_display_config_spi_tag;
|
||||
test_config.dev.display.dispType.spi.cs = 19;
|
||||
test_config.dev.display.dispType.spi.speed = 8000000;
|
||||
test_config.dev.has_rotary = true;
|
||||
|
||||
test_config.dev.rotary.A = 23;
|
||||
test_config.dev.rotary.B = 22;
|
||||
test_config.dev.rotary.SW = 34;
|
||||
|
||||
test_config.dev.rotary.volume = true;
|
||||
test_config.dev.rotary.longpress = true;
|
||||
test_config.has_names = true;
|
||||
strcpy(test_config.names.device, "test_name");
|
||||
if (!test_config.target) {
|
||||
test_config.target = strdup_psram("test_target");
|
||||
}
|
||||
return &test_config;
|
||||
}
|
||||
void check_sys_config_structure(sys_config* config) {
|
||||
auto check = GetTestConfig();
|
||||
// Test SPI configuration
|
||||
TEST_ASSERT_EQUAL(check->dev.has_spi, config->dev.has_spi);
|
||||
TEST_ASSERT_EQUAL(check->dev.spi.mosi, config->dev.spi.mosi);
|
||||
TEST_ASSERT_EQUAL(check->dev.spi.clk, config->dev.spi.clk);
|
||||
TEST_ASSERT_EQUAL(check->dev.spi.dc, config->dev.spi.dc);
|
||||
TEST_ASSERT_EQUAL(check->dev.spi.host, config->dev.spi.host);
|
||||
TEST_ASSERT_EQUAL(check->dev.has_dac, config->dev.has_dac);
|
||||
TEST_ASSERT_EQUAL(check->dev.dac.bck, config->dev.dac.bck);
|
||||
TEST_ASSERT_EQUAL(check->dev.dac.ws, config->dev.dac.ws);
|
||||
TEST_ASSERT_EQUAL(check->dev.dac.dout, config->dev.dac.dout);
|
||||
TEST_ASSERT_EQUAL(check->dev.dac.model, config->dev.dac.model);
|
||||
TEST_ASSERT_EQUAL(check->dev.has_display, config->dev.has_display);
|
||||
TEST_ASSERT_EQUAL(check->dev.display.common.width, config->dev.display.common.width);
|
||||
TEST_ASSERT_EQUAL(check->dev.display.common.height, config->dev.display.common.height);
|
||||
TEST_ASSERT_EQUAL(check->dev.display.common.HFlip, config->dev.display.common.HFlip);
|
||||
TEST_ASSERT_EQUAL(check->dev.display.common.VFlip, config->dev.display.common.VFlip);
|
||||
TEST_ASSERT_EQUAL(check->dev.display.common.rotate, config->dev.display.common.rotate);
|
||||
TEST_ASSERT_EQUAL(check->dev.display.common.driver, config->dev.display.common.driver);
|
||||
TEST_ASSERT_EQUAL(check->dev.display.common.reset, config->dev.display.common.reset);
|
||||
TEST_ASSERT_EQUAL(check->dev.display.which_dispType, config->dev.display.which_dispType);
|
||||
TEST_ASSERT_EQUAL(check->dev.display.dispType.spi.cs, config->dev.display.dispType.spi.cs);
|
||||
TEST_ASSERT_EQUAL(check->dev.display.dispType.spi.speed, config->dev.display.dispType.spi.speed);
|
||||
TEST_ASSERT_EQUAL(check->dev.has_rotary, config->dev.has_rotary);
|
||||
TEST_ASSERT_EQUAL(check->dev.rotary.A, config->dev.rotary.A);
|
||||
TEST_ASSERT_EQUAL(check->dev.rotary.B, config->dev.rotary.B);
|
||||
TEST_ASSERT_EQUAL(check->dev.rotary.SW, config->dev.rotary.SW);
|
||||
TEST_ASSERT_EQUAL(check->dev.rotary.volume, config->dev.rotary.volume);
|
||||
TEST_ASSERT_EQUAL(check->dev.rotary.longpress, config->dev.rotary.longpress);
|
||||
TEST_ASSERT_EQUAL_STRING(check->names.device, config->names.device);
|
||||
TEST_ASSERT_EQUAL_STRING(check->target, config->target);
|
||||
}
|
||||
TEST_CASE("Test change platform", "[platform_config]") {
|
||||
struct stat fileInformation;
|
||||
eraseConfigFile();
|
||||
std::stringstream test_target_file;
|
||||
test_target_file << spiffs_base_path << "/targets/" << GetTestConfig()->target << "/config.bin";
|
||||
PlatformConfig::ProtoWrapperHelper::CommitFile(test_target_file.str().c_str(), &sys_config_msg, GetTestConfig());
|
||||
|
||||
// first ensure that the target state file exists.
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInformation, test_target_file.str().c_str()));
|
||||
TEST_ASSERT_TRUE(fileInformation.st_size > 0);
|
||||
platform = nullptr;
|
||||
|
||||
// here we must use the configurator object
|
||||
// since we're testing some config_ functions
|
||||
std::string expectedTarget = "ESP32";
|
||||
TEST_ASSERT_NULL(platform);
|
||||
config_load();
|
||||
TEST_ASSERT_NOT_NULL(platform);
|
||||
TEST_ASSERT_NOT_NULL(platform->target)
|
||||
TEST_ASSERT_EQUAL_STRING(expectedTarget.c_str(), platform->target);
|
||||
config_set_target_reset(GetTestConfig()->target);
|
||||
check_sys_config_structure(platform);
|
||||
TEST_ASSERT_EQUAL_STRING(platform->target, GetTestConfig()->target);
|
||||
TEST_ASSERT_TRUE(erase_path(test_target_file.str().c_str(), false));
|
||||
TEST_ASSERT_FALSE(get_file_info(&fileInformation, test_target_file.str().c_str()));
|
||||
}
|
||||
|
||||
// TEST_CASE("Test load state", "[platform_config]") {
|
||||
// eraseConfigFile();
|
||||
// const char* urlvalue = "http://somerandomurl";
|
||||
// config_load();
|
||||
// system_set_string(&sys_state_data_msg, sys_state_data_ota_url_tag, sys_state, urlvalue);
|
||||
// TEST_ASSERT_EQUAL_STRING(urlvalue, sys_state->ota_url);
|
||||
// ESP_LOGI(TAG, "Raising state change");
|
||||
// config_raise_state_changed();
|
||||
|
||||
// // create an async timer lambda to trigger the commit after 1 second so
|
||||
// //we can test waitcommit
|
||||
// config_commit_config
|
||||
|
||||
// TEST_CASE("Test Raise State Change", "[platform_config]") {
|
||||
// // config_load();
|
||||
// ESP_LOGI(TAG, "Raising state change");
|
||||
// TEST_ASSERT_FALSE(configurator.HasStateChanges());
|
||||
// TEST_ASSERT_FALSE(configurator.HasChanges());
|
||||
// config_raise_state_changed();
|
||||
// TEST_ASSERT_TRUE(configurator.HasStateChanges());
|
||||
// TEST_ASSERT_FALSE(configurator.HasChanges());
|
||||
// configurator.ResetStateModified();
|
||||
// TEST_ASSERT_FALSE(configurator.HasStateChanges());
|
||||
// TEST_ASSERT_FALSE(configurator.HasChanges());
|
||||
// }
|
||||
// TEST_CASE("Test Raise Change", "[platform_config]") {
|
||||
// // config_load();
|
||||
// ESP_LOGI(TAG, "Raising change");
|
||||
// PlatformConfig::PB wrapper =
|
||||
// PlatformConfig::PB("config", "", &sys_config_msg, Init_sys_config);
|
||||
// TEST_ASSERT_FALSE(configurator.HasStateChanges());
|
||||
// TEST_ASSERT_FALSE(configurator.HasChanges());
|
||||
// config_raise_changed();
|
||||
// TEST_ASSERT_TRUE(configurator.HasChanges());
|
||||
// TEST_ASSERT_FALSE(configurator.HasStateChanges());
|
||||
// configurator.ResetModified();
|
||||
// TEST_ASSERT_FALSE(configurator.HasChanges());
|
||||
// TEST_ASSERT_FALSE(configurator.HasStateChanges());
|
||||
// }
|
||||
|
||||
TEST_CASE("Test Lock Unlock", "[platform_config]") {
|
||||
auto lock = PlatformConfig::Locking("test");
|
||||
TEST_ASSERT_FALSE(lock.IsLocked());
|
||||
TEST_ASSERT_TRUE(lock.Lock());
|
||||
TEST_ASSERT_TRUE(lock.Lock());
|
||||
TEST_ASSERT_TRUE(lock.IsLocked());
|
||||
lock.Unlock();
|
||||
TEST_ASSERT_TRUE(lock.IsLocked());
|
||||
lock.Unlock();
|
||||
TEST_ASSERT_FALSE(lock.IsLocked());
|
||||
}
|
||||
|
||||
TEST_CASE("Recovery not updating message definition", "[platform_config]") {
|
||||
is_recovery_running = false;
|
||||
auto name = std::string("extra");
|
||||
struct stat struct_info_extra;
|
||||
struct stat struct_info_reco;
|
||||
// create instance with fresh definition
|
||||
erase_path(PBHelper::GetDefFileName(name).c_str(), false);
|
||||
PB<sys_dac_extra_config> wrapper_extra(name.c_str(), &sys_dac_extra_config_msg, sizeof(sys_dac_extra_config_msg));
|
||||
TEST_ASSERT_TRUE(get_file_info(&struct_info_extra, wrapper_extra.GetDefFileName().c_str()));
|
||||
TEST_ASSERT_TRUE(struct_info_extra.st_size > 0);
|
||||
auto& extra = wrapper_extra.Root();
|
||||
extra.dummy1 = 20;
|
||||
extra.dummy2 = 30;
|
||||
extra.has_dummy3 = true;
|
||||
extra.dummy3.level = sys_gpio_lvl_HIGH;
|
||||
extra.dummy3.pin = 22;
|
||||
wrapper_extra.CommitChanges();
|
||||
|
||||
is_recovery_running = true;
|
||||
PB<sys_dac_config> wrapper(name, &sys_dac_config_msg, sizeof(sys_dac_config_msg));
|
||||
TEST_ASSERT_TRUE(get_file_info(&struct_info_reco, wrapper.GetDefFileName().c_str()));
|
||||
TEST_ASSERT_TRUE(struct_info_reco.st_size == struct_info_extra.st_size);
|
||||
TEST_ASSERT_EQUAL(wrapper_extra.GetDataSize(), wrapper.GetDataSize());
|
||||
wrapper.LoadFile(true);
|
||||
|
||||
sys_dac_extra_config* config = reinterpret_cast<sys_dac_extra_config*>(wrapper.get());
|
||||
TEST_ASSERT_EQUAL(config->dummy1, extra.dummy1);
|
||||
TEST_ASSERT_EQUAL(config->dummy2, extra.dummy2);
|
||||
TEST_ASSERT_EQUAL(config->has_dummy3, extra.has_dummy3);
|
||||
TEST_ASSERT_EQUAL(config->dummy3.level, extra.dummy3.level);
|
||||
TEST_ASSERT_EQUAL(config->dummy3.pin, extra.dummy3.pin);
|
||||
|
||||
config->bck = 55;
|
||||
wrapper.CommitChanges();
|
||||
PB<sys_dac_extra_config> check_structure(name.c_str(), &sys_dac_extra_config_msg, sizeof(sys_dac_extra_config_msg));
|
||||
check_structure.LoadFile(true);
|
||||
auto config_check = check_structure.get();
|
||||
TEST_ASSERT_EQUAL(config->bck, check_structure.get()->bck);
|
||||
TEST_ASSERT_EQUAL(config_check->dummy1, extra.dummy1);
|
||||
TEST_ASSERT_EQUAL(config_check->dummy2, extra.dummy2);
|
||||
TEST_ASSERT_EQUAL(config_check->has_dummy3, extra.has_dummy3);
|
||||
TEST_ASSERT_EQUAL(config_check->dummy3.level, extra.dummy3.level);
|
||||
TEST_ASSERT_EQUAL(config_check->dummy3.pin, extra.dummy3.pin);
|
||||
|
||||
// not simulate an update
|
||||
is_recovery_running = false;
|
||||
PB<sys_dac_extra2_config> extra2(name.c_str(), &sys_dac_extra2_config_msg, sizeof(sys_dac_extra2_config_msg));
|
||||
extra2.LoadFile(true);
|
||||
auto config_extra2 = extra2.get();
|
||||
config_extra2->has_dummy4 = true;
|
||||
config_extra2->dummy4.pin = 99;
|
||||
extra2.CommitChanges();
|
||||
is_recovery_running = true;
|
||||
PB<sys_dac_config> wrapper2(name, &sys_dac_config_msg, sizeof(sys_dac_config_msg));
|
||||
|
||||
wrapper2.LoadFile(true);
|
||||
wrapper2.Root().bck = 88;
|
||||
wrapper2.CommitChanges();
|
||||
|
||||
is_recovery_running = false;
|
||||
extra2.LoadFile(true);
|
||||
TEST_ASSERT_EQUAL(config_extra2->bck, 88);
|
||||
TEST_ASSERT_TRUE(config_extra2->has_dummy4);
|
||||
TEST_ASSERT_EQUAL(config_extra2->dummy4.pin, 99);
|
||||
}
|
||||
TEST_CASE("String conversion from uint8_t array", "[WifiCredentialsManager]") {
|
||||
// Prepare test data
|
||||
const uint8_t testData[] = {'T', 'e', 's', 't', '\0', 'D', 'a', 't', 'a'};
|
||||
const size_t testDataLength = 9; // Including '\0'
|
||||
|
||||
// Call the method
|
||||
std::string result = WifiList::toString(testData, testDataLength);
|
||||
|
||||
// Assert expectations
|
||||
TEST_ASSERT_EQUAL_STRING_LEN("Test", result.c_str(), result.length());
|
||||
}
|
||||
TEST_CASE("Get SSID from wifi_sta_config_t", "[WifiCredentialsManager]") {
|
||||
// Prepare test data
|
||||
wifi_sta_config_t testConfig = getMockSTA(1);
|
||||
// Call the method
|
||||
std::string result = WifiList::GetSSID(&testConfig);
|
||||
TEST_ASSERT_EQUAL_STRING("SSID_1", result.c_str());
|
||||
}
|
||||
TEST_CASE("Get SSID from wifi_event_sta_connected_t", "[WifiCredentialsManager]") {
|
||||
wifi_event_sta_connected_t testEvent = {};
|
||||
const char* ssid = "EventSSID";
|
||||
memcpy(testEvent.ssid, ssid, strlen(ssid) + 1); // Including '\0'
|
||||
|
||||
std::string result = WifiList::GetSSID(&testEvent);
|
||||
TEST_ASSERT_EQUAL_STRING(ssid, result.c_str());
|
||||
}
|
||||
TEST_CASE("Get SSID from wifi_ap_record_t", "[WifiCredentialsManager]") {
|
||||
wifi_ap_record_t testRecord = {};
|
||||
const char* ssid = "RecordSSID";
|
||||
memcpy(testRecord.ssid, ssid, strlen(ssid) + 1); // Including '\0'
|
||||
|
||||
std::string result = WifiList::GetSSID(&testRecord);
|
||||
TEST_ASSERT_EQUAL_STRING(ssid, result.c_str());
|
||||
}
|
||||
TEST_CASE("Get Password from wifi_sta_config_t", "[WifiCredentialsManager]") {
|
||||
wifi_sta_config_t testConfig = {};
|
||||
memcpy(testConfig.password, password, strlen(password) + 1); // Including '\0'
|
||||
|
||||
std::string result = WifiList::GetPassword(&testConfig);
|
||||
TEST_ASSERT_EQUAL_STRING(password, result.c_str());
|
||||
}
|
||||
TEST_CASE("Get BSSID from wifi_event_sta_connected_t", "[WifiCredentialsManager]") {
|
||||
wifi_event_sta_connected_t testEvent = {};
|
||||
memcpy(testEvent.bssid, bssid, sizeof(testEvent.bssid));
|
||||
std::string result = WifiCredentialsManager::GetBSSID(&testEvent);
|
||||
TEST_ASSERT_EQUAL_STRING(char_bssid, result.c_str());
|
||||
}
|
||||
TEST_CASE("Format BSSID from uint8_t array", "[WifiCredentialsManager]") {
|
||||
char buffer[18] = {0};
|
||||
WifiCredentialsManager::FormatBSSID(buffer, sizeof(buffer), bssid);
|
||||
TEST_ASSERT_EQUAL_STRING(char_bssid, buffer);
|
||||
}
|
||||
|
||||
TEST_CASE("Update timestamp", "[WifiCredentialsManager]") {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
|
||||
// Test with a non-null timestamp and an uninitialized flag
|
||||
google_protobuf_Timestamp ts = {0, 0};
|
||||
bool has_flag = false;
|
||||
|
||||
bool result = manager.UpdateTimeStamp(&ts, has_flag);
|
||||
|
||||
TEST_ASSERT_TRUE(result); // Check if the method returns true for change
|
||||
TEST_ASSERT_TRUE(has_flag); // Check if the flag is set to true
|
||||
TEST_ASSERT_NOT_EQUAL(0, ts.seconds); // Assuming gettimeofday() gives a non-zero time
|
||||
TEST_ASSERT_NOT_EQUAL(0, ts.nanos);
|
||||
|
||||
// Store the updated values for comparison
|
||||
long prev_seconds = ts.seconds;
|
||||
long prev_nanos = ts.nanos;
|
||||
advanceTime(2);
|
||||
|
||||
// Call the method again with the same timestamp
|
||||
result = manager.UpdateTimeStamp(&ts, has_flag);
|
||||
|
||||
// Since the timestamp should be updated, check if the new values are different
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_TRUE(has_flag);
|
||||
TEST_ASSERT_NOT_EQUAL(prev_seconds, ts.seconds);
|
||||
TEST_ASSERT_NOT_EQUAL(prev_nanos, ts.nanos);
|
||||
|
||||
// Test with a null timestamp
|
||||
result = manager.UpdateTimeStamp(nullptr, has_flag);
|
||||
|
||||
// The method should return false, and the flag should remain unchanged
|
||||
TEST_ASSERT_FALSE(result);
|
||||
TEST_ASSERT_TRUE(has_flag); // Flag remains unchanged
|
||||
advanceTime(-2);
|
||||
}
|
||||
|
||||
TEST_CASE("Update wifi entry", "[WifiCredentialsManager]") {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
|
||||
sys_net_wifi_entry existingEntry = getMockEntry();
|
||||
sys_net_wifi_entry updatedEntry = getMockEntry();
|
||||
|
||||
// Modify updatedEntry with different values
|
||||
strcpy(updatedEntry.password, "NewPassword");
|
||||
updatedEntry.channel = 5;
|
||||
updatedEntry.auth_type = sys_net_auth_types_WPA2_PSK;
|
||||
delete[] updatedEntry.radio_type;
|
||||
updatedEntry.radio_type_count = 1;
|
||||
updatedEntry.radio_type = new sys_net_radio_types[updatedEntry.radio_type_count]; // Dynamic allocation for radio_type
|
||||
updatedEntry.radio_type[0] = sys_net_radio_types_PHY_11N;
|
||||
|
||||
updatedEntry.rssi = 42;
|
||||
updatedEntry.connected = true;
|
||||
|
||||
// Call the Update method
|
||||
bool result = manager.Update(existingEntry, updatedEntry);
|
||||
|
||||
// Assert that the Update method returns true
|
||||
TEST_ASSERT_TRUE(result);
|
||||
|
||||
// Assert that the existingEntry is updated correctly
|
||||
TEST_ASSERT_EQUAL_STRING("NewPassword", existingEntry.password);
|
||||
TEST_ASSERT_EQUAL(updatedEntry.channel, existingEntry.channel);
|
||||
TEST_ASSERT_EQUAL(sys_net_auth_types_WPA2_PSK, existingEntry.auth_type);
|
||||
TEST_ASSERT_EQUAL(sys_net_radio_types_PHY_11N, existingEntry.radio_type[0]);
|
||||
TEST_ASSERT_EQUAL(updatedEntry.radio_type_count, existingEntry.radio_type_count);
|
||||
TEST_ASSERT_EQUAL(updatedEntry.rssi, existingEntry.rssi);
|
||||
TEST_ASSERT_TRUE(existingEntry.connected);
|
||||
|
||||
// Clean up dynamically allocated memory
|
||||
delete[] updatedEntry.radio_type;
|
||||
delete[] existingEntry.radio_type;
|
||||
}
|
||||
|
||||
TEST_CASE("Update wifi entry from AP record", "[WifiCredentialsManager]") {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
|
||||
// Create a mock wifi_ap_record_t
|
||||
wifi_ap_record_t mockAP = {};
|
||||
// Initialize mockAP with test data
|
||||
// ...
|
||||
|
||||
// Add the initial entry
|
||||
sys_net_wifi_entry initialEntry = manager.ToSTAEntry(&mockAP);
|
||||
initialEntry.connected = false; // Initially not connected
|
||||
manager.AddUpdate(initialEntry);
|
||||
|
||||
// Now call the update with the same AP but with 'connected' status
|
||||
bool result = manager.Update(&mockAP, true);
|
||||
|
||||
// Assert that the update was successful
|
||||
TEST_ASSERT_TRUE(result);
|
||||
|
||||
// Retrieve the entry and check if it's updated
|
||||
auto updatedEntry = manager.Get(&mockAP);
|
||||
TEST_ASSERT_NOT_NULL(updatedEntry);
|
||||
TEST_ASSERT_TRUE(updatedEntry->connected); // Check if connected is now true
|
||||
manager.Clear();
|
||||
}
|
||||
|
||||
TEST_CASE("Format radio types", "[WifiCredentialsManager]") {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
|
||||
// Create an array of radio types for testing
|
||||
sys_net_radio_types radioTypes[] = {sys_net_radio_types_PHY_11B, sys_net_radio_types_PHY_11G, sys_net_radio_types_PHY_11N, sys_net_radio_types_LR,
|
||||
sys_net_radio_types_WPS, sys_net_radio_types_FTM_RESPONDER, sys_net_radio_types_FTM_INITIATOR, sys_net_radio_types_UNKNOWN};
|
||||
pb_size_t count = sizeof(radioTypes) / sizeof(radioTypes[0]);
|
||||
|
||||
// Call the formatRadioTypes method
|
||||
std::string formattedString = manager.formatRadioTypes(radioTypes, count);
|
||||
|
||||
// Define the expected result
|
||||
std::string expectedResult = "B,G,N,L,W,FR,FI,U";
|
||||
|
||||
// Assert that the formatted string is as expected
|
||||
TEST_ASSERT_EQUAL_STRING(expectedResult.c_str(), formattedString.c_str());
|
||||
manager.Clear();
|
||||
}
|
||||
|
||||
TEST_CASE("Update wifi entry from STA config", "[WifiCredentialsManager]") {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
|
||||
// Create a mock wifi_sta_config_t
|
||||
wifi_sta_config_t mockSTA = getMockSTA();
|
||||
|
||||
// Create and add an initial entry
|
||||
sys_net_wifi_entry initialEntry = manager.ToSTAEntry(&mockSTA);
|
||||
initialEntry.connected = false; // Initially not connected
|
||||
manager.AddUpdate(initialEntry);
|
||||
|
||||
// Modify the mockSTA for the update
|
||||
mockSTA.channel = 6; // Change the channel
|
||||
|
||||
// Now call the update with the modified mockSTA and 'connected' status
|
||||
bool result = manager.Update(&mockSTA, true);
|
||||
|
||||
// Assert that the update was successful
|
||||
TEST_ASSERT_TRUE(result);
|
||||
|
||||
// Retrieve the updated entry and check if it's correctly updated
|
||||
auto updatedEntry = manager.Get(&mockSTA);
|
||||
TEST_ASSERT_NOT_NULL(updatedEntry);
|
||||
TEST_ASSERT_EQUAL(mockSTA.channel, updatedEntry->channel);
|
||||
TEST_ASSERT_TRUE(updatedEntry->connected); // Check if connected is now true
|
||||
manager.Clear();
|
||||
}
|
||||
|
||||
TEST_CASE("Convert AP record to STA entry", "[WifiCredentialsManager]") {
|
||||
int testindex = 1;
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
|
||||
// Create a mock wifi_ap_record_t
|
||||
wifi_ap_record_t mockAP = getMockAPRec(testindex);
|
||||
|
||||
sys_net_wifi_entry entry; // Create an entry to be populated
|
||||
|
||||
// Call ToSTAEntry
|
||||
sys_net_wifi_entry& resultEntry = manager.ToSTAEntry(&mockAP, entry);
|
||||
|
||||
// Assert that the entry is populated correctly
|
||||
TEST_ASSERT_EQUAL_STRING("SSID_1", resultEntry.ssid);
|
||||
TEST_ASSERT_EQUAL(start_channel + testindex, resultEntry.channel);
|
||||
TEST_ASSERT_EQUAL(start_rssi + testindex, resultEntry.rssi);
|
||||
TEST_ASSERT_EQUAL(WifiList::GetAuthType(AUTH_MODE_INDEX(testindex)), resultEntry.auth_type);
|
||||
TEST_ASSERT_EQUAL(4, resultEntry.radio_type_count);
|
||||
std::string formattedString = manager.formatRadioTypes(resultEntry.radio_type, resultEntry.radio_type_count);
|
||||
// Define the expected result
|
||||
std::string expectedResult = "B,N,W,FI";
|
||||
TEST_ASSERT_EQUAL_STRING(expectedResult.c_str(), formattedString.c_str());
|
||||
char bssid_buffer[20] = {};
|
||||
WifiList::FormatBSSID(bssid_buffer, sizeof(bssid_buffer), mockAP.bssid);
|
||||
TEST_ASSERT_EQUAL_STRING((char*)bssid_buffer, resultEntry.bssid);
|
||||
|
||||
manager.Clear();
|
||||
}
|
||||
|
||||
TEST_CASE("Remove entries from manager instance", "[WifiCredentialsManager]") {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
FillSSIDs(manager, 4);
|
||||
TEST_ASSERT_EQUAL(4, manager.GetCount());
|
||||
TEST_ASSERT_TRUE(manager.RemoveCredential("SSID_1"));
|
||||
TEST_ASSERT_EQUAL(3, manager.GetCount());
|
||||
TEST_ASSERT_FALSE(manager.RemoveCredential("SSID_1"));
|
||||
TEST_ASSERT_EQUAL(3, manager.GetCount());
|
||||
manager.Clear();
|
||||
TEST_ASSERT_EQUAL(0, manager.GetCount());
|
||||
|
||||
FillSSIDs(manager, 4);
|
||||
TEST_ASSERT_EQUAL(4, manager.GetCount());
|
||||
TEST_ASSERT_TRUE(manager.RemoveCredential(std::string("SSID_1")));
|
||||
TEST_ASSERT_FALSE(manager.RemoveCredential(std::string("SSID_1")));
|
||||
TEST_ASSERT_TRUE(manager.RemoveCredential("SSID_2"));
|
||||
TEST_ASSERT_FALSE(manager.RemoveCredential(std::string("SSID_2")));
|
||||
TEST_ASSERT_EQUAL(2, manager.GetCount());
|
||||
manager.Clear();
|
||||
TEST_ASSERT_EQUAL(0, manager.GetCount());
|
||||
}
|
||||
TEST_CASE("Reset all entries", "[WifiCredentialsManager]") {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
FillSSIDs(manager, 4);
|
||||
for (auto& e : manager) {
|
||||
TEST_ASSERT_TRUE(e.second.rssi > 0);
|
||||
TEST_ASSERT_TRUE(e.second.connected);
|
||||
}
|
||||
manager.ResetRSSI();
|
||||
for (auto& e : manager) {
|
||||
TEST_ASSERT_TRUE(e.second.rssi == 0);
|
||||
TEST_ASSERT_TRUE(e.second.connected);
|
||||
}
|
||||
manager.Clear();
|
||||
FillSSIDs(manager, 4);
|
||||
manager.ResetConnected();
|
||||
for (auto& e : manager) {
|
||||
TEST_ASSERT_TRUE(e.second.rssi > 0);
|
||||
TEST_ASSERT_FALSE(e.second.connected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Getting/setting Connected", "[WifiCredentialsManager]") {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
auto conn_mock_4 = getMockConnectedEvent(4);
|
||||
auto conn_mock_5 = getMockConnectedEvent(5);
|
||||
auto entry_5 = getMockEntry(5);
|
||||
char bssid_buffer[20] = {};
|
||||
FillSSIDs(manager, 4);
|
||||
for (auto& e : manager) {
|
||||
if (strcmp(e.second.ssid, "SSID_3") != 0) {
|
||||
e.second.connected = false;
|
||||
}
|
||||
}
|
||||
auto entry = manager.GetConnected();
|
||||
TEST_ASSERT_EQUAL_STRING("SSID_3", entry->ssid);
|
||||
TEST_ASSERT_FALSE(manager.SetConnected(&conn_mock_5, true));
|
||||
manager.AddUpdate(&entry_5);
|
||||
WifiCredentialsManager::Release(entry_5);
|
||||
TEST_ASSERT_TRUE(manager.SetConnected(&conn_mock_5, true));
|
||||
entry = manager.GetConnected();
|
||||
TEST_ASSERT_EQUAL_STRING_LEN((char*)conn_mock_5.ssid, entry->ssid, conn_mock_5.ssid_len);
|
||||
WifiCredentialsManager::FormatBSSID(bssid_buffer, sizeof(bssid_buffer), conn_mock_5.bssid);
|
||||
TEST_ASSERT_EQUAL_STRING((char*)bssid_buffer, entry->bssid);
|
||||
TEST_ASSERT_EQUAL(WifiList::GetAuthType(conn_mock_5.authmode), entry->auth_type);
|
||||
TEST_ASSERT_EQUAL(conn_mock_5.channel, entry->channel);
|
||||
TEST_ASSERT_TRUE(manager.SetConnected(&conn_mock_4, true));
|
||||
entry = manager.GetConnected();
|
||||
TEST_ASSERT_EQUAL_STRING_LEN((char*)conn_mock_4.ssid, entry->ssid, conn_mock_5.ssid_len);
|
||||
WifiList::FormatBSSID(bssid_buffer, sizeof(bssid_buffer), conn_mock_4.bssid);
|
||||
TEST_ASSERT_EQUAL_STRING((char*)bssid_buffer, entry->bssid);
|
||||
TEST_ASSERT_EQUAL(WifiList::GetAuthType(conn_mock_4.authmode), entry->auth_type);
|
||||
TEST_ASSERT_EQUAL(conn_mock_4.channel, entry->channel);
|
||||
manager.ResetConnected();
|
||||
TEST_ASSERT_NULL(manager.GetConnected());
|
||||
manager.Clear();
|
||||
}
|
||||
|
||||
TEST_CASE("Getting by index/by name", "[WifiCredentialsManager]") {
|
||||
char buffer[25] = {};
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
FillSSIDs(manager, 4);
|
||||
for (int i = 0; i < manager.GetCount(); i++) {
|
||||
sprintf(buffer, "SSID_%d", i + 1);
|
||||
auto mockap = getMockAPRec(i + 1);
|
||||
auto mockSTA = getMockSTA(i + 1);
|
||||
auto entry = manager.GetIndex(i);
|
||||
TEST_ASSERT_EQUAL_STRING(buffer, entry->ssid);
|
||||
auto pEntry = manager.Get(std::string(buffer));
|
||||
TEST_ASSERT_EQUAL_STRING(buffer, pEntry->ssid);
|
||||
pEntry = manager.Get(buffer);
|
||||
TEST_ASSERT_EQUAL_STRING(buffer, pEntry->ssid);
|
||||
pEntry = manager.Get(&mockap);
|
||||
TEST_ASSERT_EQUAL_STRING(buffer, pEntry->ssid);
|
||||
pEntry = manager.Get(&mockSTA);
|
||||
TEST_ASSERT_EQUAL_STRING(buffer, pEntry->ssid);
|
||||
}
|
||||
auto entry = manager.GetStrongestSTA();
|
||||
TEST_ASSERT_EQUAL_STRING("SSID_4", entry.ssid);
|
||||
manager.Clear();
|
||||
}
|
||||
|
||||
TEST_CASE("Update last try", "[WifiCredentialsManager]") {
|
||||
char buffer[25] = {};
|
||||
google_protobuf_Timestamp ts;
|
||||
bool flag;
|
||||
WifiCredentialsManager::UpdateTimeStamp(&ts, flag);
|
||||
|
||||
// now advance the time to ensure tests are a success
|
||||
advanceTime(2);
|
||||
|
||||
WifiList manager("test_manager");
|
||||
FillSSIDs(manager, 4);
|
||||
for (int i = 0; i < manager.GetCount(); i++) {
|
||||
sprintf(buffer, "SSID_%d", i + 1);
|
||||
auto mockap = getMockAPRec(i + 1);
|
||||
auto mockSTA = getMockSTA(i + 1);
|
||||
auto entry = manager.GetIndex(i);
|
||||
entry->has_last_try = false;
|
||||
memset(&entry->last_try, 0x00, sizeof(entry->last_try));
|
||||
manager.UpdateLastTry(entry);
|
||||
TEST_ASSERT_TRUE(entry->has_last_try);
|
||||
TEST_ASSERT_TRUE(entry->last_try.seconds >= ts.seconds);
|
||||
entry->has_last_try = false;
|
||||
memset(&entry->last_try, 0x00, sizeof(entry->last_try));
|
||||
manager.UpdateLastTry(std::string(buffer));
|
||||
TEST_ASSERT_TRUE(entry->has_last_try);
|
||||
TEST_ASSERT_TRUE(entry->last_try.seconds >= ts.seconds);
|
||||
entry->has_last_try = false;
|
||||
memset(&entry->last_try, 0x00, sizeof(entry->last_try));
|
||||
manager.UpdateLastTry(&mockap);
|
||||
TEST_ASSERT_TRUE(entry->has_last_try);
|
||||
TEST_ASSERT_TRUE(entry->last_try.seconds >= ts.seconds);
|
||||
entry->has_last_try = false;
|
||||
memset(&entry->last_try, 0x00, sizeof(entry->last_try));
|
||||
manager.UpdateLastTry(&mockSTA);
|
||||
TEST_ASSERT_TRUE(entry->has_last_try);
|
||||
TEST_ASSERT_TRUE(entry->last_try.seconds >= ts.seconds);
|
||||
}
|
||||
auto entry = manager.GetStrongestSTA();
|
||||
TEST_ASSERT_EQUAL_STRING("SSID_4", entry.ssid);
|
||||
manager.Clear();
|
||||
advanceTime(-2);
|
||||
}
|
||||
|
||||
TEST_CASE("Update last seen", "[WifiCredentialsManager]") {
|
||||
char buffer[25] = {};
|
||||
google_protobuf_Timestamp ts;
|
||||
bool flag;
|
||||
WifiCredentialsManager::UpdateTimeStamp(&ts, flag);
|
||||
|
||||
// now advance the time to ensure tests are a success
|
||||
advanceTime(2);
|
||||
|
||||
WifiList manager("test_manager");
|
||||
FillSSIDs(manager, 4);
|
||||
for (int i = 0; i < manager.GetCount(); i++) {
|
||||
sprintf(buffer, "SSID_%d", i + 1);
|
||||
auto mockap = getMockAPRec(i + 1);
|
||||
auto mockSTA = getMockSTA(i + 1);
|
||||
auto entry = manager.GetIndex(i);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(entry);
|
||||
TEST_ASSERT_TRUE(entry->has_last_seen);
|
||||
TEST_ASSERT_TRUE(entry->last_seen.seconds >= ts.seconds);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(std::string(buffer));
|
||||
TEST_ASSERT_TRUE(entry->has_last_seen);
|
||||
TEST_ASSERT_TRUE(entry->last_seen.seconds >= ts.seconds);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(&mockap);
|
||||
TEST_ASSERT_TRUE(entry->has_last_seen);
|
||||
TEST_ASSERT_TRUE(entry->last_seen.seconds >= ts.seconds);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(&mockSTA);
|
||||
TEST_ASSERT_TRUE(entry->has_last_seen);
|
||||
TEST_ASSERT_TRUE(entry->last_seen.seconds >= ts.seconds);
|
||||
}
|
||||
|
||||
manager.Clear();
|
||||
advanceTime(-2);
|
||||
}
|
||||
|
||||
TEST_CASE("Memory leak test", "[WifiCredentialsManager]") {
|
||||
char buffer[25] = {};
|
||||
char err_buffer[55] = {};
|
||||
int fillqty=20;
|
||||
google_protobuf_Timestamp ts;
|
||||
bool flag;
|
||||
|
||||
WifiCredentialsManager::UpdateTimeStamp(&ts, flag);
|
||||
|
||||
// now advance the time to ensure tests are a success
|
||||
advanceTime(2);
|
||||
HasMemoryUsageIncreased(0);
|
||||
|
||||
for (int runs = 0; runs < 100; runs++) {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
FillSSIDs(manager, fillqty);
|
||||
for (int i = 1; i <= manager.GetCount(); i++) {
|
||||
sprintf(buffer, "SSID_%d", i);
|
||||
sprintf(err_buffer,"Round %d, SSID_%d",runs,i);
|
||||
auto mockap = getMockAPRec(i);
|
||||
auto mockSTA = getMockSTA(i);
|
||||
auto entry = manager.Get(buffer);
|
||||
TEST_ASSERT_EQUAL_STRING(buffer,entry->ssid);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(entry);
|
||||
TEST_ASSERT_TRUE(entry->has_last_seen);
|
||||
TEST_ASSERT_TRUE(entry->last_seen.seconds >= ts.seconds);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(std::string(buffer));
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(&mockap);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(&mockSTA);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
}
|
||||
manager.Clear();
|
||||
}
|
||||
TEST_ASSERT_FALSE(HasMemoryUsageIncreased(1));
|
||||
|
||||
for (int runs = 0; runs < 100; runs++) {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
FillSSIDFromAPRec(manager, fillqty);
|
||||
for (int i = 1; i <= manager.GetCount(); i++) {
|
||||
sprintf(buffer, "SSID_%d", i);
|
||||
sprintf(err_buffer,"Round %d, SSID_%d",runs,i);
|
||||
auto mockap = getMockAPRec(i);
|
||||
auto mockSTA = getMockSTA(i);
|
||||
auto entry = manager.GetIndex(i-1);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(entry);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(std::string(buffer));
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(&mockap);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(&mockSTA);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
}
|
||||
manager.Clear();
|
||||
}
|
||||
TEST_ASSERT_FALSE(HasMemoryUsageIncreased(2));
|
||||
|
||||
for (int runs = 0; runs < 100; runs++) {
|
||||
WifiCredentialsManager manager("test_manager");
|
||||
FillSSIDFromMockEntry(manager, fillqty);
|
||||
for (int i = 1; i <= manager.GetCount(); i++) {
|
||||
sprintf(buffer, "SSID_%d", i);
|
||||
sprintf(err_buffer,"Round %d, SSID_%d",runs,i);
|
||||
auto mockap = getMockAPRec(i);
|
||||
auto mockSTA = getMockSTA(i);
|
||||
auto entry = manager.GetIndex(i-1);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(entry);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(std::string(buffer));
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(&mockap);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
entry->has_last_seen = false;
|
||||
memset(&entry->last_seen, 0x00, sizeof(entry->last_seen));
|
||||
manager.UpdateLastSeen(&mockSTA);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->has_last_seen,err_buffer);
|
||||
TEST_ASSERT_TRUE_MESSAGE(entry->last_seen.seconds >= ts.seconds,err_buffer);
|
||||
}
|
||||
manager.Clear();
|
||||
}
|
||||
advanceTime(-2);
|
||||
TEST_ASSERT_FALSE(HasMemoryUsageIncreased(3));
|
||||
}
|
||||
Reference in New Issue
Block a user