Files
squeezelite-esp32/components/platform_config/PBW.cpp
2025-03-18 17:38:34 -04:00

273 lines
10 KiB
C++

#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;
}