initial commit of C++ back end and react front end

This commit is contained in:
rjwats@gmail.com
2018-02-26 00:11:31 +00:00
commit 63a639eb22
87 changed files with 14975 additions and 0 deletions

49
src/APSettingsService.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include <APSettingsService.h>
APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, AP_SETTINGS_SERVICE_PATH, AP_SETTINGS_FILE) {
}
APSettingsService::~APSettingsService() {}
void APSettingsService::loop() {
unsigned long now = millis();
if (_manageAtMillis <= now){
WiFiMode_t currentWiFiMode = WiFi.getMode();
if (_provisionMode == AP_MODE_ALWAYS || (_provisionMode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED)) {
if (currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA){
Serial.println("Starting software access point");
WiFi.softAP(_ssid.c_str(), _password.c_str());
}
} else {
if (currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA){
Serial.println("Stopping software access point");
WiFi.softAPdisconnect(true);
}
}
_manageAtMillis = now + MANAGE_NETWORK_DELAY;
}
}
void APSettingsService::readFromJsonObject(JsonObject& root) {
_provisionMode = root["provision_mode"] | AP_MODE_ALWAYS;
switch (_provisionMode) {
case AP_MODE_ALWAYS:
case AP_MODE_DISCONNECTED:
case AP_MODE_NEVER:
break;
default:
_provisionMode = AP_MODE_ALWAYS;
}
_ssid = root["ssid"] | AP_DEFAULT_SSID;
_password = root["password"] | AP_DEFAULT_PASSWORD;
}
void APSettingsService::writeToJsonObject(JsonObject& root) {
root["provision_mode"] = _provisionMode;
root["ssid"] = _ssid;
root["password"] = _password;
}
void APSettingsService::onConfigUpdated() {
_manageAtMillis = 0;
}

43
src/APSettingsService.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef APSettingsConfig_h
#define APSettingsConfig_h
#include <IPAddress.h>
#include <SettingsService.h>
#define MANAGE_NETWORK_DELAY 10000
#define AP_MODE_ALWAYS 0
#define AP_MODE_DISCONNECTED 1
#define AP_MODE_NEVER 2
#define AP_DEFAULT_SSID "ssid"
#define AP_DEFAULT_PASSWORD "password"
#define AP_SETTINGS_FILE "/config/apSettings.json"
#define AP_SETTINGS_SERVICE_PATH "/apSettings"
class APSettingsService : public SettingsService {
public:
APSettingsService(AsyncWebServer* server, FS* fs);
~APSettingsService();
void loop();
protected:
void readFromJsonObject(JsonObject& root);
void writeToJsonObject(JsonObject& root);
void onConfigUpdated();
private:
int _provisionMode;
String _ssid;
String _password;
unsigned long _manageAtMillis;
};
#endif // end APSettingsConfig_h

19
src/APStatus.cpp Normal file
View File

@@ -0,0 +1,19 @@
#include <APStatus.h>
APStatus::APStatus(AsyncWebServer *server) : _server(server) {
_server->on("/apStatus", HTTP_GET, std::bind(&APStatus::apStatus, this, std::placeholders::_1));
}
void APStatus::apStatus(AsyncWebServerRequest *request) {
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
WiFiMode_t currentWiFiMode = WiFi.getMode();
root["active"] = (currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA);
root["ip_address"] = WiFi.softAPIP().toString();
root["mac_address"] = WiFi.softAPmacAddress();
root["station_num"] = WiFi.softAPgetStationNum();
response->setLength();
request->send(response);
}

25
src/APStatus.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef APStatus_h
#define APStatus_h
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <IPAddress.h>
class APStatus {
public:
APStatus(AsyncWebServer *server);
private:
AsyncWebServer* _server;
void apStatus(AsyncWebServerRequest *request);
};
#endif // end APStatus_h

View File

@@ -0,0 +1,31 @@
#ifndef _AsyncJsonCallbackResponse_H_
#define _AsyncJsonCallbackResponse_H_
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
/*
* Listens for a response being destroyed and calls a callback during said distruction.
* used so we can take action after the response has been rendered to the client.
*
* Avoids having to fork ESPAsyncWebServer with a callback feature, but not nice!
*/
typedef std::function<void()> AsyncJsonCallback;
class AsyncJsonCallbackResponse: public AsyncJsonResponse {
private:
AsyncJsonCallback _callback;
public:
AsyncJsonCallbackResponse(AsyncJsonCallback callback, bool isArray=false) : _callback{callback}, AsyncJsonResponse(isArray) {}
~AsyncJsonCallbackResponse() {
_callback();
}
};
#endif // end _AsyncJsonCallbackResponse_H_

View File

@@ -0,0 +1,115 @@
#ifndef Async_Json_Request_Web_Handler_H_
#define Async_Json_Request_Web_Handler_H_
#include <ArduinoJson.h>
#define ASYNC_JSON_REQUEST_DEFAULT_MAX_SIZE 1024
#define ASYNC_JSON_REQUEST_MIMETYPE "application/json"
/*
* Handy little utility for dealing with small JSON request body payloads.
*
* Need to be careful using this as we are somewhat limited by RAM.
*
* Really only of use where there is a determinate payload size.
*/
typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> JsonRequestCallback;
class AsyncJsonRequestWebHandler: public AsyncWebHandler {
private:
String _uri;
WebRequestMethodComposite _method;
JsonRequestCallback _onRequest;
int _maxContentLength;
public:
AsyncJsonRequestWebHandler() :
_uri(),
_method(HTTP_POST|HTTP_PUT|HTTP_PATCH),
_onRequest(NULL),
_maxContentLength(ASYNC_JSON_REQUEST_DEFAULT_MAX_SIZE) {}
~AsyncJsonRequestWebHandler() {}
void setUri(const String& uri) { _uri = uri; }
void setMethod(WebRequestMethodComposite method) { _method = method; }
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
void onRequest(JsonRequestCallback fn) { _onRequest = fn; }
virtual bool canHandle(AsyncWebServerRequest *request) override final {
if(!_onRequest)
return false;
if(!(_method & request->method()))
return false;
if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
return false;
if (!request->contentType().equalsIgnoreCase(ASYNC_JSON_REQUEST_MIMETYPE))
return false;
request->addInterestingHeader("ANY");
return true;
}
virtual void handleRequest(AsyncWebServerRequest *request) override final {
// no request configured
if(!_onRequest) {
request->send(404);
return;
}
// we have been handed too much data, return a 413 (payload too large)
if (request->contentLength() > _maxContentLength) {
request->send(413);
return;
}
// parse JSON and if possible handle the request
if (request->_tempObject) {
DynamicJsonBuffer jsonBuffer;
JsonVariant json = jsonBuffer.parse((uint8_t *) request->_tempObject);
if (json.success()) {
_onRequest(request, json);
}else{
request->send(400);
}
return;
}
// fallthrough, we have a null pointer, return 500.
// this can be due to running out of memory or never recieving body data.
request->send(500);
}
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
if (_onRequest) {
// don't allocate if data is too large
if (total > _maxContentLength){
return;
}
// try to allocate memory on first call
// NB: the memory allocated here is freed by ~AsyncWebServerRequest
if(index == 0 && !request->_tempObject){
request->_tempObject = malloc(total);
}
// copy the data into the buffer, if we have a buffer!
if (request->_tempObject) {
memcpy((uint8_t *) request->_tempObject+index, data, len);
}
}
}
virtual bool isRequestHandlerTrivial() override final {
return _onRequest ? false : true;
}
};
#endif // end Async_Json_Request_Web_Handler_H_

View File

@@ -0,0 +1,47 @@
#include <AuthSettingsService.h>
AuthSettingsService::AuthSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, AUTH_SETTINGS_SERVICE_PATH, AUTH_SETTINGS_FILE) {
_server->on(AUTH_LOGOUT_PATH, HTTP_GET, std::bind(&AuthSettingsService::logout, this, std::placeholders::_1));
// configure authentication handler
_authenticationHandler.setUri(AUTH_AUTHENTICATE_PATH);
_authenticationHandler.setMethod(HTTP_POST);
_authenticationHandler.onRequest(std::bind(&AuthSettingsService::authenticate, this, std::placeholders::_1, std::placeholders::_2));
_server->addHandler(&_authenticationHandler);
}
AuthSettingsService::~AuthSettingsService() {}
// checks the session is authenticated, refreshes the sessions timeout if so
bool AuthSettingsService::authenticated(AsyncWebServerRequest *request){
request->send(400);
return false;
}
void AuthSettingsService::readFromJsonObject(JsonObject& root){
_username = root["username"] | AUTH_DEFAULT_USERNAME;
_password = root["password"] | AUTH_DEFAULT_PASSWORD;
_sessionTimeout= root["session_timeout"] | AUTH_DEFAULT_SESSION_TIMEOUT;
}
void AuthSettingsService::writeToJsonObject(JsonObject& root){
root["username"] = _username;
root["password"] = _password;
root["session_timeout"] = _sessionTimeout;
}
void AuthSettingsService::logout(AsyncWebServerRequest *request){
// revoke the current requests session
}
void AuthSettingsService::authenticate(AsyncWebServerRequest *request, JsonVariant &json){
if (json.is<JsonObject>()){
JsonObject& credentials = json.as<JsonObject>();
if (credentials["username"] == _username && credentials["password"] == _password){
// store cookie and write to response
}
request->send(401);
} else{
request->send(400);
}
}

56
src/AuthSettingsService.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef AuthSettingsService_h
#define AuthSettingsService_h
#include <SettingsService.h>
#define AUTH_DEFAULT_USERNAME "admin"
#define AUTH_DEFAULT_PASSWORD "admin"
#define AUTH_DEFAULT_SESSION_TIMEOUT 3600
#define AUTH_SETTINGS_FILE "/config/authSettings.json"
#define AUTH_SETTINGS_SERVICE_PATH "/authSettings"
#define AUTH_LOGOUT_PATH "/logout"
#define AUTH_AUTHENTICATE_PATH "/authenticate"
// max number of concurrently authenticated clients
#define AUTH_MAX_CLIENTS 10
/*
* TODO: Will protect services with a cookie based authentication service.
*/
class AuthSettingsService : public SettingsService {
public:
AuthSettingsService(AsyncWebServer* server, FS* fs);
~AuthSettingsService();
// checks the session is authenticated,
// refreshes the sessions timeout if found
bool authenticated(AsyncWebServerRequest *request);
protected:
void readFromJsonObject(JsonObject& root);
void writeToJsonObject(JsonObject& root);
private:
// callback handler for authentication endpoint
AsyncJsonRequestWebHandler _authenticationHandler;
// only supporting one username at the moment
String _username;
String _password;
// session timeout in seconds
unsigned int _sessionTimeout;
void logout(AsyncWebServerRequest *request);
void authenticate(AsyncWebServerRequest *request, JsonVariant &json);
};
#endif // end AuthSettingsService_h

View File

@@ -0,0 +1,94 @@
#include <NTPSettingsService.h>
NTPSettingsService::NTPSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, NTP_SETTINGS_SERVICE_PATH, NTP_SETTINGS_FILE) {
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
_onStationModeGotIPHandler = WiFi.onStationModeGotIP(std::bind(&NTPSettingsService::onStationModeGotIP, this, std::placeholders::_1));
NTP.onNTPSyncEvent ([this](NTPSyncEvent_t ntpEvent) {
_ntpEvent = ntpEvent;
_syncEventTriggered = true;
});
}
NTPSettingsService::~NTPSettingsService() {}
void NTPSettingsService::loop() {
// detect when we need to re-configure NTP and do it in the main loop
if (_reconfigureNTP) {
_reconfigureNTP = false;
configureNTP();
}
// output sync event to serial
if (_syncEventTriggered) {
processSyncEvent(_ntpEvent);
_syncEventTriggered = false;
}
// keep time synchronized in background
now();
}
void NTPSettingsService::readFromJsonObject(JsonObject& root) {
_server = root["server"] | NTP_SETTINGS_SERVICE_DEFAULT_SERVER;
_interval = root["interval"];
// validate server is specified, resorting to default
_server.trim();
if (!_server){
_server = NTP_SETTINGS_SERVICE_DEFAULT_SERVER;
}
// make sure interval is in bounds
if (_interval < NTP_SETTINGS_MIN_INTERVAL){
_interval = NTP_SETTINGS_MIN_INTERVAL;
} else if (_interval > NTP_SETTINGS_MAX_INTERVAL) {
_interval = NTP_SETTINGS_MAX_INTERVAL;
}
}
void NTPSettingsService::writeToJsonObject(JsonObject& root) {
root["server"] = _server;
root["interval"] = _interval;
}
void NTPSettingsService::onConfigUpdated() {
_reconfigureNTP = true;
}
void NTPSettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
Serial.printf("Got IP address, starting NTP Synchronization\n");
_reconfigureNTP = true;
}
void NTPSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
Serial.printf("WiFi connection dropped, stopping NTP.\n");
// stop NTP synchronization, ensuring no re-configuration can take place
_reconfigureNTP = false;
NTP.stop();
}
void NTPSettingsService::configureNTP() {
Serial.println("Configuring NTP...");
// disable sync
NTP.stop();
// enable sync
NTP.begin(_server);
NTP.setInterval(_interval);
}
void NTPSettingsService::processSyncEvent(NTPSyncEvent_t ntpEvent) {
if (ntpEvent) {
Serial.print ("Time Sync error: ");
if (ntpEvent == noResponse)
Serial.println ("NTP server not reachable");
else if (ntpEvent == invalidAddress)
Serial.println ("Invalid NTP server address");
} else {
Serial.print ("Got NTP time: ");
Serial.println (NTP.getTimeDateString (NTP.getLastNTPSync ()));
}
}

56
src/NTPSettingsService.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef NTPSettingsService_h
#define NTPSettingsService_h
#include <SettingsService.h>
#include <ESP8266WiFi.h>
#include <TimeLib.h>
#include <NtpClientLib.h>
// default time server
#define NTP_SETTINGS_SERVICE_DEFAULT_SERVER "pool.ntp.org"
#define NTP_SETTINGS_SERVICE_DEFAULT_INTERVAL 3600
// min poll delay of 60 secs, max 1 day
#define NTP_SETTINGS_MIN_INTERVAL 60
#define NTP_SETTINGS_MAX_INTERVAL 86400
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
#define NTP_SETTINGS_SERVICE_PATH "/ntpSettings"
class NTPSettingsService : public SettingsService {
public:
NTPSettingsService(AsyncWebServer* server, FS* fs);
~NTPSettingsService();
void loop();
protected:
void readFromJsonObject(JsonObject& root);
void writeToJsonObject(JsonObject& root);
void onConfigUpdated();
private:
WiFiEventHandler _onStationModeDisconnectedHandler;
WiFiEventHandler _onStationModeGotIPHandler;
String _server;
int _interval;
bool _reconfigureNTP = false;
bool _syncEventTriggered = false;
NTPSyncEvent_t _ntpEvent;
void onStationModeGotIP(const WiFiEventStationModeGotIP& event);
void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
void configureNTP();
void processSyncEvent(NTPSyncEvent_t ntpEvent);
};
#endif // end NTPSettingsService_h

28
src/NTPStatus.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include <NTPStatus.h>
NTPStatus::NTPStatus(AsyncWebServer *server) : _server(server) {
_server->on("/ntpStatus", HTTP_GET, std::bind(&NTPStatus::ntpStatus, this, std::placeholders::_1));
}
void NTPStatus::ntpStatus(AsyncWebServerRequest *request) {
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
// request time now first, this can sometimes force a sync
time_t timeNow = now();
timeStatus_t status = timeStatus();
time_t lastSync = NTP.getLastNTPSync();
root["status"] = (int) status;
root["last_sync"] = lastSync;
root["server"] = NTP.getNtpServerName();
root["interval"] = NTP.getInterval();
root["uptime"] = NTP.getUptime();
// only add now to response if we have successfully synced
if (status != timeNotSet){
root["now"] = timeNow;
}
response->setLength();
request->send(response);
}

26
src/NTPStatus.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef NTPStatus_h
#define NTPStatus_h
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <TimeLib.h>
#include <NtpClientLib.h>
class NTPStatus {
public:
NTPStatus(AsyncWebServer *server);
private:
AsyncWebServer* _server;
void ntpStatus(AsyncWebServerRequest *request);
};
#endif // end NTPStatus_h

View File

@@ -0,0 +1,67 @@
#include <OTASettingsService.h>
OTASettingsService::OTASettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, OTA_SETTINGS_SERVICE_PATH, OTA_SETTINGS_FILE) {}
OTASettingsService::~OTASettingsService() {}
void OTASettingsService::begin() {
// load settings
SettingsService::begin();
// configure arduino OTA
configureArduinoOTA();
}
void OTASettingsService::loop() {
if (_enabled && _arduinoOTA){
_arduinoOTA->handle();
}
}
void OTASettingsService::onConfigUpdated() {
configureArduinoOTA();
}
void OTASettingsService::readFromJsonObject(JsonObject& root) {
_enabled = root["enabled"];
_port = root["port"];
_password = root["password"] | DEFAULT_OTA_PASSWORD;
// provide defaults
if (_port < 0){
_port = DEFAULT_OTA_PORT;
}
}
void OTASettingsService::writeToJsonObject(JsonObject& root) {
root["enabled"] = _enabled;
root["port"] = _port;
root["password"] = _password;
}
void OTASettingsService::configureArduinoOTA() {
delete _arduinoOTA;
if (_enabled) {
_arduinoOTA = new ArduinoOTAClass;
_arduinoOTA->setPort(_port);
_arduinoOTA->setPassword(_password.c_str());
_arduinoOTA->onStart([]() {
Serial.println("Starting");
});
_arduinoOTA->onEnd([]() {
Serial.println("\nEnd");
});
_arduinoOTA->onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
_arduinoOTA->onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
_arduinoOTA->begin();
}
}

44
src/OTASettingsService.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef OTASettingsService_h
#define OTASettingsService_h
#include <SettingsService.h>
#include <ESP8266WiFi.h> // ??
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
// Emergency defaults
#define DEFAULT_OTA_PORT 8266
#define DEFAULT_OTA_PASSWORD "esp-react"
#define OTA_SETTINGS_FILE "/config/otaSettings.json"
#define OTA_SETTINGS_SERVICE_PATH "/otaSettings"
class OTASettingsService : public SettingsService {
public:
OTASettingsService(AsyncWebServer* server, FS* fs);
~OTASettingsService();
void begin();
void loop();
protected:
void onConfigUpdated();
void readFromJsonObject(JsonObject& root);
void writeToJsonObject(JsonObject& root);
private:
ArduinoOTAClass *_arduinoOTA;
bool _enabled;
int _port;
String _password;
void configureArduinoOTA();
};
#endif // end NTPSettingsService_h

146
src/SettingsService.h Normal file
View File

@@ -0,0 +1,146 @@
#ifndef SettingsService_h
#define SettingsService_h
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <AsyncJson.h>
#include <ArduinoJson.h>
#include <AsyncJsonRequestWebHandler.h>
#include <AsyncJsonCallbackResponse.h>
/**
* At the moment, not expecting settings service to have to deal with large JSON
* files this could be made configurable fairly simply, it's exposed on
* AsyncJsonRequestWebHandler with a setter.
*/
#define MAX_SETTINGS_SIZE 1024
/*
* Abstraction of a service which stores it's settings as JSON in SPIFFS.
*/
class SettingsService {
private:
char const* _filePath;
AsyncJsonRequestWebHandler _updateHandler;
bool writeToSPIFFS() {
// create and populate a new json object
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
writeToJsonObject(root);
// serialize it to SPIFFS
File configFile = SPIFFS.open(_filePath, "w");
// failed to open file, return false
if (!configFile) {
return false;
}
root.printTo(configFile);
configFile.close();
return true;
}
void readFromSPIFFS(){
File configFile = SPIFFS.open(_filePath, "r");
// use defaults if no config found
if (configFile) {
// Protect against bad data uploaded to SPIFFS
// We never expect the config file to get very large, so cap it.
size_t size = configFile.size();
if (size <= MAX_SETTINGS_SIZE) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(configFile);
if (root.success()) {
readFromJsonObject(root);
configFile.close();
return;
}
}
configFile.close();
}
// If we reach here we have not been successful in loading the config,
// hard-coded emergency defaults are now applied.
applyDefaultConfig();
}
void fetchConfig(AsyncWebServerRequest *request){
AsyncJsonResponse * response = new AsyncJsonResponse();
writeToJsonObject(response->getRoot());
response->setLength();
request->send(response);
}
void updateConfig(AsyncWebServerRequest *request, JsonVariant &json){
if (json.is<JsonObject>()){
JsonObject& newConfig = json.as<JsonObject>();
readFromJsonObject(newConfig);
writeToSPIFFS();
// write settings back with a callback to reconfigure the wifi
AsyncJsonCallbackResponse * response = new AsyncJsonCallbackResponse([this] () {onConfigUpdated();});
writeToJsonObject(response->getRoot());
response->setLength();
request->send(response);
} else{
request->send(400);
}
}
protected:
// will serve setting endpoints from here
AsyncWebServer* _server;
// will store and retrieve config from the file system
FS* _fs;
// reads the local config from the
virtual void readFromJsonObject(JsonObject& root){}
virtual void writeToJsonObject(JsonObject& root){}
// implement to perform action when config has been updated
virtual void onConfigUpdated(){}
// We assume the readFromJsonObject supplies sensible defaults if an empty object
// is supplied, this virtual function allows that to be changed.
virtual void applyDefaultConfig(){
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
readFromJsonObject(root);
}
public:
SettingsService(AsyncWebServer* server, FS* fs, char const* servicePath, char const* filePath):
_server(server), _fs(fs), _filePath(filePath) {
// configure fetch config handler
_server->on(servicePath, HTTP_GET, std::bind(&SettingsService::fetchConfig, this, std::placeholders::_1));
// configure update settings handler
_updateHandler.setUri(servicePath);
_updateHandler.setMethod(HTTP_POST);
_updateHandler.setMaxContentLength(MAX_SETTINGS_SIZE);
_updateHandler.onRequest(std::bind(&SettingsService::updateConfig, this, std::placeholders::_1, std::placeholders::_2));
_server->addHandler(&_updateHandler);
}
virtual ~SettingsService() {}
virtual void begin() {
readFromSPIFFS();
}
};
#endif // end SettingsService

38
src/WiFiScanner.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include <WiFiScanner.h>
WiFiScanner::WiFiScanner(AsyncWebServer *server) : _server(server) {
_server->on("/scanNetworks", HTTP_GET, std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1));
_server->on("/listNetworks", HTTP_GET, std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1));
}
void WiFiScanner::scanNetworks(AsyncWebServerRequest *request) {
if (WiFi.scanComplete() != -1){
WiFi.scanDelete();
WiFi.scanNetworks(true);
}
request->send(202);
}
void WiFiScanner::listNetworks(AsyncWebServerRequest *request) {
int numNetworks = WiFi.scanComplete();
if (numNetworks > -1){
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
JsonArray& networks = root.createNestedArray("networks");
for (int i=0; i<numNetworks ; i++){
JsonObject& network = networks.createNestedObject();
network["rssi"] = WiFi.RSSI(i);
network["ssid"] = WiFi.SSID(i);
network["bssid"] = WiFi.BSSIDstr(i);
network["channel"] = WiFi.channel(i);
network["encryption_type"] = WiFi.encryptionType(i);
network["hidden"] = WiFi.isHidden(i);
}
response->setLength();
request->send(response);
} else if (numNetworks == -1){
request->send(202);
}else{
scanNetworks(request);
}
}

26
src/WiFiScanner.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef WiFiScanner_h
#define WiFiScanner_h
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <TimeLib.h>
class WiFiScanner {
public:
WiFiScanner(AsyncWebServer *server);
private:
AsyncWebServer* _server;
void scanNetworks(AsyncWebServerRequest *request);
void listNetworks(AsyncWebServerRequest *request);
};
#endif // end WiFiScanner_h

View File

@@ -0,0 +1,85 @@
#include <WiFiSettingsService.h>
WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, WIFI_SETTINGS_SERVICE_PATH, WIFI_SETTINGS_FILE) {
}
WiFiSettingsService::~WiFiSettingsService() {}
void WiFiSettingsService::begin() {
SettingsService::begin();
reconfigureWiFiConnection();
}
void WiFiSettingsService::readFromJsonObject(JsonObject& root){
_ssid = root["ssid"] | "";
_password = root["password"] | "";
_hostname = root["hostname"] | "";
_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);
// Swap around the dns servers if 2 is populated but 1 is not
if (_dnsIP1 == 0U && _dnsIP2 != 0U){
_dnsIP1 = _dnsIP2;
_dnsIP2 = 0U;
}
// 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 == 0U || _gatewayIP == 0U || _subnetMask == 0U)){
_staticIPConfig = false;
}
}
void WiFiSettingsService::writeToJsonObject(JsonObject& root){
// connection settings
root["ssid"] = _ssid;
root["password"] = _password;
root["hostname"] = _hostname;
root["static_ip_config"] = _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);
}
void WiFiSettingsService::onConfigUpdated() {
reconfigureWiFiConnection();
}
void WiFiSettingsService::reconfigureWiFiConnection() {
Serial.println("Reconfiguring WiFi...");
// disconnect and de-configure wifi and software access point
WiFi.disconnect(true);
// configure static ip config for station mode (if set)
if (_staticIPConfig) {
WiFi.config(_localIP, _gatewayIP, _subnetMask, _dnsIP1, _dnsIP2);
}
// connect to the network
WiFi.hostname(_hostname);
WiFi.begin(_ssid.c_str(), _password.c_str());
}
void WiFiSettingsService::readIP(JsonObject& root, String key, IPAddress& _ip){
if (!root[key] || !_ip.fromString(root[key].as<String>())){
_ip = 0U;
}
}
void WiFiSettingsService::writeIP(JsonObject& root, String key, IPAddress& _ip){
if (_ip != 0U){
root[key] = _ip.toString();
}
}

46
src/WiFiSettingsService.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef WiFiSettingsService_h
#define WiFiSettingsService_h
#include <IPAddress.h>
#include <SettingsService.h>
#define WIFI_SETTINGS_FILE "/config/wifiSettings.json"
#define WIFI_SETTINGS_SERVICE_PATH "/wifiSettings"
class WiFiSettingsService : public SettingsService {
public:
WiFiSettingsService(AsyncWebServer* server, FS* fs);
~WiFiSettingsService();
void begin();
protected:
void readFromJsonObject(JsonObject& root);
void writeToJsonObject(JsonObject& root);
void onConfigUpdated();
void reconfigureWiFiConnection();
private:
// connection settings
String _ssid;
String _password;
String _hostname;
bool _staticIPConfig;
// optional configuration for static IP address
IPAddress _localIP;
IPAddress _gatewayIP;
IPAddress _subnetMask;
IPAddress _dnsIP1;
IPAddress _dnsIP2;
void readIP(JsonObject& root, String key, IPAddress& _ip);
void writeIP(JsonObject& root, String key, IPAddress& _ip);
};
#endif // end WiFiSettingsService_h

52
src/WiFiStatus.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include <WiFiStatus.h>
WiFiStatus::WiFiStatus(AsyncWebServer *server) : _server(server) {
_server->on("/wifiStatus", HTTP_GET, std::bind(&WiFiStatus::wifiStatus, this, std::placeholders::_1));
_onStationModeConnectedHandler = WiFi.onStationModeConnected(onStationModeConnected);
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(onStationModeDisconnected);
_onStationModeGotIPHandler = WiFi.onStationModeGotIP(onStationModeGotIP);
}
void WiFiStatus::onStationModeConnected(const WiFiEventStationModeConnected& event) {
Serial.print("WiFi Connected. SSID=");
Serial.println(event.ssid);
}
void WiFiStatus::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
Serial.print("WiFi Disconnected. Reason code=");
Serial.println(event.reason);
}
void WiFiStatus::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
Serial.print("WiFi Got IP. localIP=");
Serial.print(event.ip);
Serial.print(", hostName=");
Serial.println(WiFi.hostname());
}
void WiFiStatus::wifiStatus(AsyncWebServerRequest *request) {
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
wl_status_t status = WiFi.status();
root["status"] = (uint8_t) status;
if (status == WL_CONNECTED){
root["local_ip"] = WiFi.localIP().toString();
root["rssi"] = WiFi.RSSI();
root["ssid"] = WiFi.SSID();
root["bssid"] = WiFi.BSSIDstr();
root["channel"] = WiFi.channel();
root["subnet_mask"] = WiFi.subnetMask().toString();
root["gateway_ip"] = WiFi.gatewayIP().toString();
IPAddress dnsIP1 = WiFi.dnsIP(0);
IPAddress dnsIP2 = WiFi.dnsIP(1);
if (dnsIP1 != 0U){
root["dns_ip_1"] = dnsIP1.toString();
}
if (dnsIP2 != 0U){
root["dns_ip_2"] = dnsIP2.toString();
}
}
response->setLength();
request->send(response);
}

35
src/WiFiStatus.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef WiFiStatus_h
#define WiFiStatus_h
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <IPAddress.h>
class WiFiStatus {
public:
WiFiStatus(AsyncWebServer *server);
private:
AsyncWebServer* _server;
// handler refrences for logging important WiFi events over serial
WiFiEventHandler _onStationModeConnectedHandler;
WiFiEventHandler _onStationModeDisconnectedHandler;
WiFiEventHandler _onStationModeGotIPHandler;
// static functions for logging wifi events to the UART
static void onStationModeConnected(const WiFiEventStationModeConnected& event);
static void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
static void onStationModeGotIP(const WiFiEventStationModeGotIP& event);
void wifiStatus(AsyncWebServerRequest *request);
};
#endif // end WiFiStatus_h

62
src/main.cpp Normal file
View File

@@ -0,0 +1,62 @@
#include <Arduino.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <WiFiSettingsService.h>
#include <WiFiStatus.h>
#include <WiFiScanner.h>
#include <APSettingsService.h>
#include <NTPSettingsService.h>
#include <NTPStatus.h>
#include <OTASettingsService.h>
#include <APStatus.h>
#define SERIAL_BAUD_RATE 115200
AsyncWebServer server(80);
WiFiSettingsService wifiSettingsService = WiFiSettingsService(&server, &SPIFFS);
WiFiStatus wifiStatus = WiFiStatus(&server);
WiFiScanner wifiScanner = WiFiScanner(&server);
APSettingsService apSettingsService = APSettingsService(&server, &SPIFFS);
NTPSettingsService ntpSettingsService = NTPSettingsService(&server, &SPIFFS);
OTASettingsService otaSettingsService = OTASettingsService(&server, &SPIFFS);
NTPStatus ntpStatus = NTPStatus(&server);
APStatus apStatus = APStatus(&server);
void setup() {
// Disable wifi config persistance
WiFi.persistent(false);
Serial.begin(SERIAL_BAUD_RATE);
SPIFFS.begin();
// start services
ntpSettingsService.begin();
otaSettingsService.begin();
apSettingsService.begin();
wifiSettingsService.begin();
// Serving static resources from /www/
server.serveStatic("/js/", SPIFFS, "/www/js/");
server.serveStatic("/css/", SPIFFS, "/www/css/");
server.serveStatic("/fonts/", SPIFFS, "/www/fonts/");
server.serveStatic("/app/", SPIFFS, "/www/app/");
// Serving all other get requests with "/www/index.htm"
server.onNotFound([](AsyncWebServerRequest *request) {
if (request->method() == HTTP_GET) {
request->send(SPIFFS, "/www/index.html");
} else {
request->send(404);
}
});
server.begin();
}
void loop() {
apSettingsService.loop();
ntpSettingsService.loop();
otaSettingsService.loop();
}