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:
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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) {
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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
|
@ -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
|
@ -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:
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user