📦 Moves templates

This commit is contained in:
Rune Harlyk
2024-11-14 15:56:23 +01:00
parent b6fe8844f4
commit 24d39e540e
18 changed files with 29 additions and 29 deletions
@@ -0,0 +1,7 @@
#pragma once
enum class StateUpdateResult {
CHANGED = 0, // The update changed the state and propagation should take place if required
UNCHANGED, // The state was unchanged, propagation should not take place
ERROR // There was a problem updating the state, propagation should not take place
};
@@ -0,0 +1,52 @@
#pragma once
#include <PsychicHttp.h>
#include <template/stateful_endpoint.h>
#include <functional>
#define HTTP_ENDPOINT_ORIGIN_ID "http"
#define HTTPS_ENDPOINT_ORIGIN_ID "https"
template <class T>
class StatefulHttpEndpoint {
protected:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> *_statefulService;
public:
StatefulHttpEndpoint(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater,
StatefulService<T> *statefulService)
: _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService) {}
esp_err_t handleStateUpdate(PsychicRequest *request, JsonVariant &json) {
if (!json.is<JsonObject>()) {
return request->reply(400);
}
JsonObject jsonObject = json.as<JsonObject>();
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
if (outcome == StateUpdateResult::ERROR) {
return request->reply(400);
} else if ((outcome == StateUpdateResult::CHANGED)) {
// persist the changes to the FS
_statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID);
}
PsychicJsonResponse response = PsychicJsonResponse(request, false);
jsonObject = response.getRoot();
_statefulService->read(jsonObject, _stateReader);
return response.send();
}
esp_err_t getState(PsychicRequest *request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false);
JsonObject jsonObject = response.getRoot();
_statefulService->read(jsonObject, _stateReader);
return response.send();
}
};
@@ -0,0 +1,129 @@
#ifndef FSPersistence_h
#define FSPersistence_h
/**
* ESP32 SvelteKit
*
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
* https://github.com/theelims/ESP32-sveltekit
*
* Copyright (C) 2018 - 2023 rjwats
* Copyright (C) 2023 theelims
* Copyright (C) 2024 runeharlyk
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
**/
#include <FS.h>
#include <template/stateful_service.h>
#include <filesystem.h>
template <class T>
class FSPersistence {
public:
FSPersistence(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> *statefulService,
const char *filePath)
: _stateReader(stateReader),
_stateUpdater(stateUpdater),
_statefulService(statefulService),
_filePath(filePath),
_updateHandlerId(0) {
enableUpdateHandler();
}
void readFromFS() {
File settingsFile = _fs->open(_filePath, "r");
if (settingsFile) {
JsonDocument jsonDocument;
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
JsonObject jsonObject = jsonDocument.as<JsonObject>();
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
settingsFile.close();
return;
}
settingsFile.close();
}
// If we reach here we have not been successful in loading the config
// and hard-coded defaults are now applied. The settings are then
// written back to the file system so the defaults persist between
// resets. This last step is required as in some cases defaults contain
// randomly generated values which would otherwise be modified on reset.
applyDefaults();
writeToFS();
}
bool writeToFS() {
// create and populate a new json object
JsonDocument jsonDocument;
JsonObject jsonObject = jsonDocument.to<JsonObject>();
_statefulService->read(jsonObject, _stateReader);
// make directories if required
mkdirs();
// serialize it to filesystem
File settingsFile = _fs->open(_filePath, "w");
// failed to open file, return false
if (!settingsFile) {
return false;
}
// serialize the data to the file
serializeJson(jsonDocument, settingsFile);
settingsFile.close();
return true;
}
void disableUpdateHandler() {
if (_updateHandlerId) {
_statefulService->removeUpdateHandler(_updateHandlerId);
_updateHandlerId = 0;
}
}
void enableUpdateHandler() {
if (!_updateHandlerId) {
_updateHandlerId = _statefulService->addUpdateHandler([&](const String &originId) { writeToFS(); });
}
}
private:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> *_statefulService;
FS *_fs {&ESPFS};
const char *_filePath;
size_t _bufferSize;
update_handler_id_t _updateHandlerId;
// We assume we have a _filePath with format
// "/directory1/directory2/filename" We create a directory for each missing
// parent
void mkdirs() {
String path(_filePath);
int index = 0;
while ((index = path.indexOf('/', index + 1)) != -1) {
String segment = path.substring(0, index);
if (!_fs->exists(segment)) {
_fs->mkdir(segment);
}
}
}
protected:
// We assume the updater supplies sensible defaults if an empty object
// is supplied, this virtual function allows that to be changed.
virtual void applyDefaults() {
JsonDocument jsonDocument;
JsonObject jsonObject = jsonDocument.as<JsonObject>();
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
}
};
#endif // end FSPersistence
@@ -0,0 +1,176 @@
#ifndef StatefulService_h
#define StatefulService_h
/**
* ESP32 SvelteKit
*
* A simple, secure and extensible framework for IoT projects for ESP32 platforms
* with responsive Sveltekit front-end built with TailwindCSS and DaisyUI.
* https://github.com/theelims/ESP32-sveltekit
*
* Copyright (C) 2018 - 2023 rjwats
* Copyright (C) 2023 theelims
* Copyright (C) 2024 runeharlyk
*
* All Rights Reserved. This software may be modified and distributed under
* the terms of the LGPL v3 license. See the LICENSE file for details.
**/
#include <Arduino.h>
#include <ArduinoJson.h>
#include <list>
#include <functional>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <template/state_result.h>
template <typename T>
using JsonStateUpdater = std::function<StateUpdateResult(JsonObject &root, T &settings)>;
template <typename T>
using JsonStateReader = std::function<void(T &settings, JsonObject &root)>;
typedef size_t update_handler_id_t;
typedef size_t hook_handler_id_t;
typedef std::function<void(const String &originId)> StateUpdateCallback;
typedef std::function<void(const String &originId, StateUpdateResult &result)> StateHookCallback;
typedef struct StateUpdateHandlerInfo {
static inline update_handler_id_t currentUpdatedHandlerId = 0;
update_handler_id_t _id;
StateUpdateCallback _cb;
bool _allowRemove;
StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove)
: _id(++currentUpdatedHandlerId), _cb(cb), _allowRemove(allowRemove) {};
} StateUpdateHandlerInfo_t;
typedef struct StateHookHandlerInfo {
static inline hook_handler_id_t currentHookHandlerId = 0;
hook_handler_id_t _id;
StateHookCallback _cb;
bool _allowRemove;
StateHookHandlerInfo(StateHookCallback cb, bool allowRemove)
: _id(++currentHookHandlerId), _cb(cb), _allowRemove(allowRemove) {};
} StateHookHandlerInfo_t;
template <class T>
class StatefulService {
public:
template <typename... Args>
StatefulService(Args &&...args)
: _state(std::forward<Args>(args)...), _accessMutex(xSemaphoreCreateRecursiveMutex()) {}
update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) {
if (!cb) {
return 0;
}
StateUpdateHandlerInfo_t updateHandler(cb, allowRemove);
_updateHandlers.push_back(updateHandler);
return updateHandler._id;
}
void removeUpdateHandler(update_handler_id_t id) {
for (auto i = _updateHandlers.begin(); i != _updateHandlers.end();) {
if ((*i)._allowRemove && (*i)._id == id) {
i = _updateHandlers.erase(i);
} else {
++i;
}
}
}
hook_handler_id_t addHookHandler(StateHookCallback cb, bool allowRemove = true) {
if (!cb) {
return 0;
}
StateHookHandlerInfo_t hookHandler(cb, allowRemove);
_hookHandlers.push_back(hookHandler);
return hookHandler._id;
}
void removeHookHandler(hook_handler_id_t id) {
for (auto i = _hookHandlers.begin(); i != _hookHandlers.end();) {
if ((*i)._allowRemove && (*i)._id == id) {
i = _hookHandlers.erase(i);
} else {
++i;
}
}
}
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater, const String &originId) {
beginTransaction();
StateUpdateResult result = stateUpdater(_state);
endTransaction();
callHookHandlers(originId, result);
if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers(originId);
}
return result;
}
StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T &)> stateUpdater) {
beginTransaction();
StateUpdateResult result = stateUpdater(_state);
endTransaction();
return result;
}
StateUpdateResult update(JsonObject &jsonObject, JsonStateUpdater<T> stateUpdater, const String &originId) {
beginTransaction();
StateUpdateResult result = stateUpdater(jsonObject, _state);
endTransaction();
callHookHandlers(originId, result);
if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers(originId);
}
return result;
}
StateUpdateResult updateWithoutPropagation(JsonObject &jsonObject, JsonStateUpdater<T> stateUpdater) {
beginTransaction();
StateUpdateResult result = stateUpdater(jsonObject, _state);
endTransaction();
return result;
}
void read(std::function<void(T &)> stateReader) {
beginTransaction();
stateReader(_state);
endTransaction();
}
void read(JsonObject &jsonObject, JsonStateReader<T> stateReader) {
beginTransaction();
stateReader(_state, jsonObject);
endTransaction();
}
void callUpdateHandlers(const String &originId) {
for (const StateUpdateHandlerInfo_t &updateHandler : _updateHandlers) {
updateHandler._cb(originId);
}
}
void callHookHandlers(const String &originId, StateUpdateResult &result) {
for (const StateHookHandlerInfo_t &hookHandler : _hookHandlers) {
hookHandler._cb(originId, result);
}
}
protected:
T _state;
inline void beginTransaction() { xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); }
inline void endTransaction() { xSemaphoreGiveRecursive(_accessMutex); }
private:
SemaphoreHandle_t _accessMutex;
std::list<StateUpdateHandlerInfo_t> _updateHandlers;
std::list<StateHookHandlerInfo_t> _hookHandlers;
};
#endif // end StatefulService_h
@@ -0,0 +1,43 @@
#pragma once
#include <PsychicHttp.h>
#include <event_socket.h>
#include <template/stateful_service.h>
template <class T>
class EventEndpoint {
public:
EventEndpoint(JsonStateReader<T> stateReader, JsonStateUpdater<T> stateUpdater, StatefulService<T> *statefulService,
const char *event)
: _stateReader(stateReader), _stateUpdater(stateUpdater), _statefulService(statefulService), _event(event) {
_statefulService->addUpdateHandler([&](const String &originId) { syncState(originId); }, false);
}
void begin() {
socket.onEvent(_event,
std::bind(&EventEndpoint::updateState, this, std::placeholders::_1, std::placeholders::_2));
socket.onSubscribe(_event,
std::bind(&EventEndpoint::syncState, this, std::placeholders::_1, std::placeholders::_2));
}
private:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> *_statefulService;
const char *_event;
void updateState(JsonObject &root, int originId) {
_statefulService->update(root, _stateUpdater, String(originId));
}
void syncState(const String &originId, bool sync = false) {
JsonDocument jsonDocument;
JsonObject root = jsonDocument.to<JsonObject>();
String output;
_statefulService->read(root, _stateReader);
serializeJson(root, output);
ESP_LOGV("EventEndpoint", "Syncing state: %s", output.c_str());
socket.emit(_event, output.c_str(), originId.c_str(), sync);
}
};