mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-06 19:47:02 +03:00
273 lines
10 KiB
C++
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;
|
|
}
|