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
24 changed files with 253 additions and 258 deletions

View File

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

View File

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

View File

@ -2,21 +2,19 @@
#define FSPersistence_h
#include <StatefulService.h>
#include <JsonSerializer.h>
#include <JsonDeserializer.h>
#include <FS.h>
template <class T>
class FSPersistence {
public:
FSPersistence(JsonSerializer<T> jsonSerializer,
JsonDeserializer<T> jsonDeserializer,
FSPersistence(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
FS* fs,
char const* filePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_jsonSerializer(jsonSerializer),
_jsonDeserializer(jsonDeserializer),
_stateReader(stateReader),
_stateUpdater(stateUpdater),
_statefulService(statefulService),
_fs(fs),
_filePath(filePath),
@ -33,7 +31,7 @@ class FSPersistence {
DeserializationError error = deserializeJson(jsonDocument, settingsFile);
if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
JsonObject jsonObject = jsonDocument.as<JsonObject>();
_statefulService->updateWithoutPropagation(jsonObject, _jsonDeserializer);
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
settingsFile.close();
return;
}
@ -49,7 +47,7 @@ class FSPersistence {
// create and populate a new json object
DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
JsonObject jsonObject = jsonDocument.to<JsonObject>();
_statefulService->read(jsonObject, _jsonSerializer);
_statefulService->read(jsonObject, _stateReader);
// serialize it to filesystem
File settingsFile = _fs->open(_filePath, "w");
@ -79,21 +77,21 @@ class FSPersistence {
}
private:
JsonSerializer<T> _jsonSerializer;
JsonDeserializer<T> _jsonDeserializer;
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T>* _statefulService;
FS* _fs;
FS* _fs;
char const* _filePath;
size_t _bufferSize;
update_handler_id_t _updateHandlerId;
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.
virtual void applyDefaults() {
DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
JsonObject jsonObject = jsonDocument.as<JsonObject>();
_statefulService->updateWithoutPropagation(jsonObject, _jsonDeserializer);
_statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
}
};

View File

@ -8,46 +8,44 @@
#include <SecurityManager.h>
#include <StatefulService.h>
#include <JsonSerializer.h>
#include <JsonDeserializer.h>
#define HTTP_ENDPOINT_ORIGIN_ID "http"
template <class T>
class HttpGetEndpoint {
public:
HttpGetEndpoint(JsonSerializer<T> jsonSerializer,
HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
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,
securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1),
authenticationPredicate));
}
HttpGetEndpoint(JsonSerializer<T> jsonSerializer,
HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
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));
}
protected:
JsonSerializer<T> _jsonSerializer;
JsonStateReader<T> _stateReader;
StatefulService<T>* _statefulService;
size_t _bufferSize;
void fetchSettings(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize);
JsonObject jsonObject = response->getRoot().to<JsonObject>();
_statefulService->read(jsonObject, _jsonSerializer);
_statefulService->read(jsonObject, _stateReader);
response->setLength();
request->send(response);
@ -57,16 +55,16 @@ class HttpGetEndpoint {
template <class T>
class HttpPostEndpoint {
public:
HttpPostEndpoint(JsonSerializer<T> jsonSerializer,
JsonDeserializer<T> jsonDeserializer,
HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_jsonSerializer(jsonSerializer),
_jsonDeserializer(jsonDeserializer),
_stateReader(stateReader),
_stateUpdater(stateUpdater),
_statefulService(statefulService),
_updateHandler(
servicePath,
@ -79,14 +77,14 @@ class HttpPostEndpoint {
server->addHandler(&_updateHandler);
}
HttpPostEndpoint(JsonSerializer<T> jsonSerializer,
JsonDeserializer<T> jsonDeserializer,
HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
_jsonSerializer(jsonSerializer),
_jsonDeserializer(jsonDeserializer),
_stateReader(stateReader),
_stateUpdater(stateUpdater),
_statefulService(statefulService),
_updateHandler(servicePath,
std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2),
@ -97,56 +95,54 @@ class HttpPostEndpoint {
}
protected:
JsonSerializer<T> _jsonSerializer;
JsonDeserializer<T> _jsonDeserializer;
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T>* _statefulService;
AsyncCallbackJsonWebHandler _updateHandler;
size_t _bufferSize;
void updateSettings(AsyncWebServerRequest* request, JsonVariant& json) {
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 {
if (!json.is<JsonObject>()) {
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>
class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
public:
HttpEndpoint(JsonSerializer<T> jsonSerializer,
JsonDeserializer<T> jsonDeserializer,
HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
SecurityManager* securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
HttpGetEndpoint<T>(jsonSerializer,
HttpGetEndpoint<T>(stateReader,
statefulService,
server,
servicePath,
securityManager,
authenticationPredicate,
bufferSize),
HttpPostEndpoint<T>(jsonSerializer,
jsonDeserializer,
HttpPostEndpoint<T>(stateReader,
stateUpdater,
statefulService,
server,
servicePath,
@ -155,14 +151,14 @@ class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
bufferSize) {
}
HttpEndpoint(JsonSerializer<T> jsonSerializer,
JsonDeserializer<T> jsonDeserializer,
HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncWebServer* server,
const String& servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
HttpGetEndpoint<T>(jsonSerializer, statefulService, server, servicePath, bufferSize),
HttpPostEndpoint<T>(jsonSerializer, jsonDeserializer, statefulService, server, servicePath, bufferSize) {
HttpGetEndpoint<T>(stateReader, 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
#include <StatefulService.h>
#include <JsonSerializer.h>
#include <JsonDeserializer.h>
#include <AsyncMqttClient.h>
#define MQTT_ORIGIN_ID "mqtt"
@ -31,12 +29,12 @@ class MqttConnector {
template <class T>
class MqttPub : virtual public MqttConnector<T> {
public:
MqttPub(JsonSerializer<T> jsonSerializer,
MqttPub(JsonStateReader<T> stateReader,
StatefulService<T>* statefulService,
AsyncMqttClient* mqttClient,
const String& pubTopic = "",
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);
}
@ -51,7 +49,7 @@ class MqttPub : virtual public MqttConnector<T> {
}
private:
JsonSerializer<T> _jsonSerializer;
JsonStateReader<T> _stateReader;
String _pubTopic;
void publish() {
@ -59,7 +57,7 @@ class MqttPub : virtual public MqttConnector<T> {
// serialize to json doc
DynamicJsonDocument json(MqttConnector<T>::_bufferSize);
JsonObject jsonObject = json.to<JsonObject>();
MqttConnector<T>::_statefulService->read(jsonObject, _jsonSerializer);
MqttConnector<T>::_statefulService->read(jsonObject, _stateReader);
// serialize to string
String payload;
@ -74,14 +72,12 @@ class MqttPub : virtual public MqttConnector<T> {
template <class T>
class MqttSub : virtual public MqttConnector<T> {
public:
MqttSub(JsonDeserializer<T> jsonDeserializer,
MqttSub(JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncMqttClient* mqttClient,
const String& subTopic = "",
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
MqttConnector<T>(statefulService, mqttClient, bufferSize),
_jsonDeserializer(jsonDeserializer),
_subTopic(subTopic) {
MqttConnector<T>(statefulService, mqttClient, bufferSize), _stateUpdater(stateUpdater), _subTopic(subTopic) {
MqttConnector<T>::_mqttClient->onMessage(std::bind(&MqttSub::onMqttMessage,
this,
std::placeholders::_1,
@ -110,7 +106,7 @@ class MqttSub : virtual public MqttConnector<T> {
}
private:
JsonDeserializer<T> _jsonDeserializer;
JsonStateUpdater<T> _stateUpdater;
String _subTopic;
void subscribe() {
@ -135,7 +131,7 @@ class MqttSub : virtual public MqttConnector<T> {
DeserializationError error = deserializeJson(json, payload, len);
if (!error && json.is<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>
class MqttPubSub : public MqttPub<T>, public MqttSub<T> {
public:
MqttPubSub(JsonSerializer<T> jsonSerializer,
JsonDeserializer<T> jsonDeserializer,
MqttPubSub(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T>* statefulService,
AsyncMqttClient* mqttClient,
const String& pubTopic = "",
const String& subTopic = "",
size_t bufferSize = DEFAULT_BUFFER_SIZE) :
MqttConnector<T>(statefulService, mqttClient, bufferSize),
MqttPub<T>(jsonSerializer, statefulService, mqttClient, pubTopic, bufferSize),
MqttSub<T>(jsonDeserializer, statefulService, mqttClient, subTopic, bufferSize) {
MqttPub<T>(stateReader, statefulService, mqttClient, pubTopic, bufferSize),
MqttSub<T>(stateUpdater, statefulService, mqttClient, subTopic, bufferSize) {
}
public:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@ class SecuritySettings {
String jwtSecret;
std::list<User> users;
static void serialize(SecuritySettings& settings, JsonObject& root) {
static void read(SecuritySettings& settings, JsonObject& root) {
// secret
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
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_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false));
}
return StateUpdateResult::CHANGED;
}
};

View File

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

View File

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

View File

@ -1,13 +1,8 @@
#include <WiFiSettingsService.h>
WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
_httpEndpoint(WiFiSettings::serialize,
WiFiSettings::deserialize,
this,
server,
WIFI_SETTINGS_SERVICE_PATH,
securityManager),
_fsPersistence(WiFiSettings::serialize, WiFiSettings::deserialize, this, fs, WIFI_SETTINGS_FILE),
_httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager),
_fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE),
_lastConnectionAttempt(0) {
// 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.

View File

@ -37,7 +37,7 @@ class WiFiSettings {
IPAddress dnsIP1;
IPAddress dnsIP2;
static void serialize(WiFiSettings& settings, JsonObject& root) {
static void read(WiFiSettings& settings, JsonObject& root) {
// connection settings
root["ssid"] = settings.ssid;
root["password"] = settings.password;
@ -52,7 +52,7 @@ class WiFiSettings {
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.password = root["password"] | FACTORY_WIFI_PASSWORD;
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.staticIPConfig = false;
}
return StateUpdateResult::CHANGED;
}
};