Protobuf persistance + Readded persistance for ap service
This commit is contained in:
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
#include <template/stateful_service.h>
|
#include <template/stateful_service.h>
|
||||||
#include <template/stateful_proto_endpoint.h>
|
#include <template/stateful_proto_endpoint.h>
|
||||||
|
#include <template/stateful_persistence_pb.h>
|
||||||
#include <settings/ap_settings.h>
|
#include <settings/ap_settings.h>
|
||||||
#include <utils/timing.h>
|
#include <utils/timing.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
|
|
||||||
|
#define AP_SETTINGS_FILE "/config/apSettings.pb"
|
||||||
|
|
||||||
class APService : public StatefulService<APSettings> {
|
class APService : public StatefulService<APSettings> {
|
||||||
public:
|
public:
|
||||||
APService();
|
APService();
|
||||||
@@ -23,6 +26,7 @@ class APService : public StatefulService<APSettings> {
|
|||||||
StatefulProtoEndpoint<APSettings, api_APSettings> protoEndpoint;
|
StatefulProtoEndpoint<APSettings, api_APSettings> protoEndpoint;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
FSPersistencePB<APSettings> _persistence;
|
||||||
DNSServer *_dnsServer;
|
DNSServer *_dnsServer;
|
||||||
|
|
||||||
volatile unsigned long _lastManaged;
|
volatile unsigned long _lastManaged;
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <FS.h>
|
||||||
|
#include <template/stateful_service.h>
|
||||||
|
#include <template/state_result.h>
|
||||||
|
#include <filesystem.h>
|
||||||
|
#include <pb_encode.h>
|
||||||
|
#include <pb_decode.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protobuf-based filesystem persistence for StatefulService.
|
||||||
|
*
|
||||||
|
* @tparam T The state type (should be a nanopb-generated struct like api_APSettings)
|
||||||
|
*/
|
||||||
|
template <class T>
|
||||||
|
class FSPersistencePB {
|
||||||
|
public:
|
||||||
|
using ProtoStateReader = std::function<void(const T&, T&)>;
|
||||||
|
// Formats are passed as referenced const (new object), and a reference to local variable we either want to read from or write to
|
||||||
|
using ProtoStateUpdater = std::function<StateUpdateResult(const T&, T&)>;
|
||||||
|
|
||||||
|
FSPersistencePB(ProtoStateReader stateReader, ProtoStateUpdater stateUpdater,
|
||||||
|
StatefulService<T> *statefulService, const char *filePath,
|
||||||
|
const pb_msgdesc_t *msgDescriptor, size_t maxSize)
|
||||||
|
: _stateReader(stateReader),
|
||||||
|
_stateUpdater(stateUpdater),
|
||||||
|
_statefulService(statefulService),
|
||||||
|
_filePath(filePath),
|
||||||
|
_msgDescriptor(msgDescriptor),
|
||||||
|
_maxSize(maxSize),
|
||||||
|
_updateHandlerId(0) {
|
||||||
|
enableUpdateHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void readFromFS() {
|
||||||
|
File file = _fs->open(_filePath, "r");
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
size_t fileSize = file.size();
|
||||||
|
if (fileSize > 0 && fileSize <= _maxSize) {
|
||||||
|
uint8_t *buffer = new uint8_t[fileSize];
|
||||||
|
size_t bytesRead = file.read(buffer, fileSize);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (bytesRead == fileSize) {
|
||||||
|
T protoMsg = {};
|
||||||
|
pb_istream_t stream = pb_istream_from_buffer(buffer, bytesRead);
|
||||||
|
|
||||||
|
if (pb_decode(&stream, _msgDescriptor, &protoMsg)) {
|
||||||
|
_statefulService->updateWithoutPropagation(
|
||||||
|
[this, &protoMsg](T &state) { return _stateUpdater(protoMsg, state); });
|
||||||
|
delete[] buffer;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete[] buffer;
|
||||||
|
} else {
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyDefaults();
|
||||||
|
writeToFS();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writeToFS() {
|
||||||
|
uint8_t *buffer = new uint8_t[_maxSize];
|
||||||
|
pb_ostream_t stream = pb_ostream_from_buffer(buffer, _maxSize);
|
||||||
|
|
||||||
|
T protoMsg = {};
|
||||||
|
_statefulService->read([this, &protoMsg](const T &state) { _stateReader(state, protoMsg); });
|
||||||
|
|
||||||
|
bool encodeSuccess = pb_encode(&stream, _msgDescriptor, &protoMsg);
|
||||||
|
|
||||||
|
if (!encodeSuccess) {
|
||||||
|
delete[] buffer;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdirs();
|
||||||
|
|
||||||
|
File file = _fs->open(_filePath, "w");
|
||||||
|
if (!file) {
|
||||||
|
delete[] buffer;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t written = file.write(buffer, stream.bytes_written);
|
||||||
|
file.close();
|
||||||
|
delete[] buffer;
|
||||||
|
|
||||||
|
return written == stream.bytes_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
void disableUpdateHandler() {
|
||||||
|
if (_updateHandlerId) {
|
||||||
|
_statefulService->removeUpdateHandler(_updateHandlerId);
|
||||||
|
_updateHandlerId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void enableUpdateHandler() {
|
||||||
|
if (!_updateHandlerId) {
|
||||||
|
_updateHandlerId = _statefulService->addUpdateHandler(
|
||||||
|
[&](const std::string &originId) { writeToFS(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ProtoStateReader _stateReader;
|
||||||
|
ProtoStateUpdater _stateUpdater;
|
||||||
|
StatefulService<T> *_statefulService;
|
||||||
|
FS *_fs{&ESP_FS};
|
||||||
|
const char *_filePath;
|
||||||
|
const pb_msgdesc_t *_msgDescriptor;
|
||||||
|
size_t _maxSize;
|
||||||
|
HandlerId _updateHandlerId;
|
||||||
|
|
||||||
|
void mkdirs() {
|
||||||
|
std::string path(_filePath);
|
||||||
|
size_t index = 0;
|
||||||
|
while ((index = path.find('/', index + 1)) != std::string::npos) {
|
||||||
|
std::string segment = path.substr(0, index);
|
||||||
|
if (!_fs->exists(segment.c_str())) _fs->mkdir(segment.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void applyDefaults() {
|
||||||
|
T defaultState = {};
|
||||||
|
_statefulService->updateWithoutPropagation(
|
||||||
|
[this, &defaultState](T &state) { return _stateUpdater(defaultState, state); });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -6,13 +6,17 @@ static const char *TAG = "APService";
|
|||||||
APService::APService()
|
APService::APService()
|
||||||
: protoEndpoint(APSettings_read, APSettings_update, this,
|
: protoEndpoint(APSettings_read, APSettings_update, this,
|
||||||
API_REQUEST_EXTRACTOR(ap_settings, api_APSettings),
|
API_REQUEST_EXTRACTOR(ap_settings, api_APSettings),
|
||||||
API_RESPONSE_ASSIGNER(ap_settings, api_APSettings)) {
|
API_RESPONSE_ASSIGNER(ap_settings, api_APSettings)),
|
||||||
|
_persistence(APSettings_read, APSettings_update, this,
|
||||||
|
AP_SETTINGS_FILE, api_APSettings_fields, api_APSettings_size) {
|
||||||
addUpdateHandler([&](const std::string &originId) { reconfigureAP(); }, false);
|
addUpdateHandler([&](const std::string &originId) { reconfigureAP(); }, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
APService::~APService() {}
|
APService::~APService() {}
|
||||||
|
|
||||||
void APService::begin() {}
|
void APService::begin() {
|
||||||
|
_persistence.readFromFS();
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t APService::getStatusProto(httpd_req_t *request) {
|
esp_err_t APService::getStatusProto(httpd_req_t *request) {
|
||||||
api_Response res = api_Response_init_zero;
|
api_Response res = api_Response_init_zero;
|
||||||
|
|||||||
Reference in New Issue
Block a user