External config

Allow config to be accessed from outside the framework core code.
This commit is contained in:
rjwats
2020-02-01 08:44:26 +00:00
committed by GitHub
parent 0ca9530afa
commit 39a86b0411
19 changed files with 422 additions and 251 deletions

View File

@ -9,7 +9,11 @@ APSettingsService::~APSettingsService() {
void APSettingsService::begin() {
SettingsService::begin();
onConfigUpdated();
reconfigureAP();
}
void APSettingsService::reconfigureAP() {
_lastManaged = millis() - MANAGE_NETWORK_DELAY;
}
void APSettingsService::loop() {
@ -24,7 +28,8 @@ void APSettingsService::loop() {
void APSettingsService::manageAP() {
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) {
startAP();
}
@ -37,7 +42,7 @@ void APSettingsService::manageAP() {
void APSettingsService::startAP() {
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) {
IPAddress apIp = WiFi.softAPIP();
Serial.print("Starting captive portal on ");
@ -65,25 +70,25 @@ void APSettingsService::handleDNS() {
}
void APSettingsService::readFromJsonObject(JsonObject& root) {
_provisionMode = root["provision_mode"] | AP_MODE_ALWAYS;
switch (_provisionMode) {
_settings.provisionMode = root["provision_mode"] | AP_MODE_ALWAYS;
switch (_settings.provisionMode) {
case AP_MODE_ALWAYS:
case AP_MODE_DISCONNECTED:
case AP_MODE_NEVER:
break;
default:
_provisionMode = AP_MODE_ALWAYS;
_settings.provisionMode = AP_MODE_ALWAYS;
}
_ssid = root["ssid"] | AP_DEFAULT_SSID;
_password = root["password"] | AP_DEFAULT_PASSWORD;
_settings.ssid = root["ssid"] | AP_DEFAULT_SSID;
_settings.password = root["password"] | AP_DEFAULT_PASSWORD;
}
void APSettingsService::writeToJsonObject(JsonObject& root) {
root["provision_mode"] = _provisionMode;
root["ssid"] = _ssid;
root["password"] = _password;
root["provision_mode"] = _settings.provisionMode;
root["ssid"] = _settings.ssid;
root["password"] = _settings.password;
}
void APSettingsService::onConfigUpdated() {
_lastManaged = millis() - MANAGE_NETWORK_DELAY;
}
reconfigureAP();
}

View File

@ -19,7 +19,14 @@
#define AP_SETTINGS_FILE "/config/apSettings.json"
#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:
APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~APSettingsService();
@ -33,17 +40,13 @@ class APSettingsService : public AdminSettingsService {
void onConfigUpdated();
private:
// access point settings
uint8_t _provisionMode;
String _ssid;
String _password;
// for the mangement delay loop
unsigned long _lastManaged;
// for the captive portal
DNSServer* _dnsServer;
void reconfigureAP();
void manageAP();
void startAP();
void stopAP();

View File

@ -3,14 +3,15 @@
#include <SettingsService.h>
class AdminSettingsService : public SettingsService {
template <class T>
class AdminSettingsService : public SettingsService<T> {
public:
AdminSettingsService(AsyncWebServer* server,
FS* fs,
SecurityManager* securityManager,
char const* servicePath,
char const* filePath) :
SettingsService(server, fs, servicePath, filePath),
SettingsService<T>(server, fs, servicePath, filePath),
_securityManager(securityManager) {
}
@ -26,7 +27,7 @@ class AdminSettingsService : public SettingsService {
return;
}
// delegate to underlying implemetation
SettingsService::fetchConfig(request);
SettingsService<T>::fetchConfig(request);
}
void updateConfig(AsyncWebServerRequest* request, JsonDocument& jsonDocument) {
@ -37,7 +38,7 @@ class AdminSettingsService : public SettingsService {
return;
}
// delegate to underlying implemetation
SettingsService::updateConfig(request, jsonDocument);
SettingsService<T>::updateConfig(request, jsonDocument);
}
// override this to replace the default authentication predicate, IS_ADMIN

View File

@ -21,7 +21,7 @@ AuthenticationService::~AuthenticationService() {
*/
void AuthenticationService::verifyAuthorization(AsyncWebServerRequest* 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 password = jsonDocument["password"];
Authentication authentication = _securityManager->authenticate(username, password);
if (authentication.isAuthenticated()) {
User* user = authentication.getUser();
if (authentication.authenticated) {
User* user = authentication.user;
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_AUTHENTICATION_SIZE);
JsonObject jsonObject = response->getRoot();
jsonObject["access_token"] = _securityManager->generateJWT(user);

View File

@ -41,14 +41,34 @@ class ESP8266React {
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:
SecuritySettingsService _securitySettingsService;
WiFiSettingsService _wifiSettingsService;
APSettingsService _apSettingsService;
NTPSettingsService _ntpSettingsService;
OTASettingsService _otaSettingsService;
RestartService _restartService;
AuthenticationService _authenticationService;
WiFiScanner _wifiScanner;
@ -56,7 +76,6 @@ class ESP8266React {
NTPStatus _ntpStatus;
APStatus _apStatus;
SystemStatus _systemStatus;
};
#endif

View File

@ -28,17 +28,17 @@ void NTPSettingsService::loop() {
}
void NTPSettingsService::readFromJsonObject(JsonObject& root) {
_enabled = root["enabled"] | NTP_SETTINGS_SERVICE_DEFAULT_ENABLED;
_server = root["server"] | NTP_SETTINGS_SERVICE_DEFAULT_SERVER;
_tzLabel = root["tz_label"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_LABEL;
_tzFormat = root["tz_format"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_FORMAT;
_settings.enabled = root["enabled"] | NTP_SETTINGS_SERVICE_DEFAULT_ENABLED;
_settings.server = root["server"] | NTP_SETTINGS_SERVICE_DEFAULT_SERVER;
_settings.tzLabel = root["tz_label"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_LABEL;
_settings.tzFormat = root["tz_format"] | NTP_SETTINGS_SERVICE_DEFAULT_TIME_ZONE_FORMAT;
}
void NTPSettingsService::writeToJsonObject(JsonObject& root) {
root["enabled"] = _enabled;
root["server"] = _server;
root["tz_label"] = _tzLabel;
root["tz_format"] = _tzFormat;
root["enabled"] = _settings.enabled;
root["server"] = _settings.server;
root["tz_label"] = _settings.tzLabel;
root["tz_format"] = _settings.tzFormat;
}
void NTPSettingsService::onConfigUpdated() {
@ -47,23 +47,23 @@ void NTPSettingsService::onConfigUpdated() {
#ifdef ESP32
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;
}
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;
sntp_stop();
}
#elif defined(ESP8266)
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;
}
void NTPSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
Serial.printf("WiFi connection dropped, stopping NTP.\n");
Serial.println("WiFi connection dropped, stopping NTP.");
_reconfigureNTP = false;
sntp_stop();
}
@ -71,13 +71,13 @@ void NTPSettingsService::onStationModeDisconnected(const WiFiEventStationModeDis
void NTPSettingsService::configureNTP() {
Serial.println("Configuring NTP...");
if (_enabled) {
if (_settings.enabled) {
#ifdef ESP32
configTzTime(_tzFormat.c_str(), _server.c_str());
configTzTime(_settings.tzFormat.c_str(), _settings.server.c_str());
#elif defined(ESP8266)
configTime(_tzFormat.c_str(), _server.c_str());
configTime(_settings.tzFormat.c_str(), _settings.server.c_str());
#endif
} else {
sntp_stop();
sntp_stop();
}
}

View File

@ -23,7 +23,15 @@
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
#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:
NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~NTPSettingsService();
@ -37,11 +45,6 @@ class NTPSettingsService : public AdminSettingsService {
void receivedNTPtime();
private:
bool _enabled;
String _tzLabel;
String _tzFormat;
String _server;
bool _reconfigureNTP = false;
#ifdef ESP32

View File

@ -15,7 +15,7 @@ OTASettingsService::~OTASettingsService() {
}
void OTASettingsService::loop() {
if (_enabled && _arduinoOTA) {
if ( _settings.enabled && _arduinoOTA) {
_arduinoOTA->handle();
}
}
@ -25,15 +25,15 @@ void OTASettingsService::onConfigUpdated() {
}
void OTASettingsService::readFromJsonObject(JsonObject& root) {
_enabled = root["enabled"] | DEFAULT_OTA_ENABLED;
_port = root["port"] | DEFAULT_OTA_PORT;
_password = root["password"] | DEFAULT_OTA_PASSWORD;
_settings.enabled = root["enabled"] | DEFAULT_OTA_ENABLED;
_settings.port = root["port"] | DEFAULT_OTA_PORT;
_settings.password = root["password"] | DEFAULT_OTA_PASSWORD;
}
void OTASettingsService::writeToJsonObject(JsonObject& root) {
root["enabled"] = _enabled;
root["port"] = _port;
root["password"] = _password;
root["enabled"] = _settings.enabled;
root["port"] = _settings.port;
root["password"] = _settings.password;
}
void OTASettingsService::configureArduinoOTA() {
@ -44,11 +44,11 @@ void OTASettingsService::configureArduinoOTA() {
delete _arduinoOTA;
_arduinoOTA = nullptr;
}
if (_enabled) {
if (_settings.enabled) {
Serial.println("Starting OTA Update Service");
_arduinoOTA = new ArduinoOTAClass;
_arduinoOTA->setPort(_port);
_arduinoOTA->setPassword(_password.c_str());
_arduinoOTA->setPort(_settings.port);
_arduinoOTA->setPassword(_settings.password.c_str());
_arduinoOTA->onStart([]() { Serial.println("Starting"); });
_arduinoOTA->onEnd([]() { Serial.println("\nEnd"); });
_arduinoOTA->onProgress([](unsigned int progress, unsigned int total) {

View File

@ -20,7 +20,14 @@
#define OTA_SETTINGS_FILE "/config/otaSettings.json"
#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:
OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~OTASettingsService();
@ -34,9 +41,6 @@ class OTASettingsService : public AdminSettingsService {
private:
ArduinoOTAClass* _arduinoOTA;
bool _enabled;
int _port;
String _password;
void configureArduinoOTA();
#ifdef ESP32

View File

@ -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);
};
}

View File

@ -14,43 +14,28 @@
#define MAX_JWT_SIZE 128
class User {
private:
String _username;
String _password;
bool _admin;
public:
String username;
String password;
bool admin;
public:
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;
User(String username, String password, bool admin) : username(username), password(password), admin(admin) {
}
};
class Authentication {
private:
User* _user;
boolean _authenticated;
public:
User* user;
boolean authenticated;
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() {
delete (_user);
}
User* getUser() {
return _user;
}
bool isAuthenticated() {
return _authenticated;
delete (user);
}
};
@ -62,10 +47,10 @@ class AuthenticationPredicates {
return true;
};
static bool IS_AUTHENTICATED(Authentication& authentication) {
return authentication.isAuthenticated();
return authentication.authenticated;
};
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
*/
Authentication authenticate(String username, String password);
virtual Authentication authenticate(String username, String password) = 0;
/*
* 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
*/
String generateJWT(User* user);
virtual String generateJWT(User* user) = 0;
/**
* Wrap the provided request to provide validation against an AuthenticationPredicate.
*/
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
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);
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest,
AuthenticationPredicate predicate) = 0;
};
#endif // end SecurityManager_h

View File

@ -12,14 +12,14 @@ void SecuritySettingsService::readFromJsonObject(JsonObject& root) {
_jwtHandler.setSecret(root["jwt_secret"] | DEFAULT_JWT_SECRET);
// users
_users.clear();
_settings.users.clear();
if (root["users"].is<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 {
_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_ADMIN_USERNAME, DEFAULT_ADMIN_USERNAME, true));
_settings.users.push_back(User(DEFAULT_GUEST_USERNAME, DEFAULT_GUEST_USERNAME, false));
}
}
@ -29,10 +29,78 @@ void SecuritySettingsService::writeToJsonObject(JsonObject& root) {
// users
JsonArray users = root.createNestedArray("users");
for (User _user : _users) {
for (User _user : _settings.users) {
JsonObject user = users.createNestedObject();
user["username"] = _user.getUsername();
user["password"] = _user.getPassword();
user["admin"] = _user.isAdmin();
user["username"] = _user.username;
user["password"] = _user.password;
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);
};
}

View File

@ -10,14 +10,39 @@
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
#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:
SecuritySettingsService(AsyncWebServer* server, FS* fs);
~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:
void readFromJsonObject(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

View File

@ -1,6 +1,8 @@
#ifndef SettingsService_h
#define SettingsService_h
#include <functional>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
@ -17,9 +19,24 @@
#include <SecurityManager.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.
*/
template <class T>
class SettingsService : public SettingsPersistence {
public:
SettingsService(AsyncWebServer* server, FS* fs, char const* servicePath, char const* filePath) :
@ -37,14 +54,71 @@ class SettingsService : public SettingsPersistence {
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() {
// read the initial data from the file system
readFromFS();
}
protected:
T _settings;
char const* _servicePath;
AsyncJsonWebHandler _updateHandler;
std::list<SettingsUpdateHandlerInfo_t> _settingsUpdateHandlers;
virtual void fetchConfig(AsyncWebServerRequest* request) {
// handle the request
@ -64,7 +138,7 @@ class SettingsService : public SettingsPersistence {
// write settings back with a callback to reconfigure the wifi
AsyncJsonCallbackResponse* response =
new AsyncJsonCallbackResponse([this]() { onConfigUpdated(); }, false, MAX_SETTINGS_SIZE);
new AsyncJsonCallbackResponse([this]() { callUpdateHandlers(); }, false, MAX_SETTINGS_SIZE);
JsonObject jsonObject = response->getRoot();
writeToJsonObject(jsonObject);
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
virtual void onConfigUpdated() {
}

View File

@ -33,45 +33,45 @@ void WiFiSettingsService::begin() {
}
void WiFiSettingsService::readFromJsonObject(JsonObject& root) {
_ssid = root["ssid"] | "";
_password = root["password"] | "";
_hostname = root["hostname"] | "";
_staticIPConfig = root["static_ip_config"] | false;
_settings.ssid = root["ssid"] | "";
_settings.password = root["password"] | "";
_settings.hostname = root["hostname"] | "";
_settings.staticIPConfig = root["static_ip_config"] | false;
// extended settings
readIP(root, "local_ip", _localIP);
readIP(root, "gateway_ip", _gatewayIP);
readIP(root, "subnet_mask", _subnetMask);
readIP(root, "dns_ip_1", _dnsIP1);
readIP(root, "dns_ip_2", _dnsIP2);
readIP(root, "local_ip", _settings.localIP);
readIP(root, "gateway_ip", _settings.gatewayIP);
readIP(root, "subnet_mask", _settings.subnetMask);
readIP(root, "dns_ip_1", _settings.dnsIP1);
readIP(root, "dns_ip_2", _settings.dnsIP2);
// Swap around the dns servers if 2 is populated but 1 is not
if (_dnsIP1 == INADDR_NONE && _dnsIP2 != INADDR_NONE) {
_dnsIP1 = _dnsIP2;
_dnsIP2 = INADDR_NONE;
if (_settings.dnsIP1 == INADDR_NONE && _settings.dnsIP2 != INADDR_NONE) {
_settings.dnsIP1 = _settings.dnsIP2;
_settings.dnsIP2 = INADDR_NONE;
}
// 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
// as sensible defaults can be assumed for gateway and subnet
if (_staticIPConfig && (_localIP == INADDR_NONE || _gatewayIP == INADDR_NONE || _subnetMask == INADDR_NONE)) {
_staticIPConfig = false;
if (_settings.staticIPConfig && (_settings.localIP == INADDR_NONE || _settings.gatewayIP == INADDR_NONE || _settings.subnetMask == INADDR_NONE)) {
_settings.staticIPConfig = false;
}
}
void WiFiSettingsService::writeToJsonObject(JsonObject& root) {
// connection settings
root["ssid"] = _ssid;
root["password"] = _password;
root["hostname"] = _hostname;
root["static_ip_config"] = _staticIPConfig;
root["ssid"] = _settings.ssid;
root["password"] = _settings.password;
root["hostname"] = _settings.hostname;
root["static_ip_config"] = _settings.staticIPConfig;
// extended settings
writeIP(root, "local_ip", _localIP);
writeIP(root, "gateway_ip", _gatewayIP);
writeIP(root, "subnet_mask", _subnetMask);
writeIP(root, "dns_ip_1", _dnsIP1);
writeIP(root, "dns_ip_2", _dnsIP2);
writeIP(root, "local_ip", _settings.localIP);
writeIP(root, "gateway_ip", _settings.gatewayIP);
writeIP(root, "subnet_mask", _settings.subnetMask);
writeIP(root, "dns_ip_1", _settings.dnsIP1);
writeIP(root, "dns_ip_2", _settings.dnsIP2);
}
void WiFiSettingsService::onConfigUpdated() {
@ -108,27 +108,27 @@ void WiFiSettingsService::loop() {
void WiFiSettingsService::manageSTA() {
// Abort if already connected, or if we have no SSID
if (WiFi.isConnected() || _ssid.length() == 0) {
if (WiFi.isConnected() || _settings.ssid.length() == 0) {
return;
}
// Connect or reconnect as required
if ((WiFi.getMode() & WIFI_STA) == 0) {
Serial.println("Connecting to WiFi.");
if (_staticIPConfig) {
if (_settings.staticIPConfig) {
// configure for static IP
WiFi.config(_localIP, _gatewayIP, _subnetMask, _dnsIP1, _dnsIP2);
WiFi.config(_settings.localIP, _settings.gatewayIP, _settings.subnetMask, _settings.dnsIP1, _settings.dnsIP2);
} else {
// configure for DHCP
#ifdef ESP32
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
WiFi.setHostname(_hostname.c_str());
WiFi.setHostname(_settings.hostname.c_str());
#elif defined(ESP8266)
WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY);
WiFi.hostname(_hostname);
WiFi.hostname(_settings.hostname);
#endif
}
// attempt to connect to the network
WiFi.begin(_ssid.c_str(), _password.c_str());
WiFi.begin(_settings.ssid.c_str(), _settings.password.c_str());
}
}

View File

@ -8,7 +8,23 @@
#define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings"
#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:
WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
~WiFiSettingsService();
@ -22,22 +38,8 @@ class WiFiSettingsService : public AdminSettingsService {
void onConfigUpdated();
private:
// connection settings
String _ssid;
String _password;
String _hostname;
bool _staticIPConfig;
// for the mangement delay loop
unsigned long _lastConnectionAttempt;
// optional configuration for static IP address
IPAddress _localIP;
IPAddress _gatewayIP;
IPAddress _subnetMask;
IPAddress _dnsIP1;
IPAddress _dnsIP2;
#ifdef ESP32
void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
#elif defined(ESP8266)