first commit using PsychicHttp

This commit is contained in:
Proddy
2023-12-25 13:27:02 +01:00
parent 68cb94547e
commit 73a51ae4ad
169 changed files with 7162 additions and 12208 deletions

View File

@@ -2,8 +2,10 @@
#include "../../src/emsesp_stub.hpp"
APSettingsService::APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager)
APSettingsService::APSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager)
, _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE)
, _dnsServer(nullptr)
, _lastManaged(0)
@@ -19,6 +21,10 @@ void APSettingsService::begin() {
// reconfigureAP();
}
void APSettingsService::registerURI() {
_httpEndpoint.registerURI();
}
// wait 10 sec on STA disconnect before starting AP
void APSettingsService::WiFiEvent(WiFiEvent_t event) {
uint8_t was_connected = _connected;

View File

@@ -4,6 +4,7 @@
#include <HttpEndpoint.h>
#include <FSPersistence.h>
#include <JsonUtils.h>
#include <WiFi.h>
#include <DNSServer.h>
#include <IPAddress.h>
@@ -118,17 +119,21 @@ class APSettings {
class APSettingsService : public StatefulService<APSettings> {
public:
APSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
APSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager);
void begin();
void loop();
void registerURI();
void begin();
void loop();
APNetworkStatus getAPNetworkStatus();
private:
SecurityManager * _securityManager;
PsychicHttpServer * _server;
HttpEndpoint<APSettings> _httpEndpoint;
FSPersistence<APSettings> _fsPersistence;
// for the captive portal
DNSServer * _dnsServer;
// for the management delay loop

View File

@@ -2,22 +2,26 @@
using namespace std::placeholders; // for `_1` etc
APStatus::APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService)
: _apSettingsService(apSettingsService) {
server->on(AP_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
APStatus::APStatus(PsychicHttpServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService)
: _apSettingsService(apSettingsService)
, _server(server)
, _securityManager(securityManager) {
}
void APStatus::apStatus(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_AP_STATUS_SIZE);
JsonObject root = response->getRoot();
void APStatus::registerURI() {
_server->on(AP_STATUS_SERVICE_PATH,
HTTP_GET,
_securityManager->wrapRequest(std::bind(&APStatus::apStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
}
esp_err_t APStatus::apStatus(PsychicRequest * request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_AP_STATUS_SIZE);
JsonObject root = response.getRoot();
root["status"] = _apSettingsService->getAPNetworkStatus();
root["ip_address"] = WiFi.softAPIP().toString();
root["mac_address"] = WiFi.softAPmacAddress();
root["station_num"] = WiFi.softAPgetStationNum();
response->setLength();
request->send(response);
return response.send();
}

View File

@@ -2,10 +2,9 @@
#define APStatus_h
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <IPAddress.h>
#include <SecurityManager.h>
#include <APSettingsService.h>
@@ -15,11 +14,16 @@
class APStatus {
public:
APStatus(AsyncWebServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService);
APStatus(PsychicHttpServer * server, SecurityManager * securityManager, APSettingsService * apSettingsService);
void registerURI();
private:
SecurityManager * _securityManager;
PsychicHttpServer * _server;
APSettingsService * _apSettingsService;
void apStatus(AsyncWebServerRequest * request);
esp_err_t apStatus(PsychicRequest * request);
};
#endif

View File

@@ -2,46 +2,31 @@
using namespace std::placeholders; // for `_1` etc
#if FT_ENABLED(FT_SECURITY)
AuthenticationService::AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager)
, _signInHandler(SIGN_IN_PATH, std::bind(&AuthenticationService::signIn, this, _1, _2)) {
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, std::bind(&AuthenticationService::verifyAuthorization, this, _1));
_signInHandler.setMethod(HTTP_POST);
_signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE);
server->addHandler(&_signInHandler);
AuthenticationService::AuthenticationService(PsychicHttpServer * server, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager) {
}
/**
* Verifies that the request supplied a valid JWT.
*/
void AuthenticationService::verifyAuthorization(AsyncWebServerRequest * request) {
Authentication authentication = _securityManager->authenticateRequest(request);
request->send(authentication.authenticated ? 200 : 401);
}
/**
* Signs in a user if the username and password match. Provides a JWT to be used in the Authorization header in
* subsequent requests.
*/
void AuthenticationService::signIn(AsyncWebServerRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
String username = json["username"];
String password = json["password"];
Authentication authentication = _securityManager->authenticate(username, password);
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);
response->setLength();
request->send(response);
return;
void AuthenticationService::registerURI() {
// Signs in a user if the username and password match. Provides a JWT to be used in the Authorization header in subsequent requests
_server->on(SIGN_IN_PATH, HTTP_POST, [this](PsychicRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
String username = json["username"];
String password = json["password"];
Authentication authentication = _securityManager->authenticate(username, password);
if (authentication.authenticated) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, 256);
JsonObject root = response.getRoot();
root["access_token"] = _securityManager->generateJWT(authentication.user);
return response.send();
}
}
}
AsyncWebServerResponse * response = request->beginResponse(401);
request->send(response);
}
return request->reply(401);
});
#endif
// Verifies that the request supplied a valid JWT
_server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, [this](PsychicRequest * request) {
Authentication authentication = _securityManager->authenticateRequest(request);
return request->reply(authentication.authenticated ? 200 : 401);
});
}

View File

@@ -2,29 +2,20 @@
#define AuthenticationService_H_
#include <Features.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
#define SIGN_IN_PATH "/rest/signIn"
#define MAX_AUTHENTICATION_SIZE 256
#if FT_ENABLED(FT_SECURITY)
class AuthenticationService {
public:
AuthenticationService(AsyncWebServer * server, SecurityManager * securityManager);
AuthenticationService(PsychicHttpServer * server, SecurityManager * securityManager);
void registerURI();
private:
SecurityManager * _securityManager;
AsyncCallbackJsonWebHandler _signInHandler;
// endpoint functions
void signIn(AsyncWebServerRequest * request, JsonVariant & json);
void verifyAuthorization(AsyncWebServerRequest * request);
SecurityManager * _securityManager;
PsychicHttpServer * _server;
};
#endif
#endif

View File

@@ -1,13 +1,11 @@
#include <ESP8266React.h>
#include <WWWData.h>
ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
: _featureService(server)
, _securitySettingsService(server, fs)
, _networkSettingsService(server, fs, &_securitySettingsService)
, _wifiScanner(server, &_securitySettingsService)
ESP8266React::ESP8266React(PsychicHttpServer * server, FS * fs)
: _networkSettingsService(server, fs, &_securitySettingsService)
, _networkStatus(server, &_securitySettingsService)
, _featureService(server)
, _securitySettingsService(server, fs)
, _wifiScanner(server, &_securitySettingsService)
, _apSettingsService(server, fs, &_securitySettingsService)
, _apStatus(server, &_securitySettingsService, &_apSettingsService)
, _ntpSettingsService(server, fs, &_securitySettingsService)
@@ -20,40 +18,11 @@ ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
, _restartService(server, &_securitySettingsService)
, _factoryResetService(server, fs, &_securitySettingsService)
, _systemStatus(server, &_securitySettingsService) {
// Serve static resources from PROGMEM
WWWData::registerRoutes([server, this](const String & uri, const String & contentType, const uint8_t * content, size_t len) {
ArRequestHandlerFunction requestHandler = [contentType, content, len](AsyncWebServerRequest * request) {
AsyncWebServerResponse * response = request->beginResponse_P(200, contentType, content, len);
response->addHeader("Content-Encoding", "gzip");
// response->addHeader("Content-Encoding", "br"); // only works over HTTPS
request->send(response);
};
server->on(uri.c_str(), HTTP_GET, requestHandler);
// Serving non matching get requests with "/index.html"
// OPTIONS get a straight up 200 response
if (uri.equals("/index.html")) {
server->onNotFound([requestHandler](AsyncWebServerRequest * request) {
if (request->method() == HTTP_GET) {
requestHandler(request);
} else if (request->method() == HTTP_OPTIONS) {
request->send(200);
} else {
request->send(404);
}
});
}
});
}
// register services
void ESP8266React::begin() {
_networkSettingsService.begin();
_networkSettingsService.read([&](NetworkSettings & networkSettings) {
if (networkSettings.enableCORS) {
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", networkSettings.CORSOrigin);
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true");
}
});
_apSettingsService.begin();
_ntpSettingsService.begin();
_otaSettingsService.begin();
@@ -61,6 +30,35 @@ void ESP8266React::begin() {
_securitySettingsService.begin();
}
// create the web server endpoints
void ESP8266React::registerURI() {
_featureService.registerURI();
_authenticationService.registerURI();
_systemStatus.registerURI();
_networkSettingsService.registerURI();
_networkStatus.registerURI();
_apSettingsService.registerURI();
_apStatus.registerURI();
_ntpSettingsService.registerURI();
_ntpStatus.registerURI();
_mqttSettingsService.registerURI();
_mqttStatus.registerURI();
_securitySettingsService.registerURI();
_otaSettingsService.registerURI();
_restartService.registerURI();
_factoryResetService.registerURI();
_wifiScanner.registerURI();
_uploadFileService.registerURI();
}
void ESP8266React::loop() {
_networkSettingsService.loop();
_apSettingsService.loop();

View File

@@ -2,8 +2,6 @@
#define ESP8266React_h
#include <Arduino.h>
#include <AsyncTCP.h>
#include <WiFi.h>
#include <FeaturesService.h>
@@ -26,10 +24,11 @@
class ESP8266React {
public:
ESP8266React(AsyncWebServer * server, FS * fs);
ESP8266React(PsychicHttpServer * server, FS * fs);
void begin();
void loop();
void registerURI();
SecurityManager * getSecurityManager() {
return &_securitySettingsService;

View File

@@ -63,6 +63,10 @@ class FSPersistence {
// serialize it to filesystem
File settingsFile = _fs->open(_filePath, "w");
#ifdef EMSESP_DEBUG
Serial.println("Writing settings to " + String(_filePath));
#endif
// failed to open file, return false
if (!settingsFile || !jsonObject.size()) {
return false;

View File

@@ -2,21 +2,24 @@
using namespace std::placeholders;
FactoryResetService::FactoryResetService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: fs(fs) {
server->on(FACTORY_RESET_SERVICE_PATH,
HTTP_POST,
securityManager->wrapRequest(std::bind(&FactoryResetService::handleRequest, this, _1), AuthenticationPredicates::IS_ADMIN));
FactoryResetService::FactoryResetService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager) {
}
void FactoryResetService::handleRequest(AsyncWebServerRequest * request) {
request->onDisconnect(std::bind(&FactoryResetService::factoryReset, this));
request->send(200);
void FactoryResetService::registerURI() {
_server->on(FACTORY_RESET_SERVICE_PATH,
HTTP_POST,
_securityManager->wrapRequest(std::bind(&FactoryResetService::handleRequest, this, _1), AuthenticationPredicates::IS_ADMIN));
}
/**
* Delete function assumes that all files are stored flat, within the config directory.
*/
esp_err_t FactoryResetService::handleRequest(PsychicRequest * request) {
request->reply(200);
RestartService::restartNow();
return ESP_OK;
}
// Delete function assumes that all files are stored flat, within the config directory.
void FactoryResetService::factoryReset() {
// TODO To replaced with fs.rmdir(FS_CONFIG_DIRECTORY) now we're using IDF 4.2
File root = fs->open(FS_CONFIG_DIRECTORY);

View File

@@ -2,7 +2,7 @@
#define FactoryResetService_h
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#include <RestartService.h>
#include <FS.h>
@@ -14,12 +14,16 @@ class FactoryResetService {
FS * fs;
public:
FactoryResetService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
FactoryResetService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager);
void registerURI();
void factoryReset();
private:
void handleRequest(AsyncWebServerRequest * request);
SecurityManager * _securityManager;
PsychicHttpServer * _server;
esp_err_t handleRequest(PsychicRequest * request);
};
#endif

View File

@@ -1,8 +1,6 @@
#ifndef Features_h
#define Features_h
// modified by Proddy
#define FT_ENABLED(feature) feature
// project feature on by default

View File

@@ -1,19 +1,19 @@
#include <FeaturesService.h>
#include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc
FeaturesService::FeaturesService(AsyncWebServer * server) {
server->on(FEATURES_SERVICE_PATH, HTTP_GET, std::bind(&FeaturesService::features, this, _1));
FeaturesService::FeaturesService(PsychicHttpServer * server)
: _server(server) {
}
void FeaturesService::features(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_FEATURES_SIZE);
JsonObject root = response->getRoot();
void FeaturesService::registerURI() {
// return feature set
_server->on(FEATURES_SERVICE_PATH, HTTP_GET, [this](PsychicRequest * request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, 256);
JsonObject root = response.getRoot();
root["version"] = EMSESP_APP_VERSION;
root["platform"] = EMSESP_PLATFORM;
root["version"] = EMSESP_APP_VERSION;
root["platform"] = EMSESP_PLATFORM;
response->setLength();
request->send(response);
return response.send();
});
}

View File

@@ -5,18 +5,18 @@
#include <WiFi.h>
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#define MAX_FEATURES_SIZE 256
#define FEATURES_SERVICE_PATH "/rest/features"
class FeaturesService {
public:
FeaturesService(AsyncWebServer * server);
FeaturesService(PsychicHttpServer * server);
void registerURI();
private:
void features(AsyncWebServerRequest * request);
PsychicHttpServer * _server;
};
#endif

View File

@@ -3,12 +3,13 @@
#include <functional>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#include <StatefulService.h>
#define HTTP_ENDPOINT_ORIGIN_ID "http"
#define HTTPS_ENDPOINT_ORIGIN_ID "https"
using namespace std::placeholders; // for `_1` etc
@@ -17,40 +18,43 @@ class HttpGetEndpoint {
public:
HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
PsychicHttpServer * server,
const char * servicePath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader)
, _statefulService(statefulService)
, _bufferSize(bufferSize) {
server->on(servicePath.c_str(), HTTP_GET, securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, _1), authenticationPredicate));
}
HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader)
, _statefulService(statefulService)
, _bufferSize(bufferSize) {
server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, _1));
, _bufferSize(bufferSize)
, _server(server)
, _servicePath(servicePath)
, _securityManager(securityManager)
, _authenticationPredicate(authenticationPredicate) {
}
protected:
JsonStateReader<T> _stateReader;
StatefulService<T> * _statefulService;
size_t _bufferSize;
JsonStateReader<T> _stateReader;
StatefulService<T> * _statefulService;
size_t _bufferSize;
PsychicHttpServer * _server;
const char * _servicePath;
SecurityManager * _securityManager;
AuthenticationPredicate _authenticationPredicate;
void fetchSettings(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, _bufferSize);
JsonObject jsonObject = response->getRoot().to<JsonObject>();
_statefulService->read(jsonObject, _stateReader);
response->setLength();
request->send(response);
void registerURI() {
#ifdef EMSESP_DEBUG
ESP_LOGE("HttpGetEndpoint", "Addding GET endpoint %s", _servicePath);
#endif
_server->on(_servicePath,
HTTP_GET,
_securityManager->wrapRequest(
[this](PsychicRequest * request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, _bufferSize);
JsonObject jsonObject = response.getRoot();
_statefulService->read(jsonObject, _stateReader);
return response.send();
},
_authenticationPredicate));
}
};
@@ -60,63 +64,67 @@ class HttpPostEndpoint {
HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
PsychicHttpServer * server,
const char * servicePath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader)
, _stateUpdater(stateUpdater)
, _statefulService(statefulService)
, _updateHandler(servicePath, securityManager->wrapCallback(std::bind(&HttpPostEndpoint::updateSettings, this, _1, _2), authenticationPredicate), bufferSize)
, _server(server)
, _servicePath(servicePath)
, _securityManager(securityManager)
, _authenticationPredicate(authenticationPredicate)
, _bufferSize(bufferSize) {
_updateHandler.setMethod(HTTP_POST);
server->addHandler(&_updateHandler);
}
HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader)
, _stateUpdater(stateUpdater)
, _statefulService(statefulService)
, _updateHandler(servicePath, std::bind(&HttpPostEndpoint::updateSettings, this, _1, _2), bufferSize)
, _bufferSize(bufferSize) {
_updateHandler.setMethod(HTTP_POST);
server->addHandler(&_updateHandler);
}
protected:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> * _statefulService;
AsyncCallbackJsonWebHandler _updateHandler;
size_t _bufferSize;
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> * _statefulService;
size_t _bufferSize;
SecurityManager * _securityManager;
AuthenticationPredicate _authenticationPredicate;
PsychicHttpServer * _server;
const char * _servicePath;
void updateSettings(AsyncWebServerRequest * request, JsonVariant & json) {
if (!json.is<JsonObject>()) {
request->send(400);
return;
}
JsonObject jsonObject = json.as<JsonObject>();
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
if (outcome == StateUpdateResult::ERROR) {
request->send(400);
return;
} else if ((outcome == StateUpdateResult::CHANGED) || (outcome == StateUpdateResult::CHANGED_RESTART)) {
request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); });
}
AsyncJsonResponse * response = new AsyncJsonResponse(false, _bufferSize);
jsonObject = response->getRoot().to<JsonObject>();
_statefulService->read(jsonObject, _stateReader);
if (outcome == StateUpdateResult::CHANGED_RESTART) {
response->setCode(205); // reboot required
}
response->setLength();
request->send(response);
void registerURI() {
#ifdef EMSESP_DEBUG
ESP_LOGE("HttpPostEndpoint", "Addding POST endpoint %s", _servicePath);
#endif
_server->on(_servicePath,
HTTP_POST,
_securityManager->wrapCallback(
[this](PsychicRequest * request, JsonVariant & json) {
if (!json.is<JsonObject>()) {
return request->reply(400);
}
JsonObject jsonObject = json.as<JsonObject>();
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
if (outcome == StateUpdateResult::ERROR) {
return request->reply(400);
} else if ((outcome == StateUpdateResult::CHANGED) || (outcome == StateUpdateResult::CHANGED_RESTART)) {
// TODO see if this works as intended. Before the stat was updated on an onDisconnect
// request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); });
// TODO add https
_statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); // persist the changes to the FS
}
PsychicJsonResponse response = PsychicJsonResponse(request, false, _bufferSize);
jsonObject = response.getRoot();
_statefulService->read(jsonObject, _stateReader);
if (outcome == StateUpdateResult::CHANGED_RESTART) {
return request->reply(205); // reboot required
}
return response.send();
},
_authenticationPredicate));
}
};
@@ -126,8 +134,8 @@ class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
PsychicHttpServer * server,
const char * servicePath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
@@ -135,14 +143,10 @@ class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
, HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, securityManager, authenticationPredicate, bufferSize) {
}
HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, bufferSize)
, HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) {
// register the web server on() endpoints
void registerURI() {
HttpGetEndpoint<T>::registerURI();
HttpPostEndpoint<T>::registerURI();
}
};

View File

@@ -24,8 +24,10 @@ static char * retainCstr(const char * cstr, char ** ptr) {
return *ptr;
}
MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager)
MqttSettingsService::MqttSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager)
, _httpEndpoint(MqttSettings::read, MqttSettings::update, this, server, MQTT_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(MqttSettings::read, MqttSettings::update, this, fs, MQTT_SETTINGS_FILE)
, _retainedHost(nullptr)
, _retainedClientId(nullptr)
@@ -47,6 +49,10 @@ void MqttSettingsService::begin() {
startClient();
}
void MqttSettingsService::registerURI() {
_httpEndpoint.registerURI();
}
void MqttSettingsService::startClient() {
static bool isSecure = false;
if (_mqttClient != nullptr) {
@@ -218,8 +224,10 @@ bool MqttSettingsService::configureMqtt() {
void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
#if CONFIG_IDF_TARGET_ESP32S3
#ifndef TASMOTA_SDK
root["enableTLS"] = settings.enableTLS;
root["rootCA"] = settings.rootCA;
#endif
#endif
root["enabled"] = settings.enabled;
root["host"] = settings.host;
@@ -255,8 +263,10 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
bool changed = false;
#if CONFIG_IDF_TARGET_ESP32S3
#ifndef TASMOTA_SDK
newSettings.enableTLS = root["enableTLS"] | false;
newSettings.rootCA = root["rootCA"] | "";
#endif
#endif
newSettings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
newSettings.host = root["host"] | FACTORY_MQTT_HOST;

View File

@@ -8,6 +8,8 @@
#include <ESPUtils.h>
#include <uuid/common.h>
#include <esp_wps.h>
#include <WiFi.h>
#define MQTT_RECONNECTION_DELAY 2000 // 2 seconds
@@ -103,12 +105,14 @@ class MqttSettings {
class MqttSettingsService : public StatefulService<MqttSettings> {
public:
MqttSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
MqttSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager);
~MqttSettingsService();
void begin();
void startClient();
void loop();
void begin();
void startClient();
void loop();
void registerURI();
bool isEnabled();
bool isConnected();
const char * getClientId();
@@ -120,6 +124,9 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
void onConfigUpdated();
private:
SecurityManager * _securityManager;
PsychicHttpServer * _server;
HttpEndpoint<MqttSettings> _httpEndpoint;
FSPersistence<MqttSettings> _fsPersistence;

View File

@@ -4,16 +4,21 @@
using namespace std::placeholders; // for `_1` etc
MqttStatus::MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager)
: _mqttSettingsService(mqttSettingsService) {
server->on(MQTT_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
MqttStatus::MqttStatus(PsychicHttpServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager)
: _mqttSettingsService(mqttSettingsService)
, _server(server)
, _securityManager(securityManager) {
}
void MqttStatus::mqttStatus(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_MQTT_STATUS_SIZE);
JsonObject root = response->getRoot();
void MqttStatus::registerURI() {
_server->on(MQTT_STATUS_SERVICE_PATH,
HTTP_GET,
_securityManager->wrapRequest(std::bind(&MqttStatus::mqttStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
}
esp_err_t MqttStatus::mqttStatus(PsychicRequest * request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_MQTT_STATUS_SIZE);
JsonObject root = response.getRoot();
root["enabled"] = _mqttSettingsService->isEnabled();
root["connected"] = _mqttSettingsService->isConnected();
@@ -24,6 +29,5 @@ void MqttStatus::mqttStatus(AsyncWebServerRequest * request) {
root["mqtt_fails"] = emsesp::Mqtt::publish_fails();
root["connect_count"] = emsesp::Mqtt::connect_count();
response->setLength();
request->send(response);
return response.send();
}

View File

@@ -4,7 +4,7 @@
#include <WiFi.h>
#include <MqttSettingsService.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#define MAX_MQTT_STATUS_SIZE 1024
@@ -12,12 +12,16 @@
class MqttStatus {
public:
MqttStatus(AsyncWebServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager);
MqttStatus(PsychicHttpServer * server, MqttSettingsService * mqttSettingsService, SecurityManager * securityManager);
void registerURI();
private:
SecurityManager * _securityManager;
PsychicHttpServer * _server;
MqttSettingsService * _mqttSettingsService;
void mqttStatus(AsyncWebServerRequest * request);
esp_err_t mqttStatus(PsychicRequest * request);
};
#endif

View File

@@ -4,16 +4,12 @@
using namespace std::placeholders; // for `_1` etc
NTPSettingsService::NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE)
, _timeHandler(TIME_PATH, securityManager->wrapCallback(std::bind(&NTPSettingsService::configureTime, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) {
_timeHandler.setMethod(HTTP_POST);
_timeHandler.setMaxContentLength(MAX_TIME_SIZE);
server->addHandler(&_timeHandler);
NTPSettingsService::NTPSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager)
, _httpEndpoint(NTPSettings::read, NTPSettings::update, this, server, NTP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(NTPSettings::read, NTPSettings::update, this, fs, NTP_SETTINGS_FILE) {
WiFi.onEvent(std::bind(&NTPSettingsService::WiFiEvent, this, _1));
addUpdateHandler([&](const String & originId) { configureNTP(); }, false);
}
@@ -22,6 +18,14 @@ void NTPSettingsService::begin() {
configureNTP();
}
void NTPSettingsService::registerURI() {
_httpEndpoint.registerURI();
_server->on(TIME_PATH,
HTTP_POST,
_securityManager->wrapCallback(std::bind(&NTPSettingsService::configureTime, this, _1, _2), AuthenticationPredicates::IS_ADMIN));
}
// handles both WiFI and Ethernet
void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
switch (event) {
@@ -61,7 +65,7 @@ void NTPSettingsService::configureNTP() {
}
}
void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVariant & json) {
esp_err_t NTPSettingsService::configureTime(PsychicRequest * request, JsonVariant & json) {
if (json.is<JsonObject>()) {
struct tm tm = {0};
String timeLocal = json["local_time"];
@@ -71,14 +75,11 @@ void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVari
time_t time = mktime(&tm);
struct timeval now = {.tv_sec = time};
settimeofday(&now, nullptr);
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
return;
return request->reply(200);
}
}
AsyncWebServerResponse * response = request->beginResponse(400);
request->send(response);
return request->reply(400);
}
void NTPSettingsService::ntp_received(struct timeval * tv) {

View File

@@ -1,6 +1,8 @@
#ifndef NTPSettingsService_h
#define NTPSettingsService_h
#include <WiFi.h>
#include <HttpEndpoint.h>
#include <FSPersistence.h>
@@ -26,7 +28,6 @@
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
#define MAX_TIME_SIZE 256
#define TIME_PATH "/rest/time"
class NTPSettings {
@@ -54,20 +55,26 @@ class NTPSettings {
class NTPSettingsService : public StatefulService<NTPSettings> {
public:
NTPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
NTPSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager);
void begin();
void registerURI();
void begin();
static void ntp_received(struct timeval * tv);
private:
HttpEndpoint<NTPSettings> _httpEndpoint;
FSPersistence<NTPSettings> _fsPersistence;
AsyncCallbackJsonWebHandler _timeHandler;
SecurityManager * _securityManager;
PsychicHttpServer * _server;
HttpEndpoint<NTPSettings> _httpEndpoint;
FSPersistence<NTPSettings> _fsPersistence;
bool connected_ = false;
void WiFiEvent(WiFiEvent_t event);
void configureNTP();
void configureTime(AsyncWebServerRequest * request, JsonVariant & json);
// POST
esp_err_t configureTime(PsychicRequest * request, JsonVariant & json);
};
#endif

View File

@@ -3,10 +3,16 @@
using namespace std::placeholders; // for `_1` etc
NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(NTP_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
NTPStatus::NTPStatus(PsychicHttpServer * server, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager) {
}
void NTPStatus::registerURI() {
_server->on(NTP_STATUS_SERVICE_PATH,
HTTP_GET,
_securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
}
/*
@@ -28,9 +34,9 @@ String toLocalTimeString(tm * time) {
return formatTime(time, "%FT%T");
}
void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_NTP_STATUS_SIZE);
JsonObject root = response->getRoot();
esp_err_t NTPStatus::ntpStatus(PsychicRequest * request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_NTP_STATUS_SIZE);
JsonObject root = response.getRoot();
// grab the current instant in unix seconds
time_t now = time(nullptr);
@@ -47,6 +53,5 @@ void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
// the sntp server name
root["server"] = esp_sntp_getservername(0);
response->setLength();
request->send(response);
return response.send();
}

View File

@@ -7,7 +7,7 @@
#include <esp_sntp.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#include <uuid/common.h>
@@ -16,10 +16,14 @@
class NTPStatus {
public:
NTPStatus(AsyncWebServer * server, SecurityManager * securityManager);
NTPStatus(PsychicHttpServer * server, SecurityManager * securityManager);
void registerURI();
private:
void ntpStatus(AsyncWebServerRequest * request);
SecurityManager * _securityManager;
PsychicHttpServer * _server;
esp_err_t ntpStatus(PsychicRequest * request);
};
#endif

View File

@@ -2,7 +2,7 @@
using namespace std::placeholders; // for `_1` etc
NetworkSettingsService::NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
NetworkSettingsService::NetworkSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(NetworkSettings::read, NetworkSettings::update, this, server, NETWORK_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(NetworkSettings::read, NetworkSettings::update, this, fs, NETWORK_SETTINGS_FILE)
, _lastConnectionAttempt(0) {
@@ -23,14 +23,18 @@ void NetworkSettingsService::begin() {
WiFi.mode(WIFI_MODE_MAX);
WiFi.mode(WIFI_MODE_NULL);
// WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // default is FAST_SCAN, connect issues in 2.0.14
// WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); // is default, no need to set
_fsPersistence.readFromFS();
// reconfigureWiFiConnection();
}
void NetworkSettingsService::registerURI() {
_httpEndpoint.registerURI();
}
void NetworkSettingsService::reconfigureWiFiConnection() {
// do not disconnect for switching to eth, restart is needed
if (WiFi.isConnected() && _state.ssid.length() == 0) {

View File

@@ -29,7 +29,7 @@
class NetworkSettings {
public:
// core wifi configuration
// core network configuration
String ssid;
String bssid;
String password;
@@ -119,12 +119,15 @@ class NetworkSettings {
class NetworkSettingsService : public StatefulService<NetworkSettings> {
public:
NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
NetworkSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager);
void begin();
void loop();
void registerURI();
private:
SecurityManager * _securityManager;
PsychicHttpServer * _server;
HttpEndpoint<NetworkSettings> _httpEndpoint;
FSPersistence<NetworkSettings> _fsPersistence;
unsigned long _lastConnectionAttempt;

View File

@@ -4,15 +4,20 @@
using namespace std::placeholders; // for `_1` etc
NetworkStatus::NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(NETWORK_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&NetworkStatus::networkStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
NetworkStatus::NetworkStatus(PsychicHttpServer * server, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager) {
}
void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_NETWORK_STATUS_SIZE);
JsonObject root = response->getRoot();
void NetworkStatus::registerURI() {
_server->on(NETWORK_STATUS_SERVICE_PATH,
HTTP_GET,
_securityManager->wrapRequest(std::bind(&NetworkStatus::networkStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
}
esp_err_t NetworkStatus::networkStatus(PsychicRequest * request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_NETWORK_STATUS_SIZE);
JsonObject root = response.getRoot();
bool ethernet_connected = emsesp::EMSESP::system_.ethernet_connected();
wl_status_t wifi_status = WiFi.status();
@@ -64,6 +69,5 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
}
}
response->setLength();
request->send(response);
return response.send();
}

View File

@@ -2,12 +2,11 @@
#define NetworkStatus_h
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ETH.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <IPAddress.h>
#include <IPUtils.h>
#include <SecurityManager.h>
@@ -17,10 +16,14 @@
class NetworkStatus {
public:
NetworkStatus(AsyncWebServer * server, SecurityManager * securityManager);
NetworkStatus(PsychicHttpServer * server, SecurityManager * securityManager);
void registerURI();
private:
void networkStatus(AsyncWebServerRequest * request);
SecurityManager * _securityManager;
PsychicHttpServer * _server;
esp_err_t networkStatus(PsychicRequest * request);
};
#endif

View File

@@ -4,8 +4,10 @@
using namespace std::placeholders; // for `_1` etc
OTASettingsService::OTASettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager)
OTASettingsService::OTASettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager)
, _httpEndpoint(OTASettings::read, OTASettings::update, this, server, OTA_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(OTASettings::read, OTASettings::update, this, fs, OTA_SETTINGS_FILE)
, _arduinoOTA(nullptr) {
WiFi.onEvent(std::bind(&OTASettingsService::WiFiEvent, this, _1, _2));
@@ -17,6 +19,10 @@ void OTASettingsService::begin() {
configureArduinoOTA();
}
void OTASettingsService::registerURI() {
_httpEndpoint.registerURI();
}
void OTASettingsService::loop() {
if (_state.enabled && _arduinoOTA) {
_arduinoOTA->handle();

View File

@@ -44,15 +44,19 @@ class OTASettings {
class OTASettingsService : public StatefulService<OTASettings> {
public:
OTASettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
OTASettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager);
void begin();
void loop();
void registerURI();
private:
SecurityManager * _securityManager;
PsychicHttpServer * _server;
HttpEndpoint<OTASettings> _httpEndpoint;
FSPersistence<OTASettings> _fsPersistence;
ArduinoOTAClass * _arduinoOTA;
ArduinoOTAClass * _arduinoOTA;
void configureArduinoOTA();
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);

View File

@@ -5,41 +5,49 @@
using namespace std::placeholders; // for `_1` etc
RestartService::RestartService(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(RESTART_SERVICE_PATH, HTTP_POST, securityManager->wrapRequest(std::bind(&RestartService::restart, this, _1), AuthenticationPredicates::IS_ADMIN));
server->on(PARTITION_SERVICE_PATH,
HTTP_POST,
securityManager->wrapRequest(std::bind(&RestartService::partition, this, _1), AuthenticationPredicates::IS_ADMIN));
RestartService::RestartService(PsychicHttpServer * server, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager) {
}
void RestartService::restart(AsyncWebServerRequest * request) {
void RestartService::registerURI() {
_server->on(RESTART_SERVICE_PATH, HTTP_POST, _securityManager->wrapRequest(std::bind(&RestartService::restart, this, _1), AuthenticationPredicates::IS_ADMIN));
_server->on(PARTITION_SERVICE_PATH,
HTTP_POST,
_securityManager->wrapRequest(std::bind(&RestartService::partition, this, _1), AuthenticationPredicates::IS_ADMIN));
}
esp_err_t RestartService::restart(PsychicRequest * request) {
emsesp::EMSESP::system_.store_nvs_values();
request->onDisconnect(RestartService::restartNow);
request->send(200);
request->reply(200);
restartNow();
return ESP_OK;
}
void RestartService::partition(AsyncWebServerRequest * request) {
esp_err_t RestartService::partition(PsychicRequest * request) {
const esp_partition_t * factory_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
if (factory_partition) {
esp_ota_set_boot_partition(factory_partition);
emsesp::EMSESP::system_.store_nvs_values();
request->onDisconnect(RestartService::restartNow);
request->send(200);
return;
request->reply(200);
restartNow();
return ESP_OK;
}
const esp_partition_t * ota_partition = esp_ota_get_next_update_partition(NULL);
if (!ota_partition) {
request->send(400); // bad request
return;
return request->reply(400); // bad request
}
uint64_t buffer;
esp_partition_read(ota_partition, 0, &buffer, 8);
if (buffer == 0xFFFFFFFFFFFFFFFF) { // partition empty
request->send(400); // bad request
return;
return request->reply(400); // bad request
}
esp_ota_set_boot_partition(ota_partition);
emsesp::EMSESP::system_.store_nvs_values();
request->onDisconnect(RestartService::restartNow);
request->send(200);
request->reply(200);
restartNow();
return ESP_OK;
}

View File

@@ -2,9 +2,8 @@
#define RestartService_h
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#define RESTART_SERVICE_PATH "/rest/restart"
@@ -12,17 +11,21 @@
class RestartService {
public:
RestartService(AsyncWebServer * server, SecurityManager * securityManager);
RestartService(PsychicHttpServer * server, SecurityManager * securityManager);
void registerURI();
static void restartNow() {
WiFi.disconnect(true);
delay(500);
delay(500); // wait for async tcp to catch up
ESP.restart();
}
private:
void restart(AsyncWebServerRequest * request);
void partition(AsyncWebServerRequest * request);
SecurityManager * _securityManager;
PsychicHttpServer * _server;
esp_err_t restart(PsychicRequest * request);
esp_err_t partition(PsychicRequest * request);
};
#endif

View File

@@ -3,9 +3,8 @@
#include <Features.h>
#include <ArduinoJsonJWT.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <ESPUtils.h>
#include <AsyncJson.h>
#include <list>
#ifndef FACTORY_JWT_SECRET
@@ -70,7 +69,6 @@ class AuthenticationPredicates {
class SecurityManager {
public:
#if FT_ENABLED(FT_SECURITY)
/*
* Authenticate, returning the user if found
*/
@@ -81,27 +79,25 @@ class SecurityManager {
*/
virtual String generateJWT(User * user) = 0;
#endif
/*
* Check the request header for the Authorization token
*/
virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0;
virtual Authentication authenticateRequest(PsychicRequest * request) = 0;
/**
* Filter a request with the provided predicate, only returning true if the predicate matches.
*/
virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
virtual PsychicRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
/**
* Wrap the provided request to provide validation against an AuthenticationPredicate.
*/
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
virtual PsychicHttpRequestCallback wrapRequest(PsychicHttpRequestCallback onRequest, AuthenticationPredicate predicate) = 0;
/**
* Wrap the provided json request callback to provide validation against an AuthenticationPredicate.
*/
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
virtual PsychicJsonRequestCallback wrapCallback(PsychicJsonRequestCallback onRequest, AuthenticationPredicate predicate) = 0;
};
#endif

View File

@@ -1,17 +1,13 @@
#include <SecuritySettingsService.h>
#if FT_ENABLED(FT_SECURITY)
#include "../../src/emsesp_stub.hpp"
SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs)
: _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this)
SecuritySettingsService::SecuritySettingsService(PsychicHttpServer * server, FS * fs)
: _server(server)
, _httpEndpoint(SecuritySettings::read, SecuritySettings::update, this, server, SECURITY_SETTINGS_PATH, this)
, _fsPersistence(SecuritySettings::read, SecuritySettings::update, this, fs, SECURITY_SETTINGS_FILE)
, _jwtHandler(FACTORY_JWT_SECRET) {
addUpdateHandler([&](const String & originId) { configureJWTHandler(); }, false);
server->on(GENERATE_TOKEN_PATH,
HTTP_GET,
wrapRequest(std::bind(&SecuritySettingsService::generateToken, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN));
}
void SecuritySettingsService::begin() {
@@ -19,17 +15,20 @@ void SecuritySettingsService::begin() {
configureJWTHandler();
}
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) {
AsyncWebHeader * authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
if (authorizationHeader) {
String value = authorizationHeader->value();
void SecuritySettingsService::registerURI() {
_httpEndpoint.registerURI();
_server->on(GENERATE_TOKEN_PATH, HTTP_GET, wrapRequest(std::bind(&SecuritySettingsService::generateToken, this, _1), AuthenticationPredicates::IS_ADMIN));
}
Authentication SecuritySettingsService::authenticateRequest(PsychicRequest * request) {
if (request->hasHeader(AUTHORIZATION_HEADER)) {
auto value = request->header(AUTHORIZATION_HEADER);
if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
return authenticateJWT(value);
}
} else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) {
AsyncWebParameter * tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER);
String value = tokenParamater->value();
String value = request->getParam(ACCESS_TOKEN_PARAMATER)->value();
return authenticateJWT(value);
}
return Authentication();
@@ -82,76 +81,42 @@ String SecuritySettingsService::generateJWT(User * user) {
return _jwtHandler.buildJWT(payload);
}
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
return [this, predicate](AsyncWebServerRequest * request) {
PsychicRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
return [this, predicate](PsychicRequest * request) {
Authentication authentication = authenticateRequest(request);
return predicate(authentication);
};
}
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
return [this, onRequest, predicate](AsyncWebServerRequest * request) {
PsychicHttpRequestCallback SecuritySettingsService::wrapRequest(PsychicHttpRequestCallback onRequest, AuthenticationPredicate predicate) {
return [this, onRequest, predicate](PsychicRequest * request) {
Authentication authentication = authenticateRequest(request);
if (!predicate(authentication)) {
request->send(401);
return;
return request->reply(401);
}
onRequest(request);
return onRequest(request);
};
}
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
return [this, onRequest, predicate](AsyncWebServerRequest * request, JsonVariant & json) {
PsychicJsonRequestCallback SecuritySettingsService::wrapCallback(PsychicJsonRequestCallback onRequest, AuthenticationPredicate predicate) {
return [this, onRequest, predicate](PsychicRequest * request, JsonVariant & json) {
Authentication authentication = authenticateRequest(request);
if (!predicate(authentication)) {
request->send(401);
return;
return request->reply(401);
}
onRequest(request, json);
return onRequest(request, json);
};
}
void SecuritySettingsService::generateToken(AsyncWebServerRequest * request) {
AsyncWebParameter * usernameParam = request->getParam("username");
esp_err_t SecuritySettingsService::generateToken(PsychicRequest * request) {
String usernameParam = request->getParam("username")->value();
for (User _user : _state.users) {
if (_user.username == usernameParam->value()) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, GENERATE_TOKEN_SIZE);
JsonObject root = response->getRoot();
if (_user.username == usernameParam) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, GENERATE_TOKEN_SIZE);
JsonObject root = response.getRoot();
root["token"] = generateJWT(&_user);
response->setLength();
request->send(response);
return;
return response.send();
}
}
request->send(401);
return request->reply(401);
}
#else
User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs)
: SecurityManager() {
}
SecuritySettingsService::~SecuritySettingsService() {
}
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
return [this, predicate](AsyncWebServerRequest * request) { return true; };
}
// Return the admin user on all request - disabling security features
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest * request) {
return Authentication(ADMIN_USER);
}
// Return the function unwrapped
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
return onRequest;
}
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) {
return onRequest;
}
#endif

View File

@@ -28,8 +28,6 @@
#define GENERATE_TOKEN_SIZE 512
#define GENERATE_TOKEN_PATH "/rest/generateToken"
#if FT_ENABLED(FT_SECURITY)
class SecuritySettings {
public:
String jwtSecret;
@@ -69,51 +67,33 @@ class SecuritySettings {
class SecuritySettingsService : public StatefulService<SecuritySettings>, public SecurityManager {
public:
SecuritySettingsService(AsyncWebServer * server, FS * fs);
SecuritySettingsService(PsychicHttpServer * server, FS * fs);
void begin();
void registerURI();
// Functions to implement SecurityManager
Authentication authenticate(const String & username, const String & password);
Authentication authenticateRequest(AsyncWebServerRequest * request);
String generateJWT(User * user);
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate);
Authentication authenticate(const String & username, const String & password);
Authentication authenticateRequest(PsychicRequest * request);
String generateJWT(User * user);
PsychicRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
PsychicHttpRequestCallback wrapRequest(PsychicHttpRequestCallback onRequest, AuthenticationPredicate predicate);
PsychicJsonRequestCallback wrapCallback(PsychicJsonRequestCallback onRequest, AuthenticationPredicate predicate);
private:
PsychicHttpServer * _server;
HttpEndpoint<SecuritySettings> _httpEndpoint;
FSPersistence<SecuritySettings> _fsPersistence;
ArduinoJsonJWT _jwtHandler;
void generateToken(AsyncWebServerRequest * request);
esp_err_t generateToken(PsychicRequest * request);
void configureJWTHandler();
/*
* Lookup the user by JWT
*/
Authentication authenticateJWT(String & jwt);
/*
* Verify the payload is correct
*/
boolean validatePayload(JsonObject & parsedPayload, User * user);
};
#else
class SecuritySettingsService : public SecurityManager {
public:
SecuritySettingsService(AsyncWebServer * server, FS * fs);
~SecuritySettingsService();
// minimal set of functions to support framework with security settings disabled
Authentication authenticateRequest(AsyncWebServerRequest * request);
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
Authentication authenticateJWT(String & jwt); // Lookup the user by JWT
boolean validatePayload(JsonObject & parsedPayload, User * user); // Verify the payload is correct
};
#endif
#endif

View File

@@ -5,24 +5,34 @@
using namespace std::placeholders; // for `_1` etc
SystemStatus::SystemStatus(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(SYSTEM_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
SystemStatus::SystemStatus(PsychicHttpServer * server, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager) {
}
void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
void SystemStatus::registerURI() {
emsesp::EMSESP::system_.refreshHeapMem(); // refresh free heap and max alloc heap
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE);
JsonObject root = response->getRoot();
_server->on(SYSTEM_STATUS_SERVICE_PATH,
HTTP_GET,
_securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
}
esp_err_t SystemStatus::systemStatus(PsychicRequest * request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_ESP_STATUS_SIZE);
JsonObject root = response.getRoot();
root["emsesp_version"] = EMSESP_APP_VERSION;
root["esp_platform"] = EMSESP_PLATFORM;
root["cpu_type"] = ESP.getChipModel();
root["cpu_rev"] = ESP.getChipRevision();
root["cpu_cores"] = ESP.getChipCores();
root["cpu_freq_mhz"] = ESP.getCpuFreqMHz();
root["max_alloc_heap"] = emsesp::EMSESP::system_.getMaxAllocMem();
root["free_heap"] = emsesp::EMSESP::system_.getHeapMem();
root["arduino_version"] = ARDUINO_VERSION;
root["sdk_version"] = ESP.getSdkVersion();
root["partition"] = esp_ota_get_running_partition()->label;
root["flash_chip_size"] = ESP.getFlashChipSize() / 1024;
root["flash_chip_speed"] = ESP.getFlashChipSpeed();
root["app_used"] = emsesp::EMSESP::system_.appUsed();
@@ -32,14 +42,17 @@ void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
root["fs_free"] = emsesp::EMSESP::system_.FStotal() - FSused;
root["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
// TODO add buid time with __TIME__ and __DATE__
if (emsesp::EMSESP::system_.PSram()) {
root["psram_size"] = emsesp::EMSESP::system_.PSram();
root["free_psram"] = ESP.getFreePsram() / 1024;
}
const esp_partition_t * partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
if (partition != NULL) { // factory partition found
root["has_loader"] = true;
} else { // check for not empty, smaller OTA partition
} else { // check for not empty, smaller OTA partition
partition = esp_ota_get_next_update_partition(NULL);
if (partition) {
uint64_t buffer;
@@ -49,6 +62,5 @@ void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
}
}
response->setLength();
request->send(response);
return response.send();
}

View File

@@ -2,12 +2,11 @@
#define SystemStatus_h
#include <WiFi.h>
#include <AsyncTCP.h>
#include <FS.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#define MAX_ESP_STATUS_SIZE 1024
@@ -15,10 +14,14 @@
class SystemStatus {
public:
SystemStatus(AsyncWebServer * server, SecurityManager * securityManager);
SystemStatus(PsychicHttpServer * server, SecurityManager * securityManager);
void registerURI();
private:
void systemStatus(AsyncWebServerRequest * request);
SecurityManager * _securityManager;
PsychicHttpServer * _server;
esp_err_t systemStatus(PsychicRequest * request);
};
#endif

View File

@@ -6,25 +6,34 @@
using namespace std::placeholders; // for `_1` etc
static bool is_firmware = false;
static char md5[33] = "\0";
static char md5[33] = "\0";
UploadFileService::UploadFileService(AsyncWebServer * server, SecurityManager * securityManager)
: _securityManager(securityManager) {
server->on(UPLOAD_FILE_PATH,
HTTP_POST,
std::bind(&UploadFileService::uploadComplete, this, _1),
std::bind(&UploadFileService::handleUpload, this, _1, _2, _3, _4, _5, _6));
static FileType fileType = ft_none;
UploadFileService::UploadFileService(PsychicHttpServer * server, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager) {
}
void UploadFileService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
void UploadFileService::registerURI() {
_server->maxUploadSize = 2300000; // 2.3 MB
PsychicUploadHandler * uploadHandler = new PsychicUploadHandler();
uploadHandler->onUpload(std::bind(&UploadFileService::handleUpload, this, _1, _2, _3, _4, _5, _6));
uploadHandler->onRequest(std::bind(&UploadFileService::uploadComplete, this, _1)); //gets called after upload has been handled
_server->on(UPLOAD_FILE_PATH, HTTP_POST, uploadHandler);
}
esp_err_t UploadFileService::handleUpload(PsychicRequest * request, const String & filename, uint64_t index, uint8_t * data, size_t len, bool final) {
// quit if not authorized
Authentication authentication = _securityManager->authenticateRequest(request);
if (!AuthenticationPredicates::IS_ADMIN(authentication)) {
handleError(request, 403); // send the forbidden response
return;
return handleError(request, 403); // forbidden
}
File jsonFile;
// at init
if (!index) {
// check details of the file, to see if its a valid bin or json file
@@ -33,44 +42,41 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
std::string extension = fname.substr(position + 1);
size_t fsize = request->contentLength();
is_firmware = false;
fileType = ft_none;
if ((extension == "bin") && (fsize > 1000000)) {
is_firmware = true;
fileType = ft_firmware;
} else if (extension == "json") {
md5[0] = '\0'; // clear md5
fileType = ft_json;
md5[0] = '\0'; // clear md5
} else if (extension == "md5") {
fileType = ft_md5;
if (len == 32) {
memcpy(md5, data, 32);
md5[32] = '\0';
}
return;
return ESP_OK;
} else {
md5[0] = '\0';
handleError(request, 406); // Not Acceptable - unsupported file type
return;
return handleError(request, 406); // Not Acceptable - unsupported file type
}
if (is_firmware) {
if (fileType == ft_firmware) {
// Check firmware header, 0xE9 magic offset 0 indicates esp bin, chip offset 12: esp32:0, S2:2, C3:5
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
if (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
handleError(request, 503); // service unavailable
return;
return handleError(request, 503); // service unavailable
}
#elif CONFIG_IDF_TARGET_ESP32S2
if (len > 12 && (data[0] != 0xE9 || data[12] != 2)) {
handleError(request, 503); // service unavailable
return;
return handleError(request, 503); // service unavailable
}
#elif CONFIG_IDF_TARGET_ESP32C3
if (len > 12 && (data[0] != 0xE9 || data[12] != 5)) {
handleError(request, 503); // service unavailable
return;
return handleError(request, 503); // service unavailable
}
#elif CONFIG_IDF_TARGET_ESP32S3
if (len > 12 && (data[0] != 0xE9 || data[12] != 9)) {
handleError(request, 503); // service unavailable
return;
return handleError(request, 503); // service unavailable
}
#endif
// it's firmware - initialize the ArduinoOTA updater
@@ -79,21 +85,19 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
Update.setMD5(md5);
md5[0] = '\0';
}
request->onDisconnect(UploadFileService::handleEarlyDisconnect); // success, let's make sure we end the update if the client hangs up
} else {
handleError(request, 507); // failed to begin, send an error response Insufficient Storage
return;
return handleError(request, 507); // failed to begin, send an error response Insufficient Storage
}
} else {
// its a normal file, open a new temp file to write the contents too
request->_tempFile = LittleFS.open(TEMP_FILENAME_PATH, "w");
jsonFile = LittleFS.open(TEMP_FILENAME_PATH, FILE_WRITE);
}
}
if (!is_firmware) {
if (fileType == ft_json) {
if (len) {
if (len != request->_tempFile.write(data, len)) { // stream the incoming chunk to the opened file
handleError(request, 507); // 507-Insufficient Storage
if (len != jsonFile.write(data, len)) { // stream the incoming chunk to the opened file
return handleError(request, 507); // failed to write chunk to file, send an error response Insufficient Storage
}
}
} else {
@@ -109,60 +113,42 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
}
}
}
return ESP_OK;
}
void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
// did we complete uploading a json file?
if (request->_tempFile) {
request->_tempFile.close(); // close the file handle as the upload is now done
emsesp::EMSESP::system_.store_nvs_values();
request->onDisconnect(RestartService::restartNow);
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
return;
esp_err_t UploadFileService::uploadComplete(PsychicRequest * request) {
// did we complete uploading a json file? no need to close the file
if (fileType == ft_md5) {
if (strlen(md5) == 32) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, 256);
JsonObject root = response.getRoot();
root["md5"] = md5;
return response.send();
}
return ESP_OK;
}
// check if it was a firmware upgrade
// if no error, send the success response as a JSON
if (is_firmware && !request->_tempObject) {
emsesp::EMSESP::system_.store_nvs_values();
request->onDisconnect(RestartService::restartNow);
AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response);
return;
}
if (strlen(md5) == 32) {
auto * response = new AsyncJsonResponse(false, 256);
JsonObject root = response->getRoot();
root["md5"] = md5;
response->setLength();
request->send(response);
return;
}
handleError(request, 500);
// store and restart regardless of whether it worked or not
emsesp::EMSESP::system_.store_nvs_values();
request->reply(200);
RestartService::restartNow();
return ESP_OK;
}
void UploadFileService::handleError(AsyncWebServerRequest * request, int code) {
esp_err_t UploadFileService::handleError(PsychicRequest * request, int code) {
// if we have had an error already, do nothing
if (request->_tempObject) {
return;
return ESP_OK;
}
// send the error code to the client and record the error code in the temp object
AsyncWebServerResponse * response = request->beginResponse(code);
request->send(response);
// check for invalid extension and immediately kill the connection, which will through an error
// that is caught by the web code. Unfortunately the http error code is not sent to the client on fast network connections
if (code == 406) {
request->client()->close(true);
handleEarlyDisconnect();
request->client()->close();
fileType = ft_none;
Update.abort();
}
}
void UploadFileService::handleEarlyDisconnect() {
is_firmware = false;
Update.abort();
return request->reply(code);
}

View File

@@ -8,23 +8,28 @@
#include <LittleFS.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#include <RestartService.h>
#define UPLOAD_FILE_PATH "/rest/uploadFile"
#define TEMP_FILENAME_PATH "/tmp_upload"
enum FileType { ft_none = 0, ft_firmware = 1, ft_md5 = 2, ft_json = 3 };
class UploadFileService {
public:
UploadFileService(AsyncWebServer * server, SecurityManager * securityManager);
UploadFileService(PsychicHttpServer * server, SecurityManager * securityManager);
void registerURI();
private:
SecurityManager * _securityManager;
void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final);
void uploadComplete(AsyncWebServerRequest * request);
void handleError(AsyncWebServerRequest * request, int code);
static void handleEarlyDisconnect();
SecurityManager * _securityManager;
PsychicHttpServer * _server;
esp_err_t handleUpload(PsychicRequest * request, const String & filename, uint64_t index, uint8_t * data, size_t len, bool final);
esp_err_t uploadComplete(PsychicRequest * request);
esp_err_t handleError(PsychicRequest * request, int code);
};
#endif

View File

@@ -2,7 +2,7 @@
#define WebSocketTxRx_h
#include <StatefulService.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#define WEB_SOCKET_CLIENT_ID_MSG_SIZE 128
@@ -16,12 +16,12 @@ template <class T>
class WebSocketConnector {
protected:
StatefulService<T> * _statefulService;
AsyncWebServer * _server;
PsychicHttpServer * _server;
AsyncWebSocket _webSocket;
size_t _bufferSize;
WebSocketConnector(StatefulService<T> * statefulService,
AsyncWebServer * server,
PsychicHttpServer * server,
const char * webSocketPath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate,
@@ -36,7 +36,7 @@ class WebSocketConnector {
_server->on(webSocketPath, HTTP_GET, std::bind(&WebSocketConnector::forbidden, this, _1));
}
WebSocketConnector(StatefulService<T> * statefulService, AsyncWebServer * server, const char * webSocketPath, size_t bufferSize)
WebSocketConnector(StatefulService<T> * statefulService, PsychicHttpServer * server, const char * webSocketPath, size_t bufferSize)
: _statefulService(statefulService)
, _server(server)
, _webSocket(webSocketPath)
@@ -52,7 +52,7 @@ class WebSocketConnector {
}
private:
void forbidden(AsyncWebServerRequest * request) {
void forbidden(PsychicRequest * request) {
request->send(403);
}
};
@@ -62,7 +62,7 @@ class WebSocketTx : virtual public WebSocketConnector<T> {
public:
WebSocketTx(JsonStateReader<T> stateReader,
StatefulService<T> * statefulService,
AsyncWebServer * server,
PsychicHttpServer * server,
const char * webSocketPath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
@@ -74,7 +74,7 @@ class WebSocketTx : virtual public WebSocketConnector<T> {
WebSocketTx(JsonStateReader<T> stateReader,
StatefulService<T> * statefulService,
AsyncWebServer * server,
PsychicHttpServer * server,
const char * webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize)
@@ -140,7 +140,7 @@ class WebSocketRx : virtual public WebSocketConnector<T> {
public:
WebSocketRx(JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
PsychicHttpServer * server,
const char * webSocketPath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
@@ -151,7 +151,7 @@ class WebSocketRx : virtual public WebSocketConnector<T> {
WebSocketRx(JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
PsychicHttpServer * server,
const char * webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize)
@@ -185,7 +185,7 @@ class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
WebSocketTxRx(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
PsychicHttpServer * server,
const char * webSocketPath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
@@ -198,7 +198,7 @@ class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
WebSocketTxRx(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
PsychicHttpServer * server,
const char * webSocketPath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize)

View File

@@ -2,30 +2,36 @@
using namespace std::placeholders; // for `_1` etc
WiFiScanner::WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(SCAN_NETWORKS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, _1), AuthenticationPredicates::IS_ADMIN));
server->on(LIST_NETWORKS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, _1), AuthenticationPredicates::IS_ADMIN));
WiFiScanner::WiFiScanner(PsychicHttpServer * server, SecurityManager * securityManager)
: _server(server)
, _securityManager(securityManager){};
void WiFiScanner::registerURI() {
_server->on(SCAN_NETWORKS_SERVICE_PATH,
HTTP_GET,
_securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, _1), AuthenticationPredicates::IS_ADMIN));
_server->on(LIST_NETWORKS_SERVICE_PATH,
HTTP_GET,
_securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, _1), AuthenticationPredicates::IS_ADMIN));
};
void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {
request->send(202); // special code to indicate scan in progress
esp_err_t WiFiScanner::scanNetworks(PsychicRequest * request) {
if (WiFi.scanComplete() != -1) {
WiFi.scanDelete();
WiFi.scanNetworks(true);
}
return request->reply(202); // special code to indicate scan in progress
}
void WiFiScanner::listNetworks(AsyncWebServerRequest * request) {
esp_err_t WiFiScanner::listNetworks(PsychicRequest * request) {
int numNetworks = WiFi.scanComplete();
if (numNetworks > -1) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_WIFI_SCANNER_SIZE);
JsonObject root = response->getRoot();
JsonArray networks = root.createNestedArray("networks");
PsychicJsonResponse response = PsychicJsonResponse(request, false, MAX_WIFI_SCANNER_SIZE);
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);
@@ -34,11 +40,12 @@ void WiFiScanner::listNetworks(AsyncWebServerRequest * request) {
network["channel"] = WiFi.channel(i);
network["encryption_type"] = (uint8_t)WiFi.encryptionType(i);
}
response->setLength();
request->send(response);
return response.send();
} else if (numNetworks == -1) {
request->send(202); // special code to indicate scan in progress
return request->reply(202); // special code to indicate scan in progress
} else {
scanNetworks(request);
return scanNetworks(request);
}
}

View File

@@ -2,10 +2,9 @@
#define WiFiScanner_h
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks"
@@ -15,11 +14,16 @@
class WiFiScanner {
public:
WiFiScanner(AsyncWebServer * server, SecurityManager * securityManager);
WiFiScanner(PsychicHttpServer * server, SecurityManager * securityManager);
void registerURI();
private:
void scanNetworks(AsyncWebServerRequest * request);
void listNetworks(AsyncWebServerRequest * request);
SecurityManager * _securityManager;
PsychicHttpServer * _server;
esp_err_t scanNetworks(PsychicRequest * request);
esp_err_t listNetworks(PsychicRequest * request);
};
#endif