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:
parent
d9ae0f5cf9
commit
0d39c5ca00
55
README.md
55
README.md
@ -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,11 +530,12 @@ 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",
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
request->send(400);
|
||||||
|
return;
|
||||||
// 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>();
|
JsonObject jsonObject = json.as<JsonObject>();
|
||||||
_jsonDeserializer(jsonObject, settings);
|
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>();
|
jsonObject = response->getRoot().to<JsonObject>();
|
||||||
_jsonSerializer(settings, jsonObject);
|
_statefulService->read(jsonObject, _stateReader);
|
||||||
});
|
|
||||||
|
|
||||||
// write the response to the client
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
} else {
|
|
||||||
request->send(400);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
#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:
|
||||||
|
@ -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),
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
xSemaphoreGiveRecursive(_accessMutex);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateWithoutPropagation(JsonObject& jsonObject, JsonDeserializer<T> deserializer) {
|
|
||||||
#ifdef ESP32
|
|
||||||
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
|
|
||||||
#endif
|
|
||||||
deserializer(jsonObject, _state);
|
|
||||||
#ifdef ESP32
|
|
||||||
xSemaphoreGiveRecursive(_accessMutex);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void update(std::function<void(T&)> callback, const String& originId) {
|
|
||||||
#ifdef ESP32
|
|
||||||
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
|
|
||||||
#endif
|
|
||||||
callback(_state);
|
|
||||||
callUpdateHandlers(originId);
|
callUpdateHandlers(originId);
|
||||||
#ifdef ESP32
|
}
|
||||||
xSemaphoreGiveRecursive(_accessMutex);
|
return result;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(JsonObject& jsonObject, JsonDeserializer<T> deserializer, const String& originId) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
StateUpdateResult update(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater, const String& originId) {
|
||||||
|
beginTransaction();
|
||||||
|
StateUpdateResult result = stateUpdater(jsonObject, _state);
|
||||||
|
endTransaction();
|
||||||
|
if (result == StateUpdateResult::CHANGED) {
|
||||||
callUpdateHandlers(originId);
|
callUpdateHandlers(originId);
|
||||||
#ifdef ESP32
|
}
|
||||||
xSemaphoreGiveRecursive(_accessMutex);
|
return result;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void read(std::function<void(T&)> callback) {
|
StateUpdateResult updateWithoutPropagation(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater) {
|
||||||
#ifdef ESP32
|
beginTransaction();
|
||||||
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
|
StateUpdateResult result = stateUpdater(jsonObject, _state);
|
||||||
#endif
|
endTransaction();
|
||||||
callback(_state);
|
return result;
|
||||||
#ifdef ESP32
|
|
||||||
xSemaphoreGiveRecursive(_accessMutex);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void read(JsonObject& jsonObject, JsonSerializer<T> serializer) {
|
void read(std::function<void(T&)> stateReader) {
|
||||||
#ifdef ESP32
|
beginTransaction();
|
||||||
xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
|
stateReader(_state);
|
||||||
#endif
|
endTransaction();
|
||||||
serializer(_state, jsonObject);
|
}
|
||||||
#ifdef ESP32
|
|
||||||
xSemaphoreGiveRecursive(_accessMutex);
|
void read(JsonObject& jsonObject, JsonStateReader<T> stateReader) {
|
||||||
#endif
|
beginTransaction();
|
||||||
|
stateReader(_state, jsonObject);
|
||||||
|
endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
@ -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:
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user