External config
Allow config to be accessed from outside the framework core code.
This commit is contained in:
parent
0ca9530afa
commit
39a86b0411
75
README.md
75
README.md
@ -349,10 +349,22 @@ Alternatively you can extend [AdminSettingsService.h](lib/framework/AdminSetting
|
|||||||
|
|
||||||
## Extending the framework
|
## Extending the framework
|
||||||
|
|
||||||
|
It is recommend that you explore the framework code to gain a better understanding of how to use it's features. The framework provides APIs so you can add your own services or features or, if required, directly configure or observe changes to core framework features. Some of these capabilities are detailed below.
|
||||||
|
|
||||||
|
### Adding a service with persistant settings
|
||||||
|
|
||||||
|
The following code demonstrates how you might extend the framework with a feature which requires a username and password to be configured to drive an unspecified feature.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <SettingsService.h>
|
#include <SettingsService.h>
|
||||||
|
|
||||||
class ExampleSettingsService : public SettingsService {
|
class ExampleSettings {
|
||||||
|
public:
|
||||||
|
String username;
|
||||||
|
String password;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ExampleSettingsService : public SettingsService<ExampleSettings> {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@ -364,20 +376,15 @@ class ExampleSettingsService : public SettingsService {
|
|||||||
protected:
|
protected:
|
||||||
|
|
||||||
void readFromJsonObject(JsonObject& root) {
|
void readFromJsonObject(JsonObject& root) {
|
||||||
_username = root["username"] | "";
|
_settings.username = root["username"] | "";
|
||||||
_password = root["password"] | "";
|
_settings.password = root["password"] | "";
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeToJsonObject(JsonObject& root) {
|
void writeToJsonObject(JsonObject& root) {
|
||||||
root["username"] = _username;
|
root["username"] = _settings.username;
|
||||||
root["password"] = _password;
|
root["password"] = _settings.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
String _username;
|
|
||||||
String _password;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -391,7 +398,7 @@ exampleSettingsService.begin();
|
|||||||
|
|
||||||
There will now be a REST service exposed on "/exampleSettings" for reading and writing (GET/POST) the settings. Any modifications will be persisted in SPIFFS, in this case to "/config/exampleSettings.json"
|
There will now be a REST service exposed on "/exampleSettings" for reading and writing (GET/POST) the settings. Any modifications will be persisted in SPIFFS, in this case to "/config/exampleSettings.json"
|
||||||
|
|
||||||
Sometimes you need to perform an action when the settings are updated, you can achieve this by overriding the onConfigUpdated() function which gets called every time the settings are updated. You can also perform an action when the service starts by overriding the begin() function, being sure to call SettingsService::begin():
|
Sometimes you need to perform an action when the settings are updated, you can achieve this by overriding the onConfigUpdated() function which gets called every time the settings are updated. You can also perform an action when the service starts by overriding the begin() function, being sure to call SettingsService::begin(). You can also provide a "loop" function in order to allow your service class continuously perform an action, calling this from the main loop.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
|
||||||
@ -409,6 +416,50 @@ void reconfigureTheService() {
|
|||||||
// do whatever is required to react to the new settings
|
// do whatever is required to react to the new settings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// execute somthing as part of the main loop
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing settings and services
|
||||||
|
|
||||||
|
The framework supplies access to it's SettingsService instances and the SecurityManager via getter functions:
|
||||||
|
|
||||||
|
SettingsService | Description
|
||||||
|
---------------------------- | ----------------------------------------------
|
||||||
|
getSecurityManager() | The security manager - detailed above
|
||||||
|
getSecuritySettingsService() | Configures the users and other security settings
|
||||||
|
getWiFiSettingsService() | Configures and manages the WiFi network connection
|
||||||
|
getAPSettingsService() | Configures and manages the Access Point
|
||||||
|
getNTPSettingsService() | Configures and manages the network time
|
||||||
|
getOTASettingsService() | Configures and manages the Over-The-Air update feature
|
||||||
|
|
||||||
|
These can be used to observe changes to settings. They can also be used to fetch or update settings directly via objects, JSON strings and JsonObjects. Here are some examples of how you may use this.
|
||||||
|
|
||||||
|
Inspect the current WiFi settings:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
WiFiSettings wifiSettings = esp8266React.getWiFiSettingsService()->fetch();
|
||||||
|
Serial.print("The ssid is:");
|
||||||
|
Serial.println(wifiSettings.ssid);
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure the SSID and password:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
WiFiSettings wifiSettings = esp8266React->getWiFiSettingsService()->fetch();
|
||||||
|
wifiSettings.ssid = "MyNetworkSSID";
|
||||||
|
wifiSettings.password = "MySuperSecretPassword";
|
||||||
|
esp8266React.getWiFiSettingsService()->update(wifiSettings);
|
||||||
|
```
|
||||||
|
|
||||||
|
Observe changes to the WiFiSettings:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
esp8266React.getWiFiSettingsService()->addUpdateHandler([]() {
|
||||||
|
Serial.println("The WiFi Settings were updated!");
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Libraries Used
|
## Libraries Used
|
||||||
@ -416,7 +467,5 @@ void reconfigureTheService() {
|
|||||||
* [React](https://reactjs.org/)
|
* [React](https://reactjs.org/)
|
||||||
* [Material-UI](https://material-ui-next.com/)
|
* [Material-UI](https://material-ui-next.com/)
|
||||||
* [notistack](https://github.com/iamhosseindhv/notistack)
|
* [notistack](https://github.com/iamhosseindhv/notistack)
|
||||||
* [Time](https://github.com/PaulStoffregen/Time)
|
|
||||||
* [NtpClient](https://github.com/gmag11/NtpClient)
|
|
||||||
* [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
|
* [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
|
||||||
* [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
|
* [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
|
||||||
|
@ -9,7 +9,11 @@ APSettingsService::~APSettingsService() {
|
|||||||
|
|
||||||
void APSettingsService::begin() {
|
void APSettingsService::begin() {
|
||||||
SettingsService::begin();
|
SettingsService::begin();
|
||||||
onConfigUpdated();
|
reconfigureAP();
|
||||||
|
}
|
||||||
|
|
||||||
|
void APSettingsService::reconfigureAP() {
|
||||||
|
_lastManaged = millis() - MANAGE_NETWORK_DELAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
void APSettingsService::loop() {
|
void APSettingsService::loop() {
|
||||||
@ -24,7 +28,8 @@ void APSettingsService::loop() {
|
|||||||
|
|
||||||
void APSettingsService::manageAP() {
|
void APSettingsService::manageAP() {
|
||||||
WiFiMode_t currentWiFiMode = WiFi.getMode();
|
WiFiMode_t currentWiFiMode = WiFi.getMode();
|
||||||
if (_provisionMode == AP_MODE_ALWAYS || (_provisionMode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED)) {
|
if (_settings.provisionMode == AP_MODE_ALWAYS ||
|
||||||
|
(_settings.provisionMode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED)) {
|
||||||
if (currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
|
if (currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
|
||||||
startAP();
|
startAP();
|
||||||
}
|
}
|
||||||
@ -37,7 +42,7 @@ void APSettingsService::manageAP() {
|
|||||||
|
|
||||||
void APSettingsService::startAP() {
|
void APSettingsService::startAP() {
|
||||||
Serial.println("Starting software access point");
|
Serial.println("Starting software access point");
|
||||||
WiFi.softAP(_ssid.c_str(), _password.c_str());
|
WiFi.softAP(_settings.ssid.c_str(), _settings.password.c_str());
|
||||||
if (!_dnsServer) {
|
if (!_dnsServer) {
|
||||||
IPAddress apIp = WiFi.softAPIP();
|
IPAddress apIp = WiFi.softAPIP();
|
||||||
Serial.print("Starting captive portal on ");
|
Serial.print("Starting captive portal on ");
|
||||||
@ -65,25 +70,25 @@ void APSettingsService::handleDNS() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APSettingsService::readFromJsonObject(JsonObject& root) {
|
void APSettingsService::readFromJsonObject(JsonObject& root) {
|
||||||
_provisionMode = root["provision_mode"] | AP_MODE_ALWAYS;
|
_settings.provisionMode = root["provision_mode"] | AP_MODE_ALWAYS;
|
||||||
switch (_provisionMode) {
|
switch (_settings.provisionMode) {
|
||||||
case AP_MODE_ALWAYS:
|
case AP_MODE_ALWAYS:
|
||||||
case AP_MODE_DISCONNECTED:
|
case AP_MODE_DISCONNECTED:
|
||||||
case AP_MODE_NEVER:
|
case AP_MODE_NEVER:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
_provisionMode = AP_MODE_ALWAYS;
|
_settings.provisionMode = AP_MODE_ALWAYS;
|
||||||
}
|
}
|
||||||
_ssid = root["ssid"] | AP_DEFAULT_SSID;
|
_settings.ssid = root["ssid"] | AP_DEFAULT_SSID;
|
||||||
_password = root["password"] | AP_DEFAULT_PASSWORD;
|
_settings.password = root["password"] | AP_DEFAULT_PASSWORD;
|
||||||
}
|
}
|
||||||
|
|
||||||
void APSettingsService::writeToJsonObject(JsonObject& root) {
|
void APSettingsService::writeToJsonObject(JsonObject& root) {
|
||||||
root["provision_mode"] = _provisionMode;
|
root["provision_mode"] = _settings.provisionMode;
|
||||||
root["ssid"] = _ssid;
|
root["ssid"] = _settings.ssid;
|
||||||
root["password"] = _password;
|
root["password"] = _settings.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
void APSettingsService::onConfigUpdated() {
|
void APSettingsService::onConfigUpdated() {
|
||||||
_lastManaged = millis() - MANAGE_NETWORK_DELAY;
|
reconfigureAP();
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,14 @@
|
|||||||
#define AP_SETTINGS_FILE "/config/apSettings.json"
|
#define AP_SETTINGS_FILE "/config/apSettings.json"
|
||||||
#define AP_SETTINGS_SERVICE_PATH "/rest/apSettings"
|
#define AP_SETTINGS_SERVICE_PATH "/rest/apSettings"
|
||||||
|
|
||||||
class APSettingsService : public AdminSettingsService {
|
class APSettings {
|
||||||
|
public:
|
||||||
|
uint8_t provisionMode;
|
||||||
|
String ssid;
|
||||||
|
String password;
|
||||||
|
};
|
||||||
|
|
||||||
|
class APSettingsService : public AdminSettingsService<APSettings> {
|
||||||
public:
|
public:
|
||||||
APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
||||||
~APSettingsService();
|
~APSettingsService();
|
||||||
@ -33,17 +40,13 @@ class APSettingsService : public AdminSettingsService {
|
|||||||
void onConfigUpdated();
|
void onConfigUpdated();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// access point settings
|
|
||||||
uint8_t _provisionMode;
|
|
||||||
String _ssid;
|
|
||||||
String _password;
|
|
||||||
|
|
||||||
// for the mangement delay loop
|
// for the mangement delay loop
|
||||||
unsigned long _lastManaged;
|
unsigned long _lastManaged;
|
||||||
|
|
||||||
// for the captive portal
|
// for the captive portal
|
||||||
DNSServer* _dnsServer;
|
DNSServer* _dnsServer;
|
||||||
|
|
||||||
|
void reconfigureAP();
|
||||||
void manageAP();
|
void manageAP();
|
||||||
void startAP();
|
void startAP();
|
||||||
void stopAP();
|
void stopAP();
|
||||||
|
@ -3,14 +3,15 @@
|
|||||||
|
|
||||||
#include <SettingsService.h>
|
#include <SettingsService.h>
|
||||||
|
|
||||||
class AdminSettingsService : public SettingsService {
|
template <class T>
|
||||||
|
class AdminSettingsService : public SettingsService<T> {
|
||||||
public:
|
public:
|
||||||
AdminSettingsService(AsyncWebServer* server,
|
AdminSettingsService(AsyncWebServer* server,
|
||||||
FS* fs,
|
FS* fs,
|
||||||
SecurityManager* securityManager,
|
SecurityManager* securityManager,
|
||||||
char const* servicePath,
|
char const* servicePath,
|
||||||
char const* filePath) :
|
char const* filePath) :
|
||||||
SettingsService(server, fs, servicePath, filePath),
|
SettingsService<T>(server, fs, servicePath, filePath),
|
||||||
_securityManager(securityManager) {
|
_securityManager(securityManager) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ class AdminSettingsService : public SettingsService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// delegate to underlying implemetation
|
// delegate to underlying implemetation
|
||||||
SettingsService::fetchConfig(request);
|
SettingsService<T>::fetchConfig(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateConfig(AsyncWebServerRequest* request, JsonDocument& jsonDocument) {
|
void updateConfig(AsyncWebServerRequest* request, JsonDocument& jsonDocument) {
|
||||||
@ -37,7 +38,7 @@ class AdminSettingsService : public SettingsService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// delegate to underlying implemetation
|
// delegate to underlying implemetation
|
||||||
SettingsService::updateConfig(request, jsonDocument);
|
SettingsService<T>::updateConfig(request, jsonDocument);
|
||||||
}
|
}
|
||||||
|
|
||||||
// override this to replace the default authentication predicate, IS_ADMIN
|
// override this to replace the default authentication predicate, IS_ADMIN
|
||||||
|
@ -21,7 +21,7 @@ AuthenticationService::~AuthenticationService() {
|
|||||||
*/
|
*/
|
||||||
void AuthenticationService::verifyAuthorization(AsyncWebServerRequest* request) {
|
void AuthenticationService::verifyAuthorization(AsyncWebServerRequest* request) {
|
||||||
Authentication authentication = _securityManager->authenticateRequest(request);
|
Authentication authentication = _securityManager->authenticateRequest(request);
|
||||||
request->send(authentication.isAuthenticated() ? 200 : 401);
|
request->send(authentication.authenticated ? 200 : 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,8 +33,8 @@ void AuthenticationService::signIn(AsyncWebServerRequest* request, JsonDocument&
|
|||||||
String username = jsonDocument["username"];
|
String username = jsonDocument["username"];
|
||||||
String password = jsonDocument["password"];
|
String password = jsonDocument["password"];
|
||||||
Authentication authentication = _securityManager->authenticate(username, password);
|
Authentication authentication = _securityManager->authenticate(username, password);
|
||||||
if (authentication.isAuthenticated()) {
|
if (authentication.authenticated) {
|
||||||
User* user = authentication.getUser();
|
User* user = authentication.user;
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_AUTHENTICATION_SIZE);
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_AUTHENTICATION_SIZE);
|
||||||
JsonObject jsonObject = response->getRoot();
|
JsonObject jsonObject = response->getRoot();
|
||||||
jsonObject["access_token"] = _securityManager->generateJWT(user);
|
jsonObject["access_token"] = _securityManager->generateJWT(user);
|
||||||
|
@ -41,14 +41,34 @@ class ESP8266React {
|
|||||||
return &_securitySettingsService;
|
return &_securitySettingsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsService<SecuritySettings>* getSecuritySettingsService() {
|
||||||
|
return &_securitySettingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsService<WiFiSettings>* getWiFiSettingsService() {
|
||||||
|
return &_wifiSettingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsService<APSettings>* getAPSettingsService() {
|
||||||
|
return &_apSettingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsService<NTPSettings>* getNTPSettingsService() {
|
||||||
|
return &_ntpSettingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsService<OTASettings>* getOTASettingsService() {
|
||||||
|
return &_otaSettingsService;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SecuritySettingsService _securitySettingsService;
|
SecuritySettingsService _securitySettingsService;
|
||||||
|
|
||||||
WiFiSettingsService _wifiSettingsService;
|
WiFiSettingsService _wifiSettingsService;
|
||||||
APSettingsService _apSettingsService;
|
APSettingsService _apSettingsService;
|
||||||
NTPSettingsService _ntpSettingsService;
|
NTPSettingsService _ntpSettingsService;
|
||||||
OTASettingsService _otaSettingsService;
|
OTASettingsService _otaSettingsService;
|
||||||
RestartService _restartService;
|
RestartService _restartService;
|
||||||
|
|
||||||
AuthenticationService _authenticationService;
|
AuthenticationService _authenticationService;
|
||||||
|
|
||||||
WiFiScanner _wifiScanner;
|
WiFiScanner _wifiScanner;
|
||||||
@ -56,7 +76,6 @@ class ESP8266React {
|
|||||||
NTPStatus _ntpStatus;
|
NTPStatus _ntpStatus;
|
||||||
APStatus _apStatus;
|
APStatus _apStatus;
|
||||||
SystemStatus _systemStatus;
|
SystemStatus _systemStatus;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -28,17 +28,17 @@ void NTPSettingsService::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NTPSettingsService::readFromJsonObject(JsonObject& root) {
|
void NTPSettingsService::readFromJsonObject(JsonObject& root) {
|
||||||
_enabled = root["enabled"] | NTP_SETTINGS_SERVICE_DEFAULT_ENABLED;
|
_settings.enabled = root["enabled"] | NTP_SETTINGS_SERVICE_DEFAULT_ENABLED;
|
||||||
_server = root["server"] | NTP_SETTINGS_SERVICE_DEFAULT_SERVER;
|
_settings.server = root["server"] | NTP_SETTINGS_SERVICE_DEFAULT_SERVER;
|
||||||
_tzLabel = root["tz_label"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_LABEL;
|
_settings.tzLabel = root["tz_label"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_LABEL;
|
||||||
_tzFormat = root["tz_format"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_FORMAT;
|
_settings.tzFormat = root["tz_format"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NTPSettingsService::writeToJsonObject(JsonObject& root) {
|
void NTPSettingsService::writeToJsonObject(JsonObject& root) {
|
||||||
root["enabled"] = _enabled;
|
root["enabled"] = _settings.enabled;
|
||||||
root["server"] = _server;
|
root["server"] = _settings.server;
|
||||||
root["tz_label"] = _tzLabel;
|
root["tz_label"] = _settings.tzLabel;
|
||||||
root["tz_format"] = _tzFormat;
|
root["tz_format"] = _settings.tzFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NTPSettingsService::onConfigUpdated() {
|
void NTPSettingsService::onConfigUpdated() {
|
||||||
@ -47,23 +47,23 @@ void NTPSettingsService::onConfigUpdated() {
|
|||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
void NTPSettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
|
void NTPSettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||||
Serial.printf("Got IP address, starting NTP Synchronization\n");
|
Serial.println("Got IP address, starting NTP Synchronization");
|
||||||
_reconfigureNTP = true;
|
_reconfigureNTP = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NTPSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
|
void NTPSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||||
Serial.printf("WiFi connection dropped, stopping NTP.\n");
|
Serial.println("WiFi connection dropped, stopping NTP.");
|
||||||
_reconfigureNTP = false;
|
_reconfigureNTP = false;
|
||||||
sntp_stop();
|
sntp_stop();
|
||||||
}
|
}
|
||||||
#elif defined(ESP8266)
|
#elif defined(ESP8266)
|
||||||
void NTPSettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
|
void NTPSettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
|
||||||
Serial.printf("Got IP address, starting NTP Synchronization\n");
|
Serial.println("Got IP address, starting NTP Synchronization");
|
||||||
_reconfigureNTP = true;
|
_reconfigureNTP = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NTPSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
|
void NTPSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
|
||||||
Serial.printf("WiFi connection dropped, stopping NTP.\n");
|
Serial.println("WiFi connection dropped, stopping NTP.");
|
||||||
_reconfigureNTP = false;
|
_reconfigureNTP = false;
|
||||||
sntp_stop();
|
sntp_stop();
|
||||||
}
|
}
|
||||||
@ -71,13 +71,13 @@ void NTPSettingsService::onStationModeDisconnected(const WiFiEventStationModeDis
|
|||||||
|
|
||||||
void NTPSettingsService::configureNTP() {
|
void NTPSettingsService::configureNTP() {
|
||||||
Serial.println("Configuring NTP...");
|
Serial.println("Configuring NTP...");
|
||||||
if (_enabled) {
|
if (_settings.enabled) {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
configTzTime(_tzFormat.c_str(), _server.c_str());
|
configTzTime(_settings.tzFormat.c_str(), _settings.server.c_str());
|
||||||
#elif defined(ESP8266)
|
#elif defined(ESP8266)
|
||||||
configTime(_tzFormat.c_str(), _server.c_str());
|
configTime(_settings.tzFormat.c_str(), _settings.server.c_str());
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
sntp_stop();
|
sntp_stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,15 @@
|
|||||||
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
|
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
|
||||||
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
|
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
|
||||||
|
|
||||||
class NTPSettingsService : public AdminSettingsService {
|
class NTPSettings {
|
||||||
|
public:
|
||||||
|
bool enabled;
|
||||||
|
String tzLabel;
|
||||||
|
String tzFormat;
|
||||||
|
String server;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NTPSettingsService : public AdminSettingsService<NTPSettings> {
|
||||||
public:
|
public:
|
||||||
NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
||||||
~NTPSettingsService();
|
~NTPSettingsService();
|
||||||
@ -37,11 +45,6 @@ class NTPSettingsService : public AdminSettingsService {
|
|||||||
void receivedNTPtime();
|
void receivedNTPtime();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _enabled;
|
|
||||||
String _tzLabel;
|
|
||||||
String _tzFormat;
|
|
||||||
String _server;
|
|
||||||
|
|
||||||
bool _reconfigureNTP = false;
|
bool _reconfigureNTP = false;
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
|
@ -15,7 +15,7 @@ OTASettingsService::~OTASettingsService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OTASettingsService::loop() {
|
void OTASettingsService::loop() {
|
||||||
if (_enabled && _arduinoOTA) {
|
if ( _settings.enabled && _arduinoOTA) {
|
||||||
_arduinoOTA->handle();
|
_arduinoOTA->handle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,15 +25,15 @@ void OTASettingsService::onConfigUpdated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OTASettingsService::readFromJsonObject(JsonObject& root) {
|
void OTASettingsService::readFromJsonObject(JsonObject& root) {
|
||||||
_enabled = root["enabled"] | DEFAULT_OTA_ENABLED;
|
_settings.enabled = root["enabled"] | DEFAULT_OTA_ENABLED;
|
||||||
_port = root["port"] | DEFAULT_OTA_PORT;
|
_settings.port = root["port"] | DEFAULT_OTA_PORT;
|
||||||
_password = root["password"] | DEFAULT_OTA_PASSWORD;
|
_settings.password = root["password"] | DEFAULT_OTA_PASSWORD;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OTASettingsService::writeToJsonObject(JsonObject& root) {
|
void OTASettingsService::writeToJsonObject(JsonObject& root) {
|
||||||
root["enabled"] = _enabled;
|
root["enabled"] = _settings.enabled;
|
||||||
root["port"] = _port;
|
root["port"] = _settings.port;
|
||||||
root["password"] = _password;
|
root["password"] = _settings.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OTASettingsService::configureArduinoOTA() {
|
void OTASettingsService::configureArduinoOTA() {
|
||||||
@ -44,11 +44,11 @@ void OTASettingsService::configureArduinoOTA() {
|
|||||||
delete _arduinoOTA;
|
delete _arduinoOTA;
|
||||||
_arduinoOTA = nullptr;
|
_arduinoOTA = nullptr;
|
||||||
}
|
}
|
||||||
if (_enabled) {
|
if (_settings.enabled) {
|
||||||
Serial.println("Starting OTA Update Service");
|
Serial.println("Starting OTA Update Service");
|
||||||
_arduinoOTA = new ArduinoOTAClass;
|
_arduinoOTA = new ArduinoOTAClass;
|
||||||
_arduinoOTA->setPort(_port);
|
_arduinoOTA->setPort(_settings.port);
|
||||||
_arduinoOTA->setPassword(_password.c_str());
|
_arduinoOTA->setPassword(_settings.password.c_str());
|
||||||
_arduinoOTA->onStart([]() { Serial.println("Starting"); });
|
_arduinoOTA->onStart([]() { Serial.println("Starting"); });
|
||||||
_arduinoOTA->onEnd([]() { Serial.println("\nEnd"); });
|
_arduinoOTA->onEnd([]() { Serial.println("\nEnd"); });
|
||||||
_arduinoOTA->onProgress([](unsigned int progress, unsigned int total) {
|
_arduinoOTA->onProgress([](unsigned int progress, unsigned int total) {
|
||||||
|
@ -20,7 +20,14 @@
|
|||||||
#define OTA_SETTINGS_FILE "/config/otaSettings.json"
|
#define OTA_SETTINGS_FILE "/config/otaSettings.json"
|
||||||
#define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings"
|
#define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings"
|
||||||
|
|
||||||
class OTASettingsService : public AdminSettingsService {
|
class OTASettings {
|
||||||
|
public:
|
||||||
|
bool enabled;
|
||||||
|
int port;
|
||||||
|
String password;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OTASettingsService : public AdminSettingsService<OTASettings> {
|
||||||
public:
|
public:
|
||||||
OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
||||||
~OTASettingsService();
|
~OTASettingsService();
|
||||||
@ -34,9 +41,6 @@ class OTASettingsService : public AdminSettingsService {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
ArduinoOTAClass* _arduinoOTA;
|
ArduinoOTAClass* _arduinoOTA;
|
||||||
bool _enabled;
|
|
||||||
int _port;
|
|
||||||
String _password;
|
|
||||||
|
|
||||||
void configureArduinoOTA();
|
void configureArduinoOTA();
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
#include <SecurityManager.h>
|
|
||||||
|
|
||||||
Authentication SecurityManager::authenticateRequest(AsyncWebServerRequest *request) {
|
|
||||||
AsyncWebHeader *authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
|
|
||||||
if (authorizationHeader) {
|
|
||||||
String value = authorizationHeader->value();
|
|
||||||
if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
|
|
||||||
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
|
|
||||||
return authenticateJWT(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Authentication();
|
|
||||||
}
|
|
||||||
|
|
||||||
Authentication SecurityManager::authenticateJWT(String jwt) {
|
|
||||||
DynamicJsonDocument payloadDocument(MAX_JWT_SIZE);
|
|
||||||
_jwtHandler.parseJWT(jwt, payloadDocument);
|
|
||||||
if (payloadDocument.is<JsonObject>()) {
|
|
||||||
JsonObject parsedPayload = payloadDocument.as<JsonObject>();
|
|
||||||
String username = parsedPayload["username"];
|
|
||||||
for (User _user : _users) {
|
|
||||||
if (_user.getUsername() == username && validatePayload(parsedPayload, &_user)) {
|
|
||||||
return Authentication(_user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Authentication();
|
|
||||||
}
|
|
||||||
|
|
||||||
Authentication SecurityManager::authenticate(String username, String password) {
|
|
||||||
for (User _user : _users) {
|
|
||||||
if (_user.getUsername() == username && _user.getPassword() == password) {
|
|
||||||
return Authentication(_user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Authentication();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void populateJWTPayload(JsonObject &payload, User *user) {
|
|
||||||
payload["username"] = user->getUsername();
|
|
||||||
payload["admin"] = user->isAdmin();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean SecurityManager::validatePayload(JsonObject &parsedPayload, User *user) {
|
|
||||||
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
|
||||||
JsonObject payload = _jsonDocument.to<JsonObject>();
|
|
||||||
populateJWTPayload(payload, user);
|
|
||||||
return payload == parsedPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
String SecurityManager::generateJWT(User *user) {
|
|
||||||
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
|
||||||
JsonObject payload = _jsonDocument.to<JsonObject>();
|
|
||||||
populateJWTPayload(payload, user);
|
|
||||||
return _jwtHandler.buildJWT(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArRequestHandlerFunction SecurityManager::wrapRequest(ArRequestHandlerFunction onRequest,
|
|
||||||
AuthenticationPredicate predicate) {
|
|
||||||
return [this, onRequest, predicate](AsyncWebServerRequest *request) {
|
|
||||||
Authentication authentication = authenticateRequest(request);
|
|
||||||
if (!predicate(authentication)) {
|
|
||||||
request->send(401);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onRequest(request);
|
|
||||||
};
|
|
||||||
}
|
|
@ -14,43 +14,28 @@
|
|||||||
#define MAX_JWT_SIZE 128
|
#define MAX_JWT_SIZE 128
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
private:
|
public:
|
||||||
String _username;
|
String username;
|
||||||
String _password;
|
String password;
|
||||||
bool _admin;
|
bool admin;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
User(String username, String password, bool admin) : _username(username), _password(password), _admin(admin) {
|
User(String username, String password, bool admin) : username(username), password(password), admin(admin) {
|
||||||
}
|
|
||||||
String getUsername() {
|
|
||||||
return _username;
|
|
||||||
}
|
|
||||||
String getPassword() {
|
|
||||||
return _password;
|
|
||||||
}
|
|
||||||
bool isAdmin() {
|
|
||||||
return _admin;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Authentication {
|
class Authentication {
|
||||||
private:
|
public:
|
||||||
User* _user;
|
User* user;
|
||||||
boolean _authenticated;
|
boolean authenticated;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Authentication(User& user) : _user(new User(user)), _authenticated(true) {
|
Authentication(User& user) : user(new User(user)), authenticated(true) {
|
||||||
}
|
}
|
||||||
Authentication() : _user(nullptr), _authenticated(false) {
|
Authentication() : user(nullptr), authenticated(false) {
|
||||||
}
|
}
|
||||||
~Authentication() {
|
~Authentication() {
|
||||||
delete (_user);
|
delete (user);
|
||||||
}
|
|
||||||
User* getUser() {
|
|
||||||
return _user;
|
|
||||||
}
|
|
||||||
bool isAuthenticated() {
|
|
||||||
return _authenticated;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -62,10 +47,10 @@ class AuthenticationPredicates {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
static bool IS_AUTHENTICATED(Authentication& authentication) {
|
static bool IS_AUTHENTICATED(Authentication& authentication) {
|
||||||
return authentication.isAuthenticated();
|
return authentication.authenticated;
|
||||||
};
|
};
|
||||||
static bool IS_ADMIN(Authentication& authentication) {
|
static bool IS_ADMIN(Authentication& authentication) {
|
||||||
return authentication.isAuthenticated() && authentication.getUser()->isAdmin();
|
return authentication.authenticated && authentication.user->admin;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -74,37 +59,23 @@ class SecurityManager {
|
|||||||
/*
|
/*
|
||||||
* Authenticate, returning the user if found
|
* Authenticate, returning the user if found
|
||||||
*/
|
*/
|
||||||
Authentication authenticate(String username, String password);
|
virtual Authentication authenticate(String username, String password) = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check the request header for the Authorization token
|
* Check the request header for the Authorization token
|
||||||
*/
|
*/
|
||||||
Authentication authenticateRequest(AsyncWebServerRequest* request);
|
virtual Authentication authenticateRequest(AsyncWebServerRequest* request) = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Generate a JWT for the user provided
|
* Generate a JWT for the user provided
|
||||||
*/
|
*/
|
||||||
String generateJWT(User* user);
|
virtual String generateJWT(User* user) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap the provided request to provide validation against an AuthenticationPredicate.
|
* Wrap the provided request to provide validation against an AuthenticationPredicate.
|
||||||
*/
|
*/
|
||||||
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
|
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest,
|
||||||
|
AuthenticationPredicate predicate) = 0;
|
||||||
protected:
|
|
||||||
ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET);
|
|
||||||
std::list<User> _users;
|
|
||||||
|
|
||||||
private:
|
|
||||||
/*
|
|
||||||
* Lookup the user by JWT
|
|
||||||
*/
|
|
||||||
Authentication authenticateJWT(String jwt);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Verify the payload is correct
|
|
||||||
*/
|
|
||||||
boolean validatePayload(JsonObject& parsedPayload, User* user);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // end SecurityManager_h
|
#endif // end SecurityManager_h
|
@ -12,14 +12,14 @@ void SecuritySettingsService::readFromJsonObject(JsonObject& root) {
|
|||||||
_jwtHandler.setSecret(root["jwt_secret"] | DEFAULT_JWT_SECRET);
|
_jwtHandler.setSecret(root["jwt_secret"] | DEFAULT_JWT_SECRET);
|
||||||
|
|
||||||
// users
|
// users
|
||||||
_users.clear();
|
_settings.users.clear();
|
||||||
if (root["users"].is<JsonArray>()) {
|
if (root["users"].is<JsonArray>()) {
|
||||||
for (JsonVariant user : root["users"].as<JsonArray>()) {
|
for (JsonVariant user : root["users"].as<JsonArray>()) {
|
||||||
_users.push_back(User(user["username"], user["password"], user["admin"]));
|
_settings.users.push_back(User(user["username"], user["password"], user["admin"]));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_users.push_back(User(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_USERNAME, true));
|
_settings.users.push_back(User(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_USERNAME, true));
|
||||||
_users.push_back(User(DEFAULT_GUEST_USERNAME, DEFAULT_GUEST_USERNAME, false));
|
_settings.users.push_back(User(DEFAULT_GUEST_USERNAME, DEFAULT_GUEST_USERNAME, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,10 +29,78 @@ void SecuritySettingsService::writeToJsonObject(JsonObject& root) {
|
|||||||
|
|
||||||
// users
|
// users
|
||||||
JsonArray users = root.createNestedArray("users");
|
JsonArray users = root.createNestedArray("users");
|
||||||
for (User _user : _users) {
|
for (User _user : _settings.users) {
|
||||||
JsonObject user = users.createNestedObject();
|
JsonObject user = users.createNestedObject();
|
||||||
user["username"] = _user.getUsername();
|
user["username"] = _user.username;
|
||||||
user["password"] = _user.getPassword();
|
user["password"] = _user.password;
|
||||||
user["admin"] = _user.isAdmin();
|
user["admin"] = _user.admin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest *request) {
|
||||||
|
AsyncWebHeader *authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
|
||||||
|
if (authorizationHeader) {
|
||||||
|
String value = authorizationHeader->value();
|
||||||
|
if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
|
||||||
|
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
|
||||||
|
return authenticateJWT(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Authentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication SecuritySettingsService::authenticateJWT(String jwt) {
|
||||||
|
DynamicJsonDocument payloadDocument(MAX_JWT_SIZE);
|
||||||
|
_jwtHandler.parseJWT(jwt, payloadDocument);
|
||||||
|
if (payloadDocument.is<JsonObject>()) {
|
||||||
|
JsonObject parsedPayload = payloadDocument.as<JsonObject>();
|
||||||
|
String username = parsedPayload["username"];
|
||||||
|
for (User _user : _settings.users) {
|
||||||
|
if (_user.username == username && validatePayload(parsedPayload, &_user)) {
|
||||||
|
return Authentication(_user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Authentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication SecuritySettingsService::authenticate(String username, String password) {
|
||||||
|
for (User _user : _settings.users) {
|
||||||
|
if (_user.username == username && _user.password == password) {
|
||||||
|
return Authentication(_user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Authentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void populateJWTPayload(JsonObject &payload, User *user) {
|
||||||
|
payload["username"] = user->username;
|
||||||
|
payload["admin"] = user->admin;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean SecuritySettingsService::validatePayload(JsonObject &parsedPayload, User *user) {
|
||||||
|
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
||||||
|
JsonObject payload = _jsonDocument.to<JsonObject>();
|
||||||
|
populateJWTPayload(payload, user);
|
||||||
|
return payload == parsedPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
String SecuritySettingsService::generateJWT(User *user) {
|
||||||
|
DynamicJsonDocument _jsonDocument(MAX_JWT_SIZE);
|
||||||
|
JsonObject payload = _jsonDocument.to<JsonObject>();
|
||||||
|
populateJWTPayload(payload, user);
|
||||||
|
return _jwtHandler.buildJWT(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest,
|
||||||
|
AuthenticationPredicate predicate) {
|
||||||
|
return [this, onRequest, predicate](AsyncWebServerRequest *request) {
|
||||||
|
Authentication authentication = authenticateRequest(request);
|
||||||
|
if (!predicate(authentication)) {
|
||||||
|
request->send(401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onRequest(request);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -10,14 +10,39 @@
|
|||||||
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
|
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
|
||||||
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
|
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
|
||||||
|
|
||||||
class SecuritySettingsService : public AdminSettingsService, public SecurityManager {
|
class SecuritySettings {
|
||||||
|
public:
|
||||||
|
String jwtSecret;
|
||||||
|
std::list<User> users;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SecuritySettingsService : public AdminSettingsService<SecuritySettings>, public SecurityManager {
|
||||||
public:
|
public:
|
||||||
SecuritySettingsService(AsyncWebServer* server, FS* fs);
|
SecuritySettingsService(AsyncWebServer* server, FS* fs);
|
||||||
~SecuritySettingsService();
|
~SecuritySettingsService();
|
||||||
|
|
||||||
|
// Functions to implement SecurityManager
|
||||||
|
Authentication authenticate(String username, String password);
|
||||||
|
Authentication authenticateRequest(AsyncWebServerRequest* request);
|
||||||
|
String generateJWT(User* user);
|
||||||
|
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void readFromJsonObject(JsonObject& root);
|
void readFromJsonObject(JsonObject& root);
|
||||||
void writeToJsonObject(JsonObject& root);
|
void writeToJsonObject(JsonObject& root);
|
||||||
|
|
||||||
|
private:
|
||||||
|
ArduinoJsonJWT _jwtHandler = ArduinoJsonJWT(DEFAULT_JWT_SECRET);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lookup the user by JWT
|
||||||
|
*/
|
||||||
|
Authentication authenticateJWT(String jwt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verify the payload is correct
|
||||||
|
*/
|
||||||
|
boolean validatePayload(JsonObject& parsedPayload, User* user);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // end SecuritySettingsService_h
|
#endif // end SecuritySettingsService_h
|
@ -1,6 +1,8 @@
|
|||||||
#ifndef SettingsService_h
|
#ifndef SettingsService_h
|
||||||
#define SettingsService_h
|
#define SettingsService_h
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <AsyncTCP.h>
|
#include <AsyncTCP.h>
|
||||||
@ -17,9 +19,24 @@
|
|||||||
#include <SecurityManager.h>
|
#include <SecurityManager.h>
|
||||||
#include <SettingsPersistence.h>
|
#include <SettingsPersistence.h>
|
||||||
|
|
||||||
|
typedef size_t update_handler_id_t;
|
||||||
|
typedef std::function<void(void)> SettingsUpdateCallback;
|
||||||
|
static update_handler_id_t currentUpdateHandlerId;
|
||||||
|
|
||||||
|
typedef struct SettingsUpdateHandlerInfo {
|
||||||
|
update_handler_id_t _id;
|
||||||
|
SettingsUpdateCallback _cb;
|
||||||
|
bool _allowRemove;
|
||||||
|
SettingsUpdateHandlerInfo(SettingsUpdateCallback cb, bool allowRemove) :
|
||||||
|
_id(++currentUpdateHandlerId),
|
||||||
|
_cb(cb),
|
||||||
|
_allowRemove(allowRemove){};
|
||||||
|
} SettingsUpdateHandlerInfo_t;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Abstraction of a service which stores it's settings as JSON in a file system.
|
* Abstraction of a service which stores it's settings as JSON in a file system.
|
||||||
*/
|
*/
|
||||||
|
template <class T>
|
||||||
class SettingsService : public SettingsPersistence {
|
class SettingsService : public SettingsPersistence {
|
||||||
public:
|
public:
|
||||||
SettingsService(AsyncWebServer* server, FS* fs, char const* servicePath, char const* filePath) :
|
SettingsService(AsyncWebServer* server, FS* fs, char const* servicePath, char const* filePath) :
|
||||||
@ -37,14 +54,71 @@ class SettingsService : public SettingsPersistence {
|
|||||||
virtual ~SettingsService() {
|
virtual ~SettingsService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_handler_id_t addUpdateHandler(SettingsUpdateCallback cb, bool allowRemove = true) {
|
||||||
|
if (!cb) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
SettingsUpdateHandlerInfo_t updateHandler(cb, allowRemove);
|
||||||
|
_settingsUpdateHandlers.push_back(updateHandler);
|
||||||
|
return updateHandler._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeUpdateHandler(update_handler_id_t id) {
|
||||||
|
for (auto i = _settingsUpdateHandlers.begin(); i != _settingsUpdateHandlers.end();) {
|
||||||
|
if ((*i)._id == id) {
|
||||||
|
i = _settingsUpdateHandlers.erase(i);
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T fetch() {
|
||||||
|
return _settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(T& settings) {
|
||||||
|
_settings = settings;
|
||||||
|
writeToFS();
|
||||||
|
callUpdateHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void fetchAsString(String& config) {
|
||||||
|
DynamicJsonDocument jsonDocument(MAX_SETTINGS_SIZE);
|
||||||
|
fetchAsDocument(jsonDocument);
|
||||||
|
serializeJson(jsonDocument, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateFromString(String& config) {
|
||||||
|
DynamicJsonDocument jsonDocument(MAX_SETTINGS_SIZE);
|
||||||
|
deserializeJson(jsonDocument, config);
|
||||||
|
updateFromDocument(jsonDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fetchAsDocument(JsonDocument& jsonDocument) {
|
||||||
|
JsonObject jsonObject = jsonDocument.to<JsonObject>();
|
||||||
|
writeToJsonObject(jsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateFromDocument(JsonDocument& jsonDocument) {
|
||||||
|
if (jsonDocument.is<JsonObject>()) {
|
||||||
|
JsonObject newConfig = jsonDocument.as<JsonObject>();
|
||||||
|
readFromJsonObject(newConfig);
|
||||||
|
writeToFS();
|
||||||
|
callUpdateHandlers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void begin() {
|
void begin() {
|
||||||
// read the initial data from the file system
|
// read the initial data from the file system
|
||||||
readFromFS();
|
readFromFS();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
T _settings;
|
||||||
char const* _servicePath;
|
char const* _servicePath;
|
||||||
AsyncJsonWebHandler _updateHandler;
|
AsyncJsonWebHandler _updateHandler;
|
||||||
|
std::list<SettingsUpdateHandlerInfo_t> _settingsUpdateHandlers;
|
||||||
|
|
||||||
virtual void fetchConfig(AsyncWebServerRequest* request) {
|
virtual void fetchConfig(AsyncWebServerRequest* request) {
|
||||||
// handle the request
|
// handle the request
|
||||||
@ -64,7 +138,7 @@ class SettingsService : public SettingsPersistence {
|
|||||||
|
|
||||||
// write settings back with a callback to reconfigure the wifi
|
// write settings back with a callback to reconfigure the wifi
|
||||||
AsyncJsonCallbackResponse* response =
|
AsyncJsonCallbackResponse* response =
|
||||||
new AsyncJsonCallbackResponse([this]() { onConfigUpdated(); }, false, MAX_SETTINGS_SIZE);
|
new AsyncJsonCallbackResponse([this]() { callUpdateHandlers(); }, false, MAX_SETTINGS_SIZE);
|
||||||
JsonObject jsonObject = response->getRoot();
|
JsonObject jsonObject = response->getRoot();
|
||||||
writeToJsonObject(jsonObject);
|
writeToJsonObject(jsonObject);
|
||||||
response->setLength();
|
response->setLength();
|
||||||
@ -74,6 +148,16 @@ class SettingsService : public SettingsPersistence {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void callUpdateHandlers() {
|
||||||
|
// call the classes own config update function
|
||||||
|
onConfigUpdated();
|
||||||
|
|
||||||
|
// call all setting update handlers
|
||||||
|
for (const SettingsUpdateHandlerInfo_t& handler : _settingsUpdateHandlers) {
|
||||||
|
handler._cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// implement to perform action when config has been updated
|
// implement to perform action when config has been updated
|
||||||
virtual void onConfigUpdated() {
|
virtual void onConfigUpdated() {
|
||||||
}
|
}
|
||||||
|
@ -33,45 +33,45 @@ void WiFiSettingsService::begin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WiFiSettingsService::readFromJsonObject(JsonObject& root) {
|
void WiFiSettingsService::readFromJsonObject(JsonObject& root) {
|
||||||
_ssid = root["ssid"] | "";
|
_settings.ssid = root["ssid"] | "";
|
||||||
_password = root["password"] | "";
|
_settings.password = root["password"] | "";
|
||||||
_hostname = root["hostname"] | "";
|
_settings.hostname = root["hostname"] | "";
|
||||||
_staticIPConfig = root["static_ip_config"] | false;
|
_settings.staticIPConfig = root["static_ip_config"] | false;
|
||||||
|
|
||||||
// extended settings
|
// extended settings
|
||||||
readIP(root, "local_ip", _localIP);
|
readIP(root, "local_ip", _settings.localIP);
|
||||||
readIP(root, "gateway_ip", _gatewayIP);
|
readIP(root, "gateway_ip", _settings.gatewayIP);
|
||||||
readIP(root, "subnet_mask", _subnetMask);
|
readIP(root, "subnet_mask", _settings.subnetMask);
|
||||||
readIP(root, "dns_ip_1", _dnsIP1);
|
readIP(root, "dns_ip_1", _settings.dnsIP1);
|
||||||
readIP(root, "dns_ip_2", _dnsIP2);
|
readIP(root, "dns_ip_2", _settings.dnsIP2);
|
||||||
|
|
||||||
// Swap around the dns servers if 2 is populated but 1 is not
|
// Swap around the dns servers if 2 is populated but 1 is not
|
||||||
if (_dnsIP1 == INADDR_NONE && _dnsIP2 != INADDR_NONE) {
|
if (_settings.dnsIP1 == INADDR_NONE && _settings.dnsIP2 != INADDR_NONE) {
|
||||||
_dnsIP1 = _dnsIP2;
|
_settings.dnsIP1 = _settings.dnsIP2;
|
||||||
_dnsIP2 = INADDR_NONE;
|
_settings.dnsIP2 = INADDR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turning off static ip config if we don't meet the minimum requirements
|
// Turning off static ip config if we don't meet the minimum requirements
|
||||||
// of ipAddress, gateway and subnet. This may change to static ip only
|
// of ipAddress, gateway and subnet. This may change to static ip only
|
||||||
// as sensible defaults can be assumed for gateway and subnet
|
// as sensible defaults can be assumed for gateway and subnet
|
||||||
if (_staticIPConfig && (_localIP == INADDR_NONE || _gatewayIP == INADDR_NONE || _subnetMask == INADDR_NONE)) {
|
if (_settings.staticIPConfig && (_settings.localIP == INADDR_NONE || _settings.gatewayIP == INADDR_NONE || _settings.subnetMask == INADDR_NONE)) {
|
||||||
_staticIPConfig = false;
|
_settings.staticIPConfig = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiSettingsService::writeToJsonObject(JsonObject& root) {
|
void WiFiSettingsService::writeToJsonObject(JsonObject& root) {
|
||||||
// connection settings
|
// connection settings
|
||||||
root["ssid"] = _ssid;
|
root["ssid"] = _settings.ssid;
|
||||||
root["password"] = _password;
|
root["password"] = _settings.password;
|
||||||
root["hostname"] = _hostname;
|
root["hostname"] = _settings.hostname;
|
||||||
root["static_ip_config"] = _staticIPConfig;
|
root["static_ip_config"] = _settings.staticIPConfig;
|
||||||
|
|
||||||
// extended settings
|
// extended settings
|
||||||
writeIP(root, "local_ip", _localIP);
|
writeIP(root, "local_ip", _settings.localIP);
|
||||||
writeIP(root, "gateway_ip", _gatewayIP);
|
writeIP(root, "gateway_ip", _settings.gatewayIP);
|
||||||
writeIP(root, "subnet_mask", _subnetMask);
|
writeIP(root, "subnet_mask", _settings.subnetMask);
|
||||||
writeIP(root, "dns_ip_1", _dnsIP1);
|
writeIP(root, "dns_ip_1", _settings.dnsIP1);
|
||||||
writeIP(root, "dns_ip_2", _dnsIP2);
|
writeIP(root, "dns_ip_2", _settings.dnsIP2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiFiSettingsService::onConfigUpdated() {
|
void WiFiSettingsService::onConfigUpdated() {
|
||||||
@ -108,27 +108,27 @@ void WiFiSettingsService::loop() {
|
|||||||
|
|
||||||
void WiFiSettingsService::manageSTA() {
|
void WiFiSettingsService::manageSTA() {
|
||||||
// Abort if already connected, or if we have no SSID
|
// Abort if already connected, or if we have no SSID
|
||||||
if (WiFi.isConnected() || _ssid.length() == 0) {
|
if (WiFi.isConnected() || _settings.ssid.length() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Connect or reconnect as required
|
// Connect or reconnect as required
|
||||||
if ((WiFi.getMode() & WIFI_STA) == 0) {
|
if ((WiFi.getMode() & WIFI_STA) == 0) {
|
||||||
Serial.println("Connecting to WiFi.");
|
Serial.println("Connecting to WiFi.");
|
||||||
if (_staticIPConfig) {
|
if (_settings.staticIPConfig) {
|
||||||
// configure for static IP
|
// configure for static IP
|
||||||
WiFi.config(_localIP, _gatewayIP, _subnetMask, _dnsIP1, _dnsIP2);
|
WiFi.config(_settings.localIP, _settings.gatewayIP, _settings.subnetMask, _settings.dnsIP1, _settings.dnsIP2);
|
||||||
} else {
|
} else {
|
||||||
// configure for DHCP
|
// configure for DHCP
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||||
WiFi.setHostname(_hostname.c_str());
|
WiFi.setHostname(_settings.hostname.c_str());
|
||||||
#elif defined(ESP8266)
|
#elif defined(ESP8266)
|
||||||
WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY);
|
WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY);
|
||||||
WiFi.hostname(_hostname);
|
WiFi.hostname(_settings.hostname);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
// attempt to connect to the network
|
// attempt to connect to the network
|
||||||
WiFi.begin(_ssid.c_str(), _password.c_str());
|
WiFi.begin(_settings.ssid.c_str(), _settings.password.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,23 @@
|
|||||||
#define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings"
|
#define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings"
|
||||||
#define WIFI_RECONNECTION_DELAY 1000 * 60
|
#define WIFI_RECONNECTION_DELAY 1000 * 60
|
||||||
|
|
||||||
class WiFiSettingsService : public AdminSettingsService {
|
class WiFiSettings {
|
||||||
|
public:
|
||||||
|
// core wifi configuration
|
||||||
|
String ssid;
|
||||||
|
String password;
|
||||||
|
String hostname;
|
||||||
|
bool staticIPConfig;
|
||||||
|
|
||||||
|
// optional configuration for static IP address
|
||||||
|
IPAddress localIP;
|
||||||
|
IPAddress gatewayIP;
|
||||||
|
IPAddress subnetMask;
|
||||||
|
IPAddress dnsIP1;
|
||||||
|
IPAddress dnsIP2;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WiFiSettingsService : public AdminSettingsService<WiFiSettings> {
|
||||||
public:
|
public:
|
||||||
WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
||||||
~WiFiSettingsService();
|
~WiFiSettingsService();
|
||||||
@ -22,22 +38,8 @@ class WiFiSettingsService : public AdminSettingsService {
|
|||||||
void onConfigUpdated();
|
void onConfigUpdated();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// connection settings
|
|
||||||
String _ssid;
|
|
||||||
String _password;
|
|
||||||
String _hostname;
|
|
||||||
bool _staticIPConfig;
|
|
||||||
|
|
||||||
// for the mangement delay loop
|
|
||||||
unsigned long _lastConnectionAttempt;
|
unsigned long _lastConnectionAttempt;
|
||||||
|
|
||||||
// optional configuration for static IP address
|
|
||||||
IPAddress _localIP;
|
|
||||||
IPAddress _gatewayIP;
|
|
||||||
IPAddress _subnetMask;
|
|
||||||
IPAddress _dnsIP1;
|
|
||||||
IPAddress _dnsIP2;
|
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
|
void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||||
#elif defined(ESP8266)
|
#elif defined(ESP8266)
|
||||||
|
@ -9,7 +9,7 @@ DemoProject::~DemoProject() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DemoProject::loop() {
|
void DemoProject::loop() {
|
||||||
unsigned delay = MAX_DELAY / 255 * (255 - _blinkSpeed);
|
unsigned delay = MAX_DELAY / 255 * (255 - _settings.blinkSpeed);
|
||||||
unsigned long currentMillis = millis();
|
unsigned long currentMillis = millis();
|
||||||
if (!_lastBlink || (unsigned long)(currentMillis - _lastBlink) >= delay) {
|
if (!_lastBlink || (unsigned long)(currentMillis - _lastBlink) >= delay) {
|
||||||
_lastBlink = currentMillis;
|
_lastBlink = currentMillis;
|
||||||
@ -18,10 +18,10 @@ void DemoProject::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DemoProject::readFromJsonObject(JsonObject& root) {
|
void DemoProject::readFromJsonObject(JsonObject& root) {
|
||||||
_blinkSpeed = root["blink_speed"] | DEFAULT_BLINK_SPEED;
|
_settings.blinkSpeed = root["blink_speed"] | DEFAULT_BLINK_SPEED;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DemoProject::writeToJsonObject(JsonObject& root) {
|
void DemoProject::writeToJsonObject(JsonObject& root) {
|
||||||
// connection settings
|
// connection settings
|
||||||
root["blink_speed"] = _blinkSpeed;
|
root["blink_speed"] = _settings.blinkSpeed;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#define DemoProject_h
|
#define DemoProject_h
|
||||||
|
|
||||||
#include <AdminSettingsService.h>
|
#include <AdminSettingsService.h>
|
||||||
|
#include <ESP8266React.h>
|
||||||
|
|
||||||
#define BLINK_LED 2
|
#define BLINK_LED 2
|
||||||
#define MAX_DELAY 1000
|
#define MAX_DELAY 1000
|
||||||
@ -10,7 +11,12 @@
|
|||||||
#define DEMO_SETTINGS_FILE "/config/demoSettings.json"
|
#define DEMO_SETTINGS_FILE "/config/demoSettings.json"
|
||||||
#define DEMO_SETTINGS_PATH "/rest/demoSettings"
|
#define DEMO_SETTINGS_PATH "/rest/demoSettings"
|
||||||
|
|
||||||
class DemoProject : public AdminSettingsService {
|
class DemoSettings {
|
||||||
|
public:
|
||||||
|
uint8_t blinkSpeed;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DemoProject : public AdminSettingsService<DemoSettings> {
|
||||||
public:
|
public:
|
||||||
DemoProject(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
DemoProject(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
|
||||||
~DemoProject();
|
~DemoProject();
|
||||||
@ -19,7 +25,6 @@ class DemoProject : public AdminSettingsService {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned long _lastBlink = 0;
|
unsigned long _lastBlink = 0;
|
||||||
uint8_t _blinkSpeed = 255;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void readFromJsonObject(JsonObject& root);
|
void readFromJsonObject(JsonObject& root);
|
||||||
|
Loading…
Reference in New Issue
Block a user