♻️ Handle merging

This commit is contained in:
Rune Harlyk
2026-01-31 16:11:20 +01:00
committed by Rune Harlyk
parent aca8ee6de5
commit bd984309f1
19 changed files with 224 additions and 391 deletions
-1
View File
@@ -1,7 +1,6 @@
#pragma once
#include <wifi/wifi_idf.h>
#include <ArduinoJson.h>
#include <esp_http_server.h>
#include "platform_shared/message.pb.h"
+10 -23
View File
@@ -1,7 +1,6 @@
#pragma once
#include <esp_http_server.h>
#include <ArduinoJson.h>
#include <esp_littlefs.h>
#include <esp_vfs.h>
#include <dirent.h>
@@ -12,40 +11,28 @@
#define MOUNT_POINT "/littlefs"
#define FS_CONFIG_DIRECTORY "/config"
#define DEVICE_CONFIG_FILE "/config/peripheral.json"
#define CAMERA_SETTINGS_FILE "/config/cameraSettings.pb"
#define AP_SETTINGS_FILE "/config/apSettings.pb"
#define MDNS_SETTINGS_FILE "/config/mdnsSettings.pb"
#define WIFI_SETTINGS_FILE "/config/wifiSettings.pb"
#define PERIPHERAL_SETTINGS_FILE "/config/peripheralSettings.pb"
#define SERVO_SETTINGS_FILE "/config/servoSettings.pb"
namespace FileSystem {
void listFilesProto(const std::string &directory, api_FileEntry *entry);
std::string listFiles(const std::string &directory, bool isRoot = true);
bool deleteFile(const char *filename);
bool editFile(const char *filename, const uint8_t *content, size_t size);
#define AP_SETTINGS_FILE MOUNT_POINT "/config/apSettings.json"
#define CAMERA_SETTINGS_FILE MOUNT_POINT "/config/cameraSettings.json"
#define FS_CONFIG_DIRECTORY MOUNT_POINT "/config"
#define DEVICE_CONFIG_FILE MOUNT_POINT "/config/peripheral.json"
#define WIFI_SETTINGS_FILE MOUNT_POINT "/config/wifiSettings.json"
#define SERVO_SETTINGS_FILE MOUNT_POINT "/config/servoSettings.json"
#define MDNS_SETTINGS_FILE MOUNT_POINT "/config/mdnsSettings.json"
#define DEVICE_CONFIG_FILE MOUNT_POINT "/config/peripheral.pb"
#define CAMERA_SETTINGS_FILE MOUNT_POINT "/config/cameraSettings.pb"
#define AP_SETTINGS_FILE MOUNT_POINT "/config/apSettings.pb"
#define MDNS_SETTINGS_FILE MOUNT_POINT "/config/mdnsSettings.pb"
#define WIFI_SETTINGS_FILE MOUNT_POINT "/config/wifiSettings.pb"
#define PERIPHERAL_SETTINGS_FILE MOUNT_POINT "/config/peripheralSettings.pb"
#define SERVO_SETTINGS_FILE MOUNT_POINT "/config/servoSettings.pb"
namespace FileSystem {
bool init();
void listFilesProto(const std::string &directory, api_FileEntry *entry);
std::string listFiles(const std::string &directory, bool isRoot = true);
bool deleteFile(const char *filename);
bool editFile(const char *filename, const uint8_t *content, size_t size);
bool editFile(const char *filename, const char *content);
bool fileExists(const char *filename);
std::string readFile(const char *filename);
bool writeFile(const char *filename, const char *content);
bool writeFile(const char *filename, const uint8_t *content, size_t size);
bool mkdirRecursive(const char *path);
esp_err_t getFilesProto(httpd_req_t *request);
+9 -10
View File
@@ -1,7 +1,6 @@
#pragma once
#include <esp_http_server.h>
#include <ArduinoJson.h>
#include <mdns.h>
#include <template/stateful_service.h>
#include <template/stateful_proto_endpoint.h>
@@ -10,15 +9,6 @@
#include <utils/timing.h>
class MDNSService : public StatefulService<MDNSSettings> {
private:
FSPersistencePB<MDNSSettings> _persistence;
bool _started {false};
void reconfigureMDNS();
void startMDNS();
void stopMDNS();
void addServices();
public:
MDNSService();
~MDNSService();
@@ -29,4 +19,13 @@ class MDNSService : public StatefulService<MDNSSettings> {
esp_err_t queryServices(httpd_req_t *request, api_Request *protoReq);
StatefulProtoEndpoint<MDNSSettings, api_MDNSSettings> protoEndpoint;
private:
FSPersistencePB<MDNSSettings> _persistence;
bool _started {false};
void reconfigureMDNS();
void startMDNS();
void stopMDNS();
void addServices();
};
+2 -5
View File
@@ -2,7 +2,6 @@
#include <wifi/wifi_idf.h>
#include <wifi/dns_server.h>
#include <ArduinoJson.h>
#include <template/state_result.h>
#include <platform_shared/api.pb.h>
#include <cstring>
@@ -77,11 +76,9 @@ inline APSettings APSettings_defaults() {
return settings;
}
inline void APSettings_read(const APSettings &settings, APSettings &proto) {
proto = settings;
}
inline void APSettings_read(const APSettings &settings, APSettings &proto) { proto = settings; }
inline StateUpdateResult APSettings_update(const APSettings &proto, APSettings &settings) {
settings = proto;
return StateUpdateResult::CHANGED;
}
}
@@ -7,10 +7,10 @@
* I2C software connection
*/
#ifndef SDA_PIN
#define SDA_PIN SDA
#define SDA_PIN 21
#endif
#ifndef SCL_PIN
#define SCL_PIN SCL
#define SCL_PIN 22
#endif
#ifndef I2C_FREQUENCY
#define I2C_FREQUENCY 1000000UL
@@ -35,7 +35,8 @@ inline void PeripheralsConfiguration_read(const PeripheralsConfiguration& settin
proto = settings;
}
inline StateUpdateResult PeripheralsConfiguration_update(const PeripheralsConfiguration& proto, PeripheralsConfiguration& settings) {
inline StateUpdateResult PeripheralsConfiguration_update(const PeripheralsConfiguration& proto,
PeripheralsConfiguration& settings) {
settings = proto;
return StateUpdateResult::CHANGED;
}
+1 -4
View File
@@ -1,7 +1,6 @@
#pragma once
#include <wifi/wifi_idf.h>
#include <ArduinoJson.h>
#include <template/state_result.h>
#include <platform_shared/api.pb.h>
#include <cstring>
@@ -50,9 +49,7 @@ inline WiFiSettings WiFiSettings_defaults() {
return settings;
}
inline void WiFiSettings_read(const WiFiSettings &settings, WiFiSettings &proto) {
proto = settings;
}
inline void WiFiSettings_read(const WiFiSettings &settings, WiFiSettings &proto) { proto = settings; }
inline StateUpdateResult WiFiSettings_update(const WiFiSettings &proto, WiFiSettings &settings) {
settings = proto;
-1
View File
@@ -20,7 +20,6 @@ esp_err_t handleSleep(httpd_req_t *request);
void reset();
void restart();
void sleep();
void status(JsonObject &root);
void getAnalytics(socket_message_AnalyticsData &analytics);
void getStaticSystemInformation(socket_message_StaticSystemInformation &info);
@@ -1,29 +1,24 @@
#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>
#include <cstdio>
#include <sys/stat.h>
#include <esp_log.h>
static const char *TAG_PERSISTENCE = "FSPersistencePB";
/**
* 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:
// Formats are passed as referenced const (local variable) we want to read from, and a reference (proto) we write to
using ProtoStateReader = std::function<void(const T&, T&)>;
// Formats are passed as referenced const (new object) we read from, and a reference to the local variable we write to
using ProtoStateUpdater = std::function<StateUpdateResult(const T&, T&)>;
using ProtoStateReader = std::function<void(const T &, T &)>;
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,
const T &defaultState)
FSPersistencePB(ProtoStateReader stateReader, ProtoStateUpdater stateUpdater, StatefulService<T> *statefulService,
const char *filePath, const pb_msgdesc_t *msgDescriptor, size_t maxSize, const T &defaultState)
: _stateReader(stateReader),
_stateUpdater(stateUpdater),
_statefulService(statefulService),
@@ -36,17 +31,19 @@ class FSPersistencePB {
}
void readFromFS() {
File file = _fs->open(_filePath, "r");
FILE *file = fopen(_filePath, "rb");
if (file) {
size_t fileSize = file.size();
fseek(file, 0, SEEK_END);
size_t fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
if (fileSize > 0 && fileSize <= _maxSize) {
uint8_t *buffer = new uint8_t[fileSize];
size_t bytesRead = file.read(buffer, fileSize);
file.close();
size_t bytesRead = fread(buffer, 1, fileSize, file);
fclose(file);
if (bytesRead == fileSize) {
// Allocate on heap to avoid stack overflow with large proto messages
T *protoMsg = new T();
*protoMsg = {};
pb_istream_t stream = pb_istream_from_buffer(buffer, bytesRead);
@@ -62,7 +59,7 @@ class FSPersistencePB {
}
delete[] buffer;
} else {
file.close();
fclose(file);
}
}
@@ -74,7 +71,6 @@ class FSPersistencePB {
uint8_t *buffer = new uint8_t[_maxSize];
pb_ostream_t stream = pb_ostream_from_buffer(buffer, _maxSize);
// Allocate on heap to avoid stack overflow with large proto messages
T *protoMsg = new T();
*protoMsg = {};
_statefulService->read([this, protoMsg](const T &state) { _stateReader(state, *protoMsg); });
@@ -89,14 +85,15 @@ class FSPersistencePB {
mkdirs();
File file = _fs->open(_filePath, "w");
FILE *file = fopen(_filePath, "wb");
if (!file) {
ESP_LOGE(TAG_PERSISTENCE, "Failed to open file for writing: %s", _filePath);
delete[] buffer;
return false;
}
size_t written = file.write(buffer, stream.bytes_written);
file.close();
size_t written = fwrite(buffer, 1, stream.bytes_written, file);
fclose(file);
delete[] buffer;
return written == stream.bytes_written;
@@ -111,8 +108,7 @@ class FSPersistencePB {
void enableUpdateHandler() {
if (!_updateHandlerId) {
_updateHandlerId = _statefulService->addUpdateHandler(
[&](const std::string &originId) { writeToFS(); });
_updateHandlerId = _statefulService->addUpdateHandler([&](const std::string &originId) { writeToFS(); });
}
}
@@ -120,7 +116,6 @@ class FSPersistencePB {
ProtoStateReader _stateReader;
ProtoStateUpdater _stateUpdater;
StatefulService<T> *_statefulService;
FS *_fs{&ESP_FS};
const char *_filePath;
const pb_msgdesc_t *_msgDescriptor;
size_t _maxSize;
@@ -132,13 +127,15 @@ class FSPersistencePB {
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());
struct stat st;
if (stat(segment.c_str(), &st) != 0) {
FileSystem::mkdirRecursive(segment.c_str());
}
}
}
protected:
void applyDefaults() {
_statefulService->updateWithoutPropagation(
[this](T &state) { return _stateUpdater(_defaultState, state); });
_statefulService->updateWithoutPropagation([this](T &state) { return _stateUpdater(_defaultState, state); });
}
};
+5 -28
View File
@@ -1,7 +1,5 @@
#pragma once
#include <ArduinoJson.h>
#include <list>
#include <functional>
#include <freertos/FreeRTOS.h>
@@ -10,19 +8,13 @@
#include <template/state_result.h>
template <typename T>
using JsonStateUpdater = std::function<StateUpdateResult(JsonVariant &root, T &settings)>;
template <typename T>
using JsonStateReader = std::function<void(T &settings, JsonVariant &root)>;
using HandlerId = size_t;
using StateUpdateCallback = std::function<void(const std::string &originId)>;
using StateHookCallback = std::function<void(const std::string &originId, StateUpdateResult &result)>;
class HandlerBase {
protected:
static inline HandlerId nextId_ = 1; // Start from 1, 0 is invalid
static inline HandlerId nextId_ = 1;
HandlerId id_;
bool allowRemove_;
@@ -98,31 +90,16 @@ class StatefulService {
return result;
}
StateUpdateResult update(JsonVariant &jsonObject, JsonStateUpdater<T> stateUpdater, const std::string &originId) {
lock();
StateUpdateResult result = stateUpdater(jsonObject, state_);
unlock();
notifyStateChange(originId, result);
return result;
}
StateUpdateResult updateWithoutPropagation(JsonVariant &jsonObject, JsonStateUpdater<T> stateUpdater) {
lock();
StateUpdateResult result = stateUpdater(jsonObject, state_);
unlock();
return result;
}
void read(std::function<void(T &)> stateReader) {
lock();
stateReader(state_);
unlock();
}
void read(JsonVariant &jsonObject, JsonStateReader<T> stateReader) {
lock();
stateReader(state_, jsonObject);
unlock();
void read(std::function<void(const T &)> stateReader) const {
const_cast<StatefulService *>(this)->lock();
stateReader(state_);
const_cast<StatefulService *>(this)->unlock();
}
void callUpdateHandlers(const std::string &originId) {
+17 -19
View File
@@ -17,25 +17,6 @@
#define IP_EVENT_STA_GOT_IP_IDF 1000
class WiFiService : public StatefulService<WiFiSettings> {
private:
static void getNetworks(JsonObject &root);
static void getNetworkStatus(JsonObject &root);
void onStationModeDisconnected(int32_t event, void *event_data);
void onStationModeStop(int32_t event, void *event_data);
static void onStationModeGotIP(int32_t event, void *event_data);
FSPersistencePB<WiFiSettings> _persistence;
void reconfigureWiFiConnection();
void manageSTA();
void connectToWiFi();
void configureNetwork(WiFiNetwork &network);
unsigned long _lastConnectionAttempt;
bool _stopping;
constexpr static uint16_t reconnectDelay {10000};
public:
WiFiService();
~WiFiService();
@@ -52,4 +33,21 @@ class WiFiService : public StatefulService<WiFiSettings> {
static esp_err_t getNetworkStatus(httpd_req_t *request);
StatefulProtoEndpoint<WiFiSettings, api_WifiSettings> protoEndpoint;
private:
void onStationModeDisconnected(int32_t event, void *event_data);
void onStationModeStop(int32_t event, void *event_data);
static void onStationModeGotIP(int32_t event, void *event_data);
FSPersistencePB<WiFiSettings> _persistence;
void reconfigureWiFiConnection();
void manageSTA();
void connectToWiFi();
void configureNetwork(WiFiNetwork &network);
unsigned long _lastConnectionAttempt;
bool _stopping;
constexpr static uint16_t reconnectDelay {10000};
};