WIP - some documentation

This commit is contained in:
Rick Watson 2019-08-10 12:35:26 +01:00
parent 8a37302ced
commit 15ae0bb248
10 changed files with 140 additions and 55 deletions

View File

@ -42,8 +42,9 @@ Resource | Description
---- | -----------
[data/](data) | The file system image directory
[interface/](interface) | React based front end
[src/](src) | C++ back end for the ESP8266 device
[src/](src) | The main.cpp and demo project to get you started
[platformio.ini](platformio.ini) | PlatformIO project configuration file
[lib/framework/](lib/framework) | C++ back end for the ESP8266 device
### Building the firmware
@ -247,13 +248,90 @@ There is also a manifest file which contains the app name to use when adding the
}
```
## Back End Overview
## Back end overview
The back end is a set of REST endpoints hosted by a [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) instance. The source is split up by feature, for example [WiFiScanner.h](src/WiFiScanner.h) implements the end points for scanning for available networks.
The back end is a set of REST endpoints hosted by a [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) instance. The ['lib/framework'](lib/framework) directory contains the majority of the back end code. The framework contains of a number of useful utility classes which you can use when extending it. The project also comes with a demo project to give you some help getting started.
There is an abstract class [SettingsService.h](src/SettingsService.h) that provides an easy means of adding configurable services/features to the device. It takes care of writing the settings as JSON to SPIFFS. All you need to do is extend the class with your required configuration and implement the functions which serialize the settings to/from JSON. JSON serialization utilizes the excellent [ArduinoJson](https://github.com/bblanchon/ArduinoJson) library.
The framework's source is split up by feature, for example [WiFiScanner.h](lib/framework/WiFiScanner.h) implements the end points for scanning for available networks where as [WiFiSettingsService.h](lib/framework/WiFiSettingsService.h) handles configuring the WiFi settings and managing the WiFi connection.
Here is a example of a service with username and password settings:
### Initializing the framework
The ['src/main.cpp'](src/main.cpp) file constructs the webserver and initializes the framework. You can add endpoints to the server here to support your IoT project. The main loop is also accessable so you can run your own code easily.
The following code creates the web server, esp8266React framework and the demo project instance:
```cpp
AsyncWebServer server(80);
ESP8266React framework(&SPIFFS);
DemoProject demoProject = DemoProject(&SPIFFS, framework.getSecurityManager());
```
Now in the `setup()` function the initialization is performed:
```cpp
void setup() {
// start serial and filesystem
Serial.begin(SERIAL_BAUD_RATE);
SPIFFS.begin();
// set up the framework
framework.init(&server);
// begin the demo project
demoProject.init(&server);
// start the server
server.begin();
}
```
Finally the loop calls the framework's loop function to service the frameworks features. You can add your own code in here, as shown with the demo project:
```cpp
void loop() {
// run the framework's loop function
framework.loop();
// run the demo project's loop function
demoProject.loop();
}
```
### Adding endpoints
There are some simple classes that support adding configurable services/features to the device:
Class | Description
----- | -----------
[SimpleService.h](lib/framework/SimpleService.h) | Exposes an endpoint to read and write settings as JSON. Extend this class and implement the functions which serialize the settings to/from JSON.
[SettingsService.h](lib/framework/SettingsService.h) | As above, however this class also handles persisting the settings as JSON to the file system.
[AdminSettingsService.h](lib/framework/AdminSettingsService.h) | Extends SettingsService to secure the endpoint to administrators only, the authentication predicate can be overridden if required.
The demo project shows how these can be used, explore the framework classes for more examples.
### Security features
The framework has security features to prevent unauthorized use of the device. This is driven by [SecurityManager.h](lib/framework/SecurityManager.h).
On successful authentication, the /rest/signIn endpoint issues a JWT which is then sent using Bearer Authentication. The framework come with built in predicates for verifying a users access level. The built in AuthenticationPredicates can be found in [SecurityManager.h](lib/framework/SecurityManager.h):
Predicate | Description
-------------------- | -----------
NONE_REQUIRED | No authentication is required.
IS_AUTHENTICATED | Any authentication is permitted.
IS_AUTHENTICATED | Any authentication is permitted.
You can use the security manager to wrap any web handler with an authentication predicate:
```cpp
server->on("/rest/someService", HTTP_GET,
_securityManager->wrapRequest(std::bind(&SomeService::someService, this, std::placeholders::_1), AuthenticationPredicates::IS_AUTHENTICATED)
);
```
Alternatively you can extend [AdminSettingsService.h](lib/framework/AdminSettingsService.h) and optionally override `getAuthenticationPredicate()` to secure an endpoint.
## Extending the framework
```cpp
#include <SettingsService.h>
@ -321,6 +399,7 @@ void reconfigureTheService() {
* [React](https://reactjs.org/)
* [Material-UI](https://material-ui-next.com/)
* [notistack](https://github.com/iamhosseindhv/notistack)
* [Time](https://github.com/PaulStoffregen/Time)
* [NtpClient](https://github.com/gmag11/NtpClient)
* [ArduinoJson](https://github.com/bblanchon/ArduinoJson)

View File

@ -1,7 +1,7 @@
#ifndef APSettingsConfig_h
#define APSettingsConfig_h
#include <SettingsService.h>
#include <AdminSettingsService.h>
#include <DNSServer.h>
#include <IPAddress.h>

View File

@ -0,0 +1,46 @@
#ifndef AdminSettingsService_h
#define AdminSettingsService_h
#include <SettingsService.h>
class AdminSettingsService : public SettingsService {
public:
AdminSettingsService(FS* fs, SecurityManager* securityManager, char const* servicePath, char const* filePath):
SettingsService(fs, servicePath, filePath), _securityManager(securityManager) {
}
protected:
// will validate the requests with the security manager
SecurityManager* _securityManager;
void fetchConfig(AsyncWebServerRequest *request) {
// verify the request against the predicate
Authentication authentication = _securityManager->authenticateRequest(request);
if (!getAuthenticationPredicate()(authentication)) {
request->send(401);
return;
}
// delegate to underlying implemetation
SettingsService::fetchConfig(request);
}
void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) {
// verify the request against the predicate
Authentication authentication = _securityManager->authenticateRequest(request);
if (!getAuthenticationPredicate()(authentication)) {
request->send(401);
return;
}
// delegate to underlying implemetation
SettingsService::updateConfig(request, jsonDocument);
}
// override this to replace the default authentication predicate, IS_ADMIN
AuthenticationPredicate getAuthenticationPredicate() {
return AuthenticationPredicates::IS_ADMIN;
}
};
#endif // end AdminSettingsService

View File

@ -1,7 +1,7 @@
#ifndef NTPSettingsService_h
#define NTPSettingsService_h
#include <SettingsService.h>
#include <AdminSettingsService.h>
#include <TimeLib.h>
#include <NtpClientLib.h>

View File

@ -1,7 +1,7 @@
#ifndef OTASettingsService_h
#define OTASettingsService_h
#include <SettingsService.h>
#include <AdminSettingsService.h>
#if defined(ESP8266)
#include <ESP8266mDNS.h>

View File

@ -1,7 +1,7 @@
#ifndef SecuritySettingsService_h
#define SecuritySettingsService_h
#include <SettingsService.h>
#include <AdminSettingsService.h>
#include <SecurityManager.h>
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"

View File

@ -78,43 +78,4 @@ protected:
};
class AdminSettingsService : public SettingsService {
public:
AdminSettingsService(FS* fs, SecurityManager* securityManager, char const* servicePath, char const* filePath):
SettingsService(fs, servicePath, filePath), _securityManager(securityManager) {
}
protected:
// will validate the requests with the security manager
SecurityManager* _securityManager;
void fetchConfig(AsyncWebServerRequest *request) {
// verify the request against the predicate
Authentication authentication = _securityManager->authenticateRequest(request);
if (!getAuthenticationPredicate()(authentication)) {
request->send(401);
return;
}
// delegate to underlying implemetation
SettingsService::fetchConfig(request);
}
void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument) {
// verify the request against the predicate
Authentication authentication = _securityManager->authenticateRequest(request);
if (!getAuthenticationPredicate()(authentication)) {
request->send(401);
return;
}
// delegate to underlying implemetation
SettingsService::updateConfig(request, jsonDocument);
}
// override to override the default authentication predicate, IS_ADMIN
AuthenticationPredicate getAuthenticationPredicate() {
return AuthenticationPredicates::IS_ADMIN;
}
};
#endif // end SettingsService

View File

@ -1,7 +1,7 @@
#ifndef WiFiSettingsService_h
#define WiFiSettingsService_h
#include <SettingsService.h>
#include <AdminSettingsService.h>
#include <IPAddress.h>
#define WIFI_SETTINGS_FILE "/config/wifiSettings.json"

View File

@ -1,7 +1,7 @@
#ifndef DemoProject_h
#define DemoProject_h
#include <SettingsService.h>
#include <AdminSettingsService.h>
#define BLINK_LED 2
#define MAX_DELAY 1000

View File

@ -5,9 +5,8 @@
#define SERIAL_BAUD_RATE 115200
AsyncWebServer server(80);
ESP8266React espServer(&SPIFFS);
DemoProject demoProject = DemoProject(&SPIFFS, espServer.getSecurityManager());
ESP8266React framework(&SPIFFS);
DemoProject demoProject = DemoProject(&SPIFFS, framework.getSecurityManager());
void setup() {
// start serial and filesystem
@ -15,7 +14,7 @@ void setup() {
SPIFFS.begin();
// set up the framework
espServer.init(&server);
framework.init(&server);
// begin the demo project
demoProject.init(&server);
@ -26,7 +25,7 @@ void setup() {
void loop() {
// run the framework's loop function
espServer.loop();
framework.loop();
// run the demo project's loop function
demoProject.loop();