Apply updates alternative (#135)

* Rename "serialize" and "deserialize" functions to "read" and "update" to reflect API in StatefulService
* Move new definitions to StatefulService.h so it is obvious it is not general purpose
* Update README
This commit is contained in:
rjwats 2020-05-29 20:18:43 +01:00 committed by GitHub
parent d9ae0f5cf9
commit 0d39c5ca00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 253 additions and 258 deletions

View File

@ -350,7 +350,9 @@ The following diagram visualises how the framework's modular components fit toge
#### Stateful service #### Stateful service
The [StatefulService.h](lib/framework/StatefulService.h) class is a responsible for managing state and interfacing with code which wants to change or respond to changes in that state. You can define a data class to hold some state, then build a StatefulService class to manage its state: The [StatefulService.h](lib/framework/StatefulService.h) class is responsible for managing state. It has an API which allows other code to update or respond to updates in the state it manages. You can define a data class to hold state, then build a StatefulService class to manage it. After that you may attach HTTP endpoints, WebSockets or MQTT topics to the StatefulService instance to provide commonly required features.
Here is a simple example of a state class and a StatefulService to manage it:
```cpp ```cpp
class LightState { class LightState {
@ -369,7 +371,8 @@ You may listen for changes to state by registering an update handler callback. I
// register an update handler // register an update handler
update_handler_id_t myUpdateHandler = lightStateService.addUpdateHandler( update_handler_id_t myUpdateHandler = lightStateService.addUpdateHandler(
[&](const String& originId) { [&](const String& originId) {
Serial.println("The light's state has been updated"); Serial.print("The light's state has been updated by: ");
Serial.println(originId);
} }
); );
@ -377,7 +380,7 @@ update_handler_id_t myUpdateHandler = lightStateService.addUpdateHandler(
lightStateService.removeUpdateHandler(myUpdateHandler); lightStateService.removeUpdateHandler(myUpdateHandler);
``` ```
An "originId" is passed to the update handler which may be used to identify the origin of the update. The default origin values the framework provides are: An "originId" is passed to the update handler which may be used to identify the origin of an update. The default origin values the framework provides are:
Origin | Description Origin | Description
--------------------- | ----------- --------------------- | -----------
@ -393,17 +396,35 @@ lightStateService.read([&](LightState& state) {
}); });
``` ```
StatefulService also exposes an update function which allows the caller to update the state with a callback. This approach automatically calls the registered update handlers when complete. The example below turns on the lights using the arbitrary origin "timer": StatefulService also exposes an update function which allows the caller to update the state with a callback. This function automatically calls the registered update handlers if the state has been changed. The example below changes the state of the light (turns it on) using the arbitrary origin "timer" and returns the "CHANGED" state update result, indicating that a change was made:
```cpp ```cpp
lightStateService.update([&](LightState& state) { lightStateService.update([&](LightState& state) {
state.on = true; // turn on the lights! if (state.on) {
return StateUpdateResult::UNCHANGED; // lights were already on, return UNCHANGED
}
state.on = true; // turn on the lights
return StateUpdateResult::CHANGED; // notify StatefulService by returning CHANGED
}, "timer"); }, "timer");
``` ```
There are three possible return values for an update function which are as follows:
Origin | Description
----------------------------- | ---------------------------------------------------------------------------
StateUpdateResult::CHANGED | The update changed the state, propagation should take place if required
StateUpdateResult::UNCHANGED | The state was unchanged, propagation should not take place
StateUpdateResult::ERROR | There was an error updating the state, propagation should not take place
#### Serialization #### Serialization
When transmitting state over HTTP, WebSockets, or MQTT it must to be marshalled into a serializable form (JSON). The framework uses ArduinoJson for serialization and the functions defined in [JsonSerializer.h](lib/framework/JsonSerializer.h) and [JsonDeserializer.h](lib/framework/JsonDeserializer.h) facilitate this. When reading or updating state from an external source (HTTP, WebSockets, or MQTT for example) the state must be marshalled into a serializable form (JSON). SettingsService provides two callback patterns which facilitate this internally:
Callback | Signature | Purpose
---------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------
JsonStateReader | void read(T& settings, JsonObject& root) | Reading the state object into a JsonObject
JsonStateUpdater | StateUpdateResult update(JsonObject& root, T& settings) | Updating the state from a JsonObject, returning the appropriate StateUpdateResult
The static functions below can be used to facilitate the serialization/deserialization of the light state: The static functions below can be used to facilitate the serialization/deserialization of the light state:
@ -413,32 +434,33 @@ class LightState {
bool on = false; bool on = false;
uint8_t brightness = 255; uint8_t brightness = 255;
static void serialize(LightState& state, JsonObject& root) { static void read(LightState& state, JsonObject& root) {
root["on"] = state.on; root["on"] = state.on;
root["brightness"] = state.brightness; root["brightness"] = state.brightness;
} }
static void deserialize(JsonObject& root, LightState& state) { static StateUpdateResult update(JsonObject& root, LightState& state) {
state.on = root["on"] | false; state.on = root["on"] | false;
state.brightness = root["brightness"] | 255; state.brightness = root["brightness"] | 255;
return StateUpdateResult::CHANGED;
} }
}; };
``` ```
For convenience, the StatefulService class provides overloads of its `update` and `read` functions which utilize these functions. For convenience, the StatefulService class provides overloads of its `update` and `read` functions which utilize these functions.
Copy the state to a JsonObject using a serializer: Read the state to a JsonObject using a serializer:
```cpp ```cpp
JsonObject jsonObject = jsonDocument.to<JsonObject>(); JsonObject jsonObject = jsonDocument.to<JsonObject>();
lightStateService->read(jsonObject, serializer); lightStateService->read(jsonObject, LightState::read);
``` ```
Update the state from a JsonObject using a deserializer: Update the state from a JsonObject using a deserializer:
```cpp ```cpp
JsonObject jsonObject = jsonDocument.as<JsonObject>(); JsonObject jsonObject = jsonDocument.as<JsonObject>();
lightStateService->update(jsonObject, deserializer, "timer"); lightStateService->update(jsonObject, LightState::update, "timer");
``` ```
#### Endpoints #### Endpoints
@ -451,7 +473,7 @@ The code below demonstrates how to extend the LightStateService class to provide
class LightStateService : public StatefulService<LightState> { class LightStateService : public StatefulService<LightState> {
public: public:
LightStateService(AsyncWebServer* server) : LightStateService(AsyncWebServer* server) :
_httpEndpoint(LightState::serialize, LightState::deserialize, this, server, "/rest/lightState") { _httpEndpoint(LightState::read, LightState::update, this, server, "/rest/lightState") {
} }
private: private:
@ -471,7 +493,7 @@ The code below demonstrates how to extend the LightStateService class to provide
class LightStateService : public StatefulService<LightState> { class LightStateService : public StatefulService<LightState> {
public: public:
LightStateService(FS* fs) : LightStateService(FS* fs) :
_fsPersistence(LightState::serialize, LightState::deserialize, this, fs, "/config/lightState.json") { _fsPersistence(LightState::read, LightState::update, this, fs, "/config/lightState.json") {
} }
private: private:
@ -489,7 +511,7 @@ The code below demonstrates how to extend the LightStateService class to provide
class LightStateService : public StatefulService<LightState> { class LightStateService : public StatefulService<LightState> {
public: public:
LightStateService(AsyncWebServer* server) : LightStateService(AsyncWebServer* server) :
_webSocket(LightState::serialize, LightState::deserialize, this, server, "/ws/lightState"), { _webSocket(LightState::read, LightState::update, this, server, "/ws/lightState"), {
} }
private: private:
@ -508,15 +530,16 @@ The framework includes an MQTT client which can be configured via the UI. MQTT r
The code below demonstrates how to extend the LightStateService class to interface with MQTT: The code below demonstrates how to extend the LightStateService class to interface with MQTT:
```cpp ```cpp
class LightStateService : public StatefulService<LightState> { class LightStateService : public StatefulService<LightState> {
public: public:
LightStateService(AsyncMqttClient* mqttClient) : LightStateService(AsyncMqttClient* mqttClient) :
_mqttPubSub(LightState::serialize, _mqttPubSub(LightState::read,
LightState::deserialize, LightState::update,
this, this,
mqttClient, mqttClient,
"homeassistant/light/my_light/set", "homeassistant/light/my_light/set",
"homeassistant/light/my_light/state") { "homeassistant/light/my_light/state") {
} }
private: private:

View File

@ -1,13 +1,8 @@
#include <APSettingsService.h> #include <APSettingsService.h>
APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(APSettings::serialize, _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager),
APSettings::deserialize, _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE),
this,
server,
AP_SETTINGS_SERVICE_PATH,
securityManager),
_fsPersistence(APSettings::serialize, APSettings::deserialize, this, fs, AP_SETTINGS_FILE),
_dnsServer(nullptr), _dnsServer(nullptr),
_lastManaged(0) { _lastManaged(0) {
addUpdateHandler([&](const String& originId) { reconfigureAP(); }, false); addUpdateHandler([&](const String& originId) { reconfigureAP(); }, false);

View File

@ -36,13 +36,13 @@ class APSettings {
String ssid; String ssid;
String password; String password;
static void serialize(APSettings& settings, JsonObject& root) { static void read(APSettings& settings, JsonObject& root) {
root["provision_mode"] = settings.provisionMode; root["provision_mode"] = settings.provisionMode;
root["ssid"] = settings.ssid; root["ssid"] = settings.ssid;
root["password"] = settings.password; root["password"] = settings.password;
} }
static void deserialize(JsonObject& root, APSettings& settings) { static StateUpdateResult update(JsonObject& root, APSettings& settings) {
settings.provisionMode = root["provision_mode"] | FACTORY_AP_PROVISION_MODE; settings.provisionMode = root["provision_mode"] | FACTORY_AP_PROVISION_MODE;
switch (settings.provisionMode) { switch (settings.provisionMode) {
case AP_MODE_ALWAYS: case AP_MODE_ALWAYS:
@ -54,6 +54,7 @@ class APSettings {
} }
settings.ssid = root["ssid"] | FACTORY_AP_SSID; settings.ssid = root["ssid"] | FACTORY_AP_SSID;
settings.password = root["password"] | FACTORY_AP_PASSWORD; settings.password = root["password"] | FACTORY_AP_PASSWORD;
return StateUpdateResult::CHANGED;
} }
}; };

View File

@ -2,21 +2,19 @@
#define FSPersistence_h #define FSPersistence_h
#include <StatefulService.h> #include <StatefulService.h>
#include <JsonSerializer.h>
#include <JsonDeserializer.h>
#include <FS.h> #include <FS.h>
template <class T> template <class T>
class FSPersistence { class FSPersistence {
public: public:
FSPersistence(JsonSerializer<T> jsonSerializer, FSPersistence(JsonStateReader<T> stateReader,
JsonDeserializer<T> jsonDeserializer, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
FS* fs, FS* fs,
char const* filePath, char const* filePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_jsonSerializer(jsonSerializer), _stateReader(stateReader),
_jsonDeserializer(jsonDeserializer), _stateUpdater(stateUpdater),
_statefulService(statefulService), _statefulService(statefulService),
_fs(fs), _fs(fs),
_filePath(filePath), _filePath(filePath),
@ -33,7 +31,7 @@ class FSPersistence {
DeserializationError error = deserializeJson(jsonDocument, settingsFile); DeserializationError error = deserializeJson(jsonDocument, settingsFile);
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) { if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
JsonObject jsonObject = jsonDocument.as<JsonObject>(); JsonObject jsonObject = jsonDocument.as<JsonObject>();
_statefulService->updateWithoutPropagation(jsonObject, _jsonDeserializer); _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
settingsFile.close(); settingsFile.close();
return; return;
} }
@ -49,7 +47,7 @@ class FSPersistence {
// create and populate a new json object // create and populate a new json object
DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
JsonObject jsonObject = jsonDocument.to<JsonObject>(); JsonObject jsonObject = jsonDocument.to<JsonObject>();
_statefulService->read(jsonObject, _jsonSerializer); _statefulService->read(jsonObject, _stateReader);
// serialize it to filesystem // serialize it to filesystem
File settingsFile = _fs->open(_filePath, "w"); File settingsFile = _fs->open(_filePath, "w");
@ -79,8 +77,8 @@ class FSPersistence {
} }
private: private:
JsonSerializer<T> _jsonSerializer; JsonStateReader<T> _stateReader;
JsonDeserializer<T> _jsonDeserializer; JsonStateUpdater<T> _stateUpdater;
StatefulService<T>* _statefulService; StatefulService<T>* _statefulService;
FS* _fs; FS* _fs;
char const* _filePath; char const* _filePath;
@ -88,12 +86,12 @@ class FSPersistence {
update_handler_id_t _updateHandlerId; update_handler_id_t _updateHandlerId;
protected: protected:
// We assume the deserializer supplies sensible defaults if an empty object // We assume the updater supplies sensible defaults if an empty object
// is supplied, this virtual function allows that to be changed. // is supplied, this virtual function allows that to be changed.
virtual void applyDefaults() { virtual void applyDefaults() {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize); DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
JsonObject jsonObject = jsonDocument.as<JsonObject>(); JsonObject jsonObject = jsonDocument.as<JsonObject>();
_statefulService->updateWithoutPropagation(jsonObject, _jsonDeserializer); _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
} }
}; };

View File

@ -8,46 +8,44 @@
#include <SecurityManager.h> #include <SecurityManager.h>
#include <StatefulService.h> #include <StatefulService.h>
#include <JsonSerializer.h>
#include <JsonDeserializer.h>
#define HTTP_ENDPOINT_ORIGIN_ID "http" #define HTTP_ENDPOINT_ORIGIN_ID "http"
template <class T> template <class T>
class HttpGetEndpoint { class HttpGetEndpoint {
public: public:
HttpGetEndpoint(JsonSerializer<T> jsonSerializer, HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
const String& servicePath, const String& servicePath,
SecurityManager* securityManager, SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_jsonSerializer(jsonSerializer), _statefulService(statefulService), _bufferSize(bufferSize) { _stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) {
server->on(servicePath.c_str(), server->on(servicePath.c_str(),
HTTP_GET, HTTP_GET,
securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1), securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1),
authenticationPredicate)); authenticationPredicate));
} }
HttpGetEndpoint(JsonSerializer<T> jsonSerializer, HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
const String& servicePath, const String& servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_jsonSerializer(jsonSerializer), _statefulService(statefulService), _bufferSize(bufferSize) { _stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) {
server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1)); server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1));
} }
protected: protected:
JsonSerializer<T> _jsonSerializer; JsonStateReader<T> _stateReader;
StatefulService<T>* _statefulService; StatefulService<T>* _statefulService;
size_t _bufferSize; size_t _bufferSize;
void fetchSettings(AsyncWebServerRequest* request) { void fetchSettings(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize); AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize);
JsonObject jsonObject = response->getRoot().to<JsonObject>(); JsonObject jsonObject = response->getRoot().to<JsonObject>();
_statefulService->read(jsonObject, _jsonSerializer); _statefulService->read(jsonObject, _stateReader);
response->setLength(); response->setLength();
request->send(response); request->send(response);
@ -57,16 +55,16 @@ class HttpGetEndpoint {
template <class T> template <class T>
class HttpPostEndpoint { class HttpPostEndpoint {
public: public:
HttpPostEndpoint(JsonSerializer<T> jsonSerializer, HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonDeserializer<T> jsonDeserializer, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
const String& servicePath, const String& servicePath,
SecurityManager* securityManager, SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_jsonSerializer(jsonSerializer), _stateReader(stateReader),
_jsonDeserializer(jsonDeserializer), _stateUpdater(stateUpdater),
_statefulService(statefulService), _statefulService(statefulService),
_updateHandler( _updateHandler(
servicePath, servicePath,
@ -79,14 +77,14 @@ class HttpPostEndpoint {
server->addHandler(&_updateHandler); server->addHandler(&_updateHandler);
} }
HttpPostEndpoint(JsonSerializer<T> jsonSerializer, HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonDeserializer<T> jsonDeserializer, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
const String& servicePath, const String& servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_jsonSerializer(jsonSerializer), _stateReader(stateReader),
_jsonDeserializer(jsonDeserializer), _stateUpdater(stateUpdater),
_statefulService(statefulService), _statefulService(statefulService),
_updateHandler(servicePath, _updateHandler(servicePath,
std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2), std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2),
@ -97,56 +95,54 @@ class HttpPostEndpoint {
} }
protected: protected:
JsonSerializer<T> _jsonSerializer; JsonStateReader<T> _stateReader;
JsonDeserializer<T> _jsonDeserializer; JsonStateUpdater<T> _stateUpdater;
StatefulService<T>* _statefulService; StatefulService<T>* _statefulService;
AsyncCallbackJsonWebHandler _updateHandler; AsyncCallbackJsonWebHandler _updateHandler;
size_t _bufferSize; size_t _bufferSize;
void updateSettings(AsyncWebServerRequest* request, JsonVariant& json) { void updateSettings(AsyncWebServerRequest* request, JsonVariant& json) {
if (json.is<JsonObject>()) { if (!json.is<JsonObject>()) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize);
// use callback to update the settings once the response is complete
request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); });
// update the settings, deferring the call to the update handlers to when the response is complete
_statefulService->updateWithoutPropagation([&](T& settings) {
JsonObject jsonObject = json.as<JsonObject>();
_jsonDeserializer(jsonObject, settings);
jsonObject = response->getRoot().to<JsonObject>();
_jsonSerializer(settings, jsonObject);
});
// write the response to the client
response->setLength();
request->send(response);
} else {
request->send(400); request->send(400);
return;
} }
JsonObject jsonObject = json.as<JsonObject>();
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
if (outcome == StateUpdateResult::ERROR) {
request->send(400);
return;
}
if (outcome == StateUpdateResult::CHANGED) {
request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); });
}
AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize);
jsonObject = response->getRoot().to<JsonObject>();
_statefulService->read(jsonObject, _stateReader);
response->setLength();
request->send(response);
} }
}; };
template <class T> template <class T>
class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> { class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
public: public:
HttpEndpoint(JsonSerializer<T> jsonSerializer, HttpEndpoint(JsonStateReader<T> stateReader,
JsonDeserializer<T> jsonDeserializer, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
const String& servicePath, const String& servicePath,
SecurityManager* securityManager, SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN, AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
HttpGetEndpoint<T>(jsonSerializer, HttpGetEndpoint<T>(stateReader,
statefulService, statefulService,
server, server,
servicePath, servicePath,
securityManager, securityManager,
authenticationPredicate, authenticationPredicate,
bufferSize), bufferSize),
HttpPostEndpoint<T>(jsonSerializer, HttpPostEndpoint<T>(stateReader,
jsonDeserializer, stateUpdater,
statefulService, statefulService,
server, server,
servicePath, servicePath,
@ -155,14 +151,14 @@ class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
bufferSize) { bufferSize) {
} }
HttpEndpoint(JsonSerializer<T> jsonSerializer, HttpEndpoint(JsonStateReader<T> stateReader,
JsonDeserializer<T> jsonDeserializer, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
const String& servicePath, const String& servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
HttpGetEndpoint<T>(jsonSerializer, statefulService, server, servicePath, bufferSize), HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, bufferSize),
HttpPostEndpoint<T>(jsonSerializer, jsonDeserializer, statefulService, server, servicePath, bufferSize) { HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) {
} }
}; };

View File

@ -1,9 +0,0 @@
#ifndef JsonDeserializer_h
#define JsonDeserializer_h
#include <ArduinoJson.h>
template <class T>
using JsonDeserializer = void (*)(JsonObject& root, T& settings);
#endif // end JsonDeserializer

View File

@ -1,9 +0,0 @@
#ifndef JsonSerializer_h
#define JsonSerializer_h
#include <ArduinoJson.h>
template <class T>
using JsonSerializer = void (*)(T& settings, JsonObject& root);
#endif // end JsonSerializer

View File

@ -2,8 +2,6 @@
#define MqttPubSub_h #define MqttPubSub_h
#include <StatefulService.h> #include <StatefulService.h>
#include <JsonSerializer.h>
#include <JsonDeserializer.h>
#include <AsyncMqttClient.h> #include <AsyncMqttClient.h>
#define MQTT_ORIGIN_ID "mqtt" #define MQTT_ORIGIN_ID "mqtt"
@ -31,12 +29,12 @@ class MqttConnector {
template <class T> template <class T>
class MqttPub : virtual public MqttConnector<T> { class MqttPub : virtual public MqttConnector<T> {
public: public:
MqttPub(JsonSerializer<T> jsonSerializer, MqttPub(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncMqttClient* mqttClient, AsyncMqttClient* mqttClient,
const String& pubTopic = "", const String& pubTopic = "",
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
MqttConnector<T>(statefulService, mqttClient, bufferSize), _jsonSerializer(jsonSerializer), _pubTopic(pubTopic) { MqttConnector<T>(statefulService, mqttClient, bufferSize), _stateReader(stateReader), _pubTopic(pubTopic) {
MqttConnector<T>::_statefulService->addUpdateHandler([&](const String& originId) { publish(); }, false); MqttConnector<T>::_statefulService->addUpdateHandler([&](const String& originId) { publish(); }, false);
} }
@ -51,7 +49,7 @@ class MqttPub : virtual public MqttConnector<T> {
} }
private: private:
JsonSerializer<T> _jsonSerializer; JsonStateReader<T> _stateReader;
String _pubTopic; String _pubTopic;
void publish() { void publish() {
@ -59,7 +57,7 @@ class MqttPub : virtual public MqttConnector<T> {
// serialize to json doc // serialize to json doc
DynamicJsonDocument json(MqttConnector<T>::_bufferSize); DynamicJsonDocument json(MqttConnector<T>::_bufferSize);
JsonObject jsonObject = json.to<JsonObject>(); JsonObject jsonObject = json.to<JsonObject>();
MqttConnector<T>::_statefulService->read(jsonObject, _jsonSerializer); MqttConnector<T>::_statefulService->read(jsonObject, _stateReader);
// serialize to string // serialize to string
String payload; String payload;
@ -74,14 +72,12 @@ class MqttPub : virtual public MqttConnector<T> {
template <class T> template <class T>
class MqttSub : virtual public MqttConnector<T> { class MqttSub : virtual public MqttConnector<T> {
public: public:
MqttSub(JsonDeserializer<T> jsonDeserializer, MqttSub(JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncMqttClient* mqttClient, AsyncMqttClient* mqttClient,
const String& subTopic = "", const String& subTopic = "",
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
MqttConnector<T>(statefulService, mqttClient, bufferSize), MqttConnector<T>(statefulService, mqttClient, bufferSize), _stateUpdater(stateUpdater), _subTopic(subTopic) {
_jsonDeserializer(jsonDeserializer),
_subTopic(subTopic) {
MqttConnector<T>::_mqttClient->onMessage(std::bind(&MqttSub::onMqttMessage, MqttConnector<T>::_mqttClient->onMessage(std::bind(&MqttSub::onMqttMessage,
this, this,
std::placeholders::_1, std::placeholders::_1,
@ -110,7 +106,7 @@ class MqttSub : virtual public MqttConnector<T> {
} }
private: private:
JsonDeserializer<T> _jsonDeserializer; JsonStateUpdater<T> _stateUpdater;
String _subTopic; String _subTopic;
void subscribe() { void subscribe() {
@ -135,7 +131,7 @@ class MqttSub : virtual public MqttConnector<T> {
DeserializationError error = deserializeJson(json, payload, len); DeserializationError error = deserializeJson(json, payload, len);
if (!error && json.is<JsonObject>()) { if (!error && json.is<JsonObject>()) {
JsonObject jsonObject = json.as<JsonObject>(); JsonObject jsonObject = json.as<JsonObject>();
MqttConnector<T>::_statefulService->update(jsonObject, _jsonDeserializer, MQTT_ORIGIN_ID); MqttConnector<T>::_statefulService->update(jsonObject, _stateUpdater, MQTT_ORIGIN_ID);
} }
} }
}; };
@ -143,16 +139,16 @@ class MqttSub : virtual public MqttConnector<T> {
template <class T> template <class T>
class MqttPubSub : public MqttPub<T>, public MqttSub<T> { class MqttPubSub : public MqttPub<T>, public MqttSub<T> {
public: public:
MqttPubSub(JsonSerializer<T> jsonSerializer, MqttPubSub(JsonStateReader<T> stateReader,
JsonDeserializer<T> jsonDeserializer, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncMqttClient* mqttClient, AsyncMqttClient* mqttClient,
const String& pubTopic = "", const String& pubTopic = "",
const String& subTopic = "", const String& subTopic = "",
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
MqttConnector<T>(statefulService, mqttClient, bufferSize), MqttConnector<T>(statefulService, mqttClient, bufferSize),
MqttPub<T>(jsonSerializer, statefulService, mqttClient, pubTopic, bufferSize), MqttPub<T>(stateReader, statefulService, mqttClient, pubTopic, bufferSize),
MqttSub<T>(jsonDeserializer, statefulService, mqttClient, subTopic, bufferSize) { MqttSub<T>(stateUpdater, statefulService, mqttClient, subTopic, bufferSize) {
} }
public: public:

View File

@ -21,13 +21,8 @@ static char* retainCstr(const char* cstr, char** ptr) {
} }
MqttSettingsService::MqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : MqttSettingsService::MqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(MqttSettings::serialize, _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager),
MqttSettings::deserialize, _fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE),
this,
server,
MQTT_SETTINGS_SERVICE_PATH,
securityManager),
_fsPersistence(MqttSettings::serialize, MqttSettings::deserialize, this, fs, MQTT_SETTINGS_FILE),
_retainedHost(nullptr), _retainedHost(nullptr),
_retainedClientId(nullptr), _retainedClientId(nullptr),
_retainedUsername(nullptr), _retainedUsername(nullptr),

View File

@ -75,7 +75,7 @@ class MqttSettings {
bool cleanSession; bool cleanSession;
uint16_t maxTopicLength; uint16_t maxTopicLength;
static void serialize(MqttSettings& settings, JsonObject& root) { static void read(MqttSettings& settings, JsonObject& root) {
root["enabled"] = settings.enabled; root["enabled"] = settings.enabled;
root["host"] = settings.host; root["host"] = settings.host;
root["port"] = settings.port; root["port"] = settings.port;
@ -87,7 +87,7 @@ class MqttSettings {
root["max_topic_length"] = settings.maxTopicLength; root["max_topic_length"] = settings.maxTopicLength;
} }
static void deserialize(JsonObject& root, MqttSettings& settings) { static StateUpdateResult update(JsonObject& root, MqttSettings& settings) {
settings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED; settings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
settings.host = root["host"] | FACTORY_MQTT_HOST; settings.host = root["host"] | FACTORY_MQTT_HOST;
settings.port = root["port"] | FACTORY_MQTT_PORT; settings.port = root["port"] | FACTORY_MQTT_PORT;
@ -97,6 +97,7 @@ class MqttSettings {
settings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE; settings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE;
settings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION; settings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
settings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH; settings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH;
return StateUpdateResult::CHANGED;
} }
}; };

View File

@ -1,13 +1,8 @@
#include <NTPSettingsService.h> #include <NTPSettingsService.h>
NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(NTPSettings::serialize, _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager),
NTPSettings::deserialize, _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) {
this,
server,
NTP_SETTINGS_SERVICE_PATH,
securityManager),
_fsPersistence(NTPSettings::serialize, NTPSettings::deserialize, this, fs, NTP_SETTINGS_FILE) {
#ifdef ESP32 #ifdef ESP32
WiFi.onEvent( WiFi.onEvent(
std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2), std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),

View File

@ -37,18 +37,19 @@ class NTPSettings {
String tzFormat; String tzFormat;
String server; String server;
static void serialize(NTPSettings& settings, JsonObject& root) { static void read(NTPSettings& settings, JsonObject& root) {
root["enabled"] = settings.enabled; root["enabled"] = settings.enabled;
root["server"] = settings.server; root["server"] = settings.server;
root["tz_label"] = settings.tzLabel; root["tz_label"] = settings.tzLabel;
root["tz_format"] = settings.tzFormat; root["tz_format"] = settings.tzFormat;
} }
static void deserialize(JsonObject& root, NTPSettings& settings) { static StateUpdateResult update(JsonObject& root, NTPSettings& settings) {
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED; settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
settings.server = root["server"] | FACTORY_NTP_SERVER; settings.server = root["server"] | FACTORY_NTP_SERVER;
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL; settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT; settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
return StateUpdateResult::CHANGED;
} }
}; };

View File

@ -1,13 +1,8 @@
#include <OTASettingsService.h> #include <OTASettingsService.h>
OTASettingsService::OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : OTASettingsService::OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(OTASettings::serialize, _httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager),
OTASettings::deserialize, _fsPersistence(OTASettings::read, OTASettings::update, this, fs, OTA_SETTINGS_FILE),
this,
server,
OTA_SETTINGS_SERVICE_PATH,
securityManager),
_fsPersistence(OTASettings::serialize, OTASettings::deserialize, this, fs, OTA_SETTINGS_FILE),
_arduinoOTA(nullptr) { _arduinoOTA(nullptr) {
#ifdef ESP32 #ifdef ESP32
WiFi.onEvent(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2), WiFi.onEvent(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2),

View File

@ -34,16 +34,17 @@ class OTASettings {
int port; int port;
String password; String password;
static void serialize(OTASettings& settings, JsonObject& root) { static void read(OTASettings& settings, JsonObject& root) {
root["enabled"] = settings.enabled; root["enabled"] = settings.enabled;
root["port"] = settings.port; root["port"] = settings.port;
root["password"] = settings.password; root["password"] = settings.password;
} }
static void deserialize(JsonObject& root, OTASettings& settings) { static StateUpdateResult update(JsonObject& root, OTASettings& settings) {
settings.enabled = root["enabled"] | FACTORY_OTA_ENABLED; settings.enabled = root["enabled"] | FACTORY_OTA_ENABLED;
settings.port = root["port"] | FACTORY_OTA_PORT; settings.port = root["port"] | FACTORY_OTA_PORT;
settings.password = root["password"] | FACTORY_OTA_PASSWORD; settings.password = root["password"] | FACTORY_OTA_PASSWORD;
return StateUpdateResult::CHANGED;
} }
}; };

View File

@ -1,13 +1,8 @@
#include <SecuritySettingsService.h> #include <SecuritySettingsService.h>
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) :
_httpEndpoint(SecuritySettings::serialize, _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this),
SecuritySettings::deserialize, _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE),
this,
server,
SECURITY_SETTINGS_PATH,
this),
_fsPersistence(SecuritySettings::serialize, SecuritySettings::deserialize, this, fs, SECURITY_SETTINGS_FILE),
_jwtHandler(FACTORY_JWT_SECRET) { _jwtHandler(FACTORY_JWT_SECRET) {
addUpdateHandler([&](const String& originId) { configureJWTHandler(); }, false); addUpdateHandler([&](const String& originId) { configureJWTHandler(); }, false);
} }

View File

@ -29,7 +29,7 @@ class SecuritySettings {
String jwtSecret; String jwtSecret;
std::list<User> users; std::list<User> users;
static void serialize(SecuritySettings& settings, JsonObject& root) { static void read(SecuritySettings& settings, JsonObject& root) {
// secret // secret
root["jwt_secret"] = settings.jwtSecret; root["jwt_secret"] = settings.jwtSecret;
@ -43,7 +43,7 @@ class SecuritySettings {
} }
} }
static void deserialize(JsonObject& root, SecuritySettings& settings) { static StateUpdateResult update(JsonObject& root, SecuritySettings& settings) {
// secret // secret
settings.jwtSecret = root["jwt_secret"] | FACTORY_JWT_SECRET; settings.jwtSecret = root["jwt_secret"] | FACTORY_JWT_SECRET;
@ -57,6 +57,7 @@ class SecuritySettings {
settings.users.push_back(User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true)); settings.users.push_back(User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true));
settings.users.push_back(User(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false)); settings.users.push_back(User(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false));
} }
return StateUpdateResult::CHANGED;
} }
}; };

View File

@ -2,8 +2,7 @@
#define StatefulService_h #define StatefulService_h
#include <Arduino.h> #include <Arduino.h>
#include <JsonDeserializer.h> #include <ArduinoJson.h>
#include <JsonSerializer.h>
#include <list> #include <list>
#include <functional> #include <functional>
@ -16,6 +15,18 @@
#define DEFAULT_BUFFER_SIZE 1024 #define DEFAULT_BUFFER_SIZE 1024
#endif #endif
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
};
template <class T>
using JsonStateUpdater = StateUpdateResult (*)(JsonObject& root, T& settings);
template <class T>
using JsonStateReader = void (*)(T& settings, JsonObject& root);
typedef size_t update_handler_id_t; typedef size_t update_handler_id_t;
typedef std::function<void(const String& originId)> StateUpdateCallback; typedef std::function<void(const String& originId)> StateUpdateCallback;
@ -60,66 +71,50 @@ class StatefulService {
} }
} }
void updateWithoutPropagation(std::function<void(T&)> callback) { StateUpdateResult update(std::function<StateUpdateResult(T&)> stateUpdater, const String& originId) {
#ifdef ESP32 beginTransaction();
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); StateUpdateResult result = stateUpdater(_state);
#endif endTransaction();
callback(_state); if (result == StateUpdateResult::CHANGED) {
#ifdef ESP32 callUpdateHandlers(originId);
xSemaphoreGiveRecursive(_accessMutex); }
#endif return result;
} }
void updateWithoutPropagation(JsonObject& jsonObject, JsonDeserializer<T> deserializer) { StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T&)> stateUpdater) {
#ifdef ESP32 beginTransaction();
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); StateUpdateResult result = stateUpdater(_state);
#endif endTransaction();
deserializer(jsonObject, _state); return result;
#ifdef ESP32
xSemaphoreGiveRecursive(_accessMutex);
#endif
} }
void update(std::function<void(T&)> callback, const String& originId) { StateUpdateResult update(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater, const String& originId) {
#ifdef ESP32 beginTransaction();
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); StateUpdateResult result = stateUpdater(jsonObject, _state);
#endif endTransaction();
callback(_state); if (result == StateUpdateResult::CHANGED) {
callUpdateHandlers(originId); callUpdateHandlers(originId);
#ifdef ESP32 }
xSemaphoreGiveRecursive(_accessMutex); return result;
#endif
} }
void update(JsonObject& jsonObject, JsonDeserializer<T> deserializer, const String& originId) { StateUpdateResult updateWithoutPropagation(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater) {
#ifdef ESP32 beginTransaction();
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); StateUpdateResult result = stateUpdater(jsonObject, _state);
#endif endTransaction();
deserializer(jsonObject, _state); return result;
callUpdateHandlers(originId);
#ifdef ESP32
xSemaphoreGiveRecursive(_accessMutex);
#endif
} }
void read(std::function<void(T&)> callback) { void read(std::function<void(T&)> stateReader) {
#ifdef ESP32 beginTransaction();
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); stateReader(_state);
#endif endTransaction();
callback(_state);
#ifdef ESP32
xSemaphoreGiveRecursive(_accessMutex);
#endif
} }
void read(JsonObject& jsonObject, JsonSerializer<T> serializer) { void read(JsonObject& jsonObject, JsonStateReader<T> stateReader) {
#ifdef ESP32 beginTransaction();
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY); stateReader(_state, jsonObject);
#endif endTransaction();
serializer(_state, jsonObject);
#ifdef ESP32
xSemaphoreGiveRecursive(_accessMutex);
#endif
} }
void callUpdateHandlers(const String& originId) { void callUpdateHandlers(const String& originId) {
@ -131,6 +126,18 @@ class StatefulService {
protected: protected:
T _state; T _state;
inline void beginTransaction() {
#ifdef ESP32
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
#endif
}
inline void endTransaction() {
#ifdef ESP32
xSemaphoreGiveRecursive(_accessMutex);
#endif
}
private: private:
#ifdef ESP32 #ifdef ESP32
SemaphoreHandle_t _accessMutex; SemaphoreHandle_t _accessMutex;

View File

@ -2,8 +2,6 @@
#define WebSocketTxRx_h #define WebSocketTxRx_h
#include <StatefulService.h> #include <StatefulService.h>
#include <JsonSerializer.h>
#include <JsonDeserializer.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#define WEB_SOCKET_CLIENT_ID_MSG_SIZE 128 #define WEB_SOCKET_CLIENT_ID_MSG_SIZE 128
@ -75,7 +73,7 @@ class WebSocketConnector {
template <class T> template <class T>
class WebSocketTx : virtual public WebSocketConnector<T> { class WebSocketTx : virtual public WebSocketConnector<T> {
public: public:
WebSocketTx(JsonSerializer<T> jsonSerializer, WebSocketTx(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
char const* webSocketPath, char const* webSocketPath,
@ -88,19 +86,19 @@ class WebSocketTx : virtual public WebSocketConnector<T> {
securityManager, securityManager,
authenticationPredicate, authenticationPredicate,
bufferSize), bufferSize),
_jsonSerializer(jsonSerializer) { _stateReader(stateReader) {
WebSocketConnector<T>::_statefulService->addUpdateHandler( WebSocketConnector<T>::_statefulService->addUpdateHandler(
[&](const String& originId) { transmitData(nullptr, originId); }, false); [&](const String& originId) { transmitData(nullptr, originId); }, false);
} }
WebSocketTx(JsonSerializer<T> jsonSerializer, WebSocketTx(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
char const* webSocketPath, char const* webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), _jsonSerializer(jsonSerializer) { WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), _stateReader(stateReader) {
WebSocketConnector<T>::_statefulService->addUpdateHandler([&](const String& originId) { transmitData(nullptr, originId); }, WebSocketConnector<T>::_statefulService->addUpdateHandler(
false); [&](const String& originId) { transmitData(nullptr, originId); }, false);
} }
protected: protected:
@ -118,7 +116,7 @@ class WebSocketTx : virtual public WebSocketConnector<T> {
} }
private: private:
JsonSerializer<T> _jsonSerializer; JsonStateReader<T> _stateReader;
void transmitId(AsyncWebSocketClient* client) { void transmitId(AsyncWebSocketClient* client) {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(WEB_SOCKET_CLIENT_ID_MSG_SIZE); DynamicJsonDocument jsonDocument = DynamicJsonDocument(WEB_SOCKET_CLIENT_ID_MSG_SIZE);
@ -146,7 +144,7 @@ class WebSocketTx : virtual public WebSocketConnector<T> {
root["type"] = "payload"; root["type"] = "payload";
root["origin_id"] = originId; root["origin_id"] = originId;
JsonObject payload = root.createNestedObject("payload"); JsonObject payload = root.createNestedObject("payload");
WebSocketConnector<T>::_statefulService->read(payload, _jsonSerializer); WebSocketConnector<T>::_statefulService->read(payload, _stateReader);
size_t len = measureJson(jsonDocument); size_t len = measureJson(jsonDocument);
AsyncWebSocketMessageBuffer* buffer = WebSocketConnector<T>::_webSocket.makeBuffer(len); AsyncWebSocketMessageBuffer* buffer = WebSocketConnector<T>::_webSocket.makeBuffer(len);
@ -164,7 +162,7 @@ class WebSocketTx : virtual public WebSocketConnector<T> {
template <class T> template <class T>
class WebSocketRx : virtual public WebSocketConnector<T> { class WebSocketRx : virtual public WebSocketConnector<T> {
public: public:
WebSocketRx(JsonDeserializer<T> jsonDeserializer, WebSocketRx(JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
char const* webSocketPath, char const* webSocketPath,
@ -177,15 +175,15 @@ class WebSocketRx : virtual public WebSocketConnector<T> {
securityManager, securityManager,
authenticationPredicate, authenticationPredicate,
bufferSize), bufferSize),
_jsonDeserializer(jsonDeserializer) { _stateUpdater(stateUpdater) {
} }
WebSocketRx(JsonDeserializer<T> jsonDeserializer, WebSocketRx(JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
char const* webSocketPath, char const* webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), _jsonDeserializer(jsonDeserializer) { WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), _stateUpdater(stateUpdater) {
} }
protected: protected:
@ -204,7 +202,7 @@ class WebSocketRx : virtual public WebSocketConnector<T> {
if (!error && jsonDocument.is<JsonObject>()) { if (!error && jsonDocument.is<JsonObject>()) {
JsonObject jsonObject = jsonDocument.as<JsonObject>(); JsonObject jsonObject = jsonDocument.as<JsonObject>();
WebSocketConnector<T>::_statefulService->update( WebSocketConnector<T>::_statefulService->update(
jsonObject, _jsonDeserializer, WebSocketConnector<T>::clientId(client)); jsonObject, _stateUpdater, WebSocketConnector<T>::clientId(client));
} }
} }
} }
@ -212,14 +210,14 @@ class WebSocketRx : virtual public WebSocketConnector<T> {
} }
private: private:
JsonDeserializer<T> _jsonDeserializer; JsonStateUpdater<T> _stateUpdater;
}; };
template <class T> template <class T>
class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> { class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
public: public:
WebSocketTxRx(JsonSerializer<T> jsonSerializer, WebSocketTxRx(JsonStateReader<T> stateReader,
JsonDeserializer<T> jsonDeserializer, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
char const* webSocketPath, char const* webSocketPath,
@ -232,14 +230,14 @@ class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
securityManager, securityManager,
authenticationPredicate, authenticationPredicate,
bufferSize), bufferSize),
WebSocketTx<T>(jsonSerializer, WebSocketTx<T>(stateReader,
statefulService, statefulService,
server, server,
webSocketPath, webSocketPath,
securityManager, securityManager,
authenticationPredicate, authenticationPredicate,
bufferSize), bufferSize),
WebSocketRx<T>(jsonDeserializer, WebSocketRx<T>(stateUpdater,
statefulService, statefulService,
server, server,
webSocketPath, webSocketPath,
@ -248,15 +246,15 @@ class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
bufferSize) { bufferSize) {
} }
WebSocketTxRx(JsonSerializer<T> jsonSerializer, WebSocketTxRx(JsonStateReader<T> stateReader,
JsonDeserializer<T> jsonDeserializer, JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService, StatefulService<T>* statefulService,
AsyncWebServer* server, AsyncWebServer* server,
char const* webSocketPath, char const* webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) : size_t bufferSize = DEFAULT_BUFFER_SIZE) :
WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize),
WebSocketTx<T>(jsonSerializer, statefulService, server, webSocketPath, bufferSize), WebSocketTx<T>(stateReader, statefulService, server, webSocketPath, bufferSize),
WebSocketRx<T>(jsonDeserializer, statefulService, server, webSocketPath, bufferSize) { WebSocketRx<T>(stateUpdater, statefulService, server, webSocketPath, bufferSize) {
} }
protected: protected:

View File

@ -1,13 +1,8 @@
#include <WiFiSettingsService.h> #include <WiFiSettingsService.h>
WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(WiFiSettings::serialize, _httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager),
WiFiSettings::deserialize, _fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE),
this,
server,
WIFI_SETTINGS_SERVICE_PATH,
securityManager),
_fsPersistence(WiFiSettings::serialize, WiFiSettings::deserialize, this, fs, WIFI_SETTINGS_FILE),
_lastConnectionAttempt(0) { _lastConnectionAttempt(0) {
// We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default. // We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default.
// If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future. // If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future.

View File

@ -37,7 +37,7 @@ class WiFiSettings {
IPAddress dnsIP1; IPAddress dnsIP1;
IPAddress dnsIP2; IPAddress dnsIP2;
static void serialize(WiFiSettings& settings, JsonObject& root) { static void read(WiFiSettings& settings, JsonObject& root) {
// connection settings // connection settings
root["ssid"] = settings.ssid; root["ssid"] = settings.ssid;
root["password"] = settings.password; root["password"] = settings.password;
@ -52,7 +52,7 @@ class WiFiSettings {
JsonUtils::writeIP(root, "dns_ip_2", settings.dnsIP2); JsonUtils::writeIP(root, "dns_ip_2", settings.dnsIP2);
} }
static void deserialize(JsonObject& root, WiFiSettings& settings) { static StateUpdateResult update(JsonObject& root, WiFiSettings& settings) {
settings.ssid = root["ssid"] | FACTORY_WIFI_SSID; settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
settings.password = root["password"] | FACTORY_WIFI_PASSWORD; settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME; settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
@ -78,6 +78,7 @@ class WiFiSettings {
(settings.localIP == INADDR_NONE || settings.gatewayIP == INADDR_NONE || settings.subnetMask == INADDR_NONE)) { (settings.localIP == INADDR_NONE || settings.gatewayIP == INADDR_NONE || settings.subnetMask == INADDR_NONE)) {
settings.staticIPConfig = false; settings.staticIPConfig = false;
} }
return StateUpdateResult::CHANGED;
} }
}; };

View File

@ -1,14 +1,14 @@
#include <LightMqttSettingsService.h> #include <LightMqttSettingsService.h>
LightMqttSettingsService::LightMqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : LightMqttSettingsService::LightMqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(LightMqttSettings::serialize, _httpEndpoint(LightMqttSettings::read,
LightMqttSettings::deserialize, LightMqttSettings::update,
this, this,
server, server,
LIGHT_BROKER_SETTINGS_PATH, LIGHT_BROKER_SETTINGS_PATH,
securityManager, securityManager,
AuthenticationPredicates::IS_AUTHENTICATED), AuthenticationPredicates::IS_AUTHENTICATED),
_fsPersistence(LightMqttSettings::serialize, LightMqttSettings::deserialize, this, fs, LIGHT_BROKER_SETTINGS_FILE) { _fsPersistence(LightMqttSettings::read, LightMqttSettings::update, this, fs, LIGHT_BROKER_SETTINGS_FILE) {
} }
void LightMqttSettingsService::begin() { void LightMqttSettingsService::begin() {

View File

@ -14,16 +14,17 @@ class LightMqttSettings {
String name; String name;
String uniqueId; String uniqueId;
static void serialize(LightMqttSettings& settings, JsonObject& root) { static void read(LightMqttSettings& settings, JsonObject& root) {
root["mqtt_path"] = settings.mqttPath; root["mqtt_path"] = settings.mqttPath;
root["name"] = settings.name; root["name"] = settings.name;
root["unique_id"] = settings.uniqueId; root["unique_id"] = settings.uniqueId;
} }
static void deserialize(JsonObject& root, LightMqttSettings& settings) { static StateUpdateResult update(JsonObject& root, LightMqttSettings& settings) {
settings.mqttPath = root["mqtt_path"] | ESPUtils::defaultDeviceValue("homeassistant/light/"); settings.mqttPath = root["mqtt_path"] | ESPUtils::defaultDeviceValue("homeassistant/light/");
settings.name = root["name"] | ESPUtils::defaultDeviceValue("light-"); settings.name = root["name"] | ESPUtils::defaultDeviceValue("light-");
settings.uniqueId = root["unique_id"] | ESPUtils::defaultDeviceValue("light-"); settings.uniqueId = root["unique_id"] | ESPUtils::defaultDeviceValue("light-");
return StateUpdateResult::CHANGED;
} }
}; };

View File

@ -4,16 +4,16 @@ LightStateService::LightStateService(AsyncWebServer* server,
SecurityManager* securityManager, SecurityManager* securityManager,
AsyncMqttClient* mqttClient, AsyncMqttClient* mqttClient,
LightMqttSettingsService* lightMqttSettingsService) : LightMqttSettingsService* lightMqttSettingsService) :
_httpEndpoint(LightState::serialize, _httpEndpoint(LightState::read,
LightState::deserialize, LightState::update,
this, this,
server, server,
LIGHT_SETTINGS_ENDPOINT_PATH, LIGHT_SETTINGS_ENDPOINT_PATH,
securityManager, securityManager,
AuthenticationPredicates::IS_AUTHENTICATED), AuthenticationPredicates::IS_AUTHENTICATED),
_mqttPubSub(LightState::haSerialize, LightState::haDeserialize, this, mqttClient), _mqttPubSub(LightState::haRead, LightState::haUpdate, this, mqttClient),
_webSocket(LightState::serialize, _webSocket(LightState::read,
LightState::deserialize, LightState::update,
this, this,
server, server,
LIGHT_SETTINGS_SOCKET_PATH, LIGHT_SETTINGS_SOCKET_PATH,
@ -40,6 +40,7 @@ void LightStateService::begin() {
} }
void LightStateService::onConfigUpdated() { void LightStateService::onConfigUpdated() {
Serial.printf_P(PSTR("The light is now: %s\r\n"), _state.ledOn ? "on" : "off");
digitalWrite(BLINK_LED, _state.ledOn ? LED_ON : LED_OFF); digitalWrite(BLINK_LED, _state.ledOn ? LED_ON : LED_OFF);
} }

View File

@ -31,21 +31,38 @@ class LightState {
public: public:
bool ledOn; bool ledOn;
static void serialize(LightState& settings, JsonObject& root) { static void read(LightState& settings, JsonObject& root) {
root["led_on"] = settings.ledOn; root["led_on"] = settings.ledOn;
} }
static void deserialize(JsonObject& root, LightState& settings) { static StateUpdateResult update(JsonObject& root, LightState& lightState) {
settings.ledOn = root["led_on"] | DEFAULT_LED_STATE; boolean newState = root["led_on"] | DEFAULT_LED_STATE;
if (lightState.ledOn != newState) {
lightState.ledOn = newState;
return StateUpdateResult::CHANGED;
}
return StateUpdateResult::UNCHANGED;
} }
static void haSerialize(LightState& settings, JsonObject& root) { static void haRead(LightState& settings, JsonObject& root) {
root["state"] = settings.ledOn ? ON_STATE : OFF_STATE; root["state"] = settings.ledOn ? ON_STATE : OFF_STATE;
} }
static void haDeserialize(JsonObject& root, LightState& settings) { static StateUpdateResult haUpdate(JsonObject& root, LightState& lightState) {
String state = root["state"]; String state = root["state"];
settings.ledOn = strcmp(ON_STATE, state.c_str()) ? false : true; // parse new led state
boolean newState = false;
if (state.equals(ON_STATE)) {
newState = true;
} else if (!state.equals(OFF_STATE)) {
return StateUpdateResult::ERROR;
}
// change the new state, if required
if (lightState.ledOn != newState) {
lightState.ledOn = newState;
return StateUpdateResult::CHANGED;
}
return StateUpdateResult::UNCHANGED;
} }
}; };