mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 01:09:51 +03:00
Merge branch 'dev' of https://github.com/emsesp/EMS-ESP32 into dev2
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
#include <APSettingsService.h>
|
||||
#include "APSettingsService.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
@@ -9,8 +9,8 @@ APSettingsService::APSettingsService(AsyncWebServer * server, FS * fs, SecurityM
|
||||
, _lastManaged(0)
|
||||
, _reconfigureAp(false)
|
||||
, _connected(0) {
|
||||
addUpdateHandler([&](const String & originId) { reconfigureAP(); }, false);
|
||||
WiFi.onEvent(std::bind(&APSettingsService::WiFiEvent, this, _1));
|
||||
addUpdateHandler([this] { reconfigureAP(); }, false);
|
||||
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
|
||||
}
|
||||
|
||||
void APSettingsService::begin() {
|
||||
@@ -53,7 +53,7 @@ void APSettingsService::reconfigureAP() {
|
||||
|
||||
void APSettingsService::loop() {
|
||||
unsigned long currentMillis = uuid::get_uptime();
|
||||
unsigned long manageElapsed = (uint32_t)(currentMillis - _lastManaged);
|
||||
unsigned long manageElapsed = static_cast<uint32_t>(currentMillis - _lastManaged);
|
||||
if (manageElapsed >= MANAGE_NETWORK_DELAY) {
|
||||
_lastManaged = currentMillis;
|
||||
manageAP();
|
||||
@@ -76,7 +76,7 @@ void APSettingsService::manageAP() {
|
||||
|
||||
void APSettingsService::startAP() {
|
||||
WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
|
||||
esp_wifi_set_bandwidth((wifi_interface_t)ESP_IF_WIFI_AP, WIFI_BW_HT20);
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_AP), WIFI_BW_HT20);
|
||||
WiFi.softAP(_state.ssid.c_str(), _state.password.c_str(), _state.channel, _state.ssidHidden, _state.maxClients);
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
|
||||
@@ -108,8 +108,54 @@ void APSettingsService::handleDNS() {
|
||||
APNetworkStatus APSettingsService::getAPNetworkStatus() {
|
||||
WiFiMode_t currentWiFiMode = WiFi.getMode();
|
||||
bool apActive = currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA;
|
||||
|
||||
if (apActive && _state.provisionMode != AP_MODE_ALWAYS && WiFi.status() == WL_CONNECTED) {
|
||||
return APNetworkStatus::LINGERING;
|
||||
}
|
||||
|
||||
return apActive ? APNetworkStatus::ACTIVE : APNetworkStatus::INACTIVE;
|
||||
}
|
||||
|
||||
|
||||
void APSettings::read(const APSettings & settings, JsonObject root) {
|
||||
root["provision_mode"] = settings.provisionMode;
|
||||
root["ssid"] = settings.ssid;
|
||||
root["password"] = settings.password;
|
||||
root["channel"] = settings.channel;
|
||||
root["ssid_hidden"] = settings.ssidHidden;
|
||||
root["max_clients"] = settings.maxClients;
|
||||
root["local_ip"] = settings.localIP.toString();
|
||||
root["gateway_ip"] = settings.gatewayIP.toString();
|
||||
root["subnet_mask"] = settings.subnetMask.toString();
|
||||
}
|
||||
|
||||
StateUpdateResult APSettings::update(JsonObject root, APSettings & settings) {
|
||||
APSettings newSettings = {};
|
||||
newSettings.provisionMode = static_cast<uint8_t>(root["provision_mode"] | FACTORY_AP_PROVISION_MODE);
|
||||
|
||||
switch (settings.provisionMode) {
|
||||
case AP_MODE_ALWAYS:
|
||||
case AP_MODE_DISCONNECTED:
|
||||
case AP_MODE_NEVER:
|
||||
break;
|
||||
default:
|
||||
newSettings.provisionMode = AP_MODE_ALWAYS;
|
||||
}
|
||||
|
||||
newSettings.ssid = root["ssid"] | FACTORY_AP_SSID;
|
||||
newSettings.password = root["password"] | FACTORY_AP_PASSWORD;
|
||||
newSettings.channel = static_cast<uint8_t>(root["channel"] | FACTORY_AP_CHANNEL);
|
||||
newSettings.ssidHidden = root["ssid_hidden"] | FACTORY_AP_SSID_HIDDEN;
|
||||
newSettings.maxClients = static_cast<uint8_t>(root["max_clients"] | FACTORY_AP_MAX_CLIENTS);
|
||||
|
||||
JsonUtils::readIP(root, "local_ip", newSettings.localIP, FACTORY_AP_LOCAL_IP);
|
||||
JsonUtils::readIP(root, "gateway_ip", newSettings.gatewayIP, FACTORY_AP_GATEWAY_IP);
|
||||
JsonUtils::readIP(root, "subnet_mask", newSettings.subnetMask, FACTORY_AP_SUBNET_MASK);
|
||||
|
||||
if (newSettings == settings) {
|
||||
return StateUpdateResult::UNCHANGED;
|
||||
}
|
||||
|
||||
settings = newSettings;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#ifndef APSettingsConfig_h
|
||||
#define APSettingsConfig_h
|
||||
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
#include <JsonUtils.h>
|
||||
#include "HttpEndpoint.h"
|
||||
#include "FSPersistence.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#include <DNSServer.h>
|
||||
#include <IPAddress.h>
|
||||
@@ -75,45 +75,8 @@ class APSettings {
|
||||
&& subnetMask == settings.subnetMask;
|
||||
}
|
||||
|
||||
static void read(APSettings & settings, JsonObject root) {
|
||||
root["provision_mode"] = settings.provisionMode;
|
||||
root["ssid"] = settings.ssid;
|
||||
root["password"] = settings.password;
|
||||
root["channel"] = settings.channel;
|
||||
root["ssid_hidden"] = settings.ssidHidden;
|
||||
root["max_clients"] = settings.maxClients;
|
||||
root["local_ip"] = settings.localIP.toString();
|
||||
root["gateway_ip"] = settings.gatewayIP.toString();
|
||||
root["subnet_mask"] = settings.subnetMask.toString();
|
||||
}
|
||||
|
||||
static StateUpdateResult update(JsonObject root, APSettings & settings) {
|
||||
APSettings newSettings = {};
|
||||
newSettings.provisionMode = root["provision_mode"] | FACTORY_AP_PROVISION_MODE;
|
||||
switch (settings.provisionMode) {
|
||||
case AP_MODE_ALWAYS:
|
||||
case AP_MODE_DISCONNECTED:
|
||||
case AP_MODE_NEVER:
|
||||
break;
|
||||
default:
|
||||
newSettings.provisionMode = AP_MODE_ALWAYS;
|
||||
}
|
||||
newSettings.ssid = root["ssid"] | FACTORY_AP_SSID;
|
||||
newSettings.password = root["password"] | FACTORY_AP_PASSWORD;
|
||||
newSettings.channel = root["channel"] | FACTORY_AP_CHANNEL;
|
||||
newSettings.ssidHidden = root["ssid_hidden"] | FACTORY_AP_SSID_HIDDEN;
|
||||
newSettings.maxClients = root["max_clients"] | FACTORY_AP_MAX_CLIENTS;
|
||||
|
||||
JsonUtils::readIP(root, "local_ip", newSettings.localIP, FACTORY_AP_LOCAL_IP);
|
||||
JsonUtils::readIP(root, "gateway_ip", newSettings.gatewayIP, FACTORY_AP_GATEWAY_IP);
|
||||
JsonUtils::readIP(root, "subnet_mask", newSettings.subnetMask, FACTORY_AP_SUBNET_MASK);
|
||||
|
||||
if (newSettings == settings) {
|
||||
return StateUpdateResult::UNCHANGED;
|
||||
}
|
||||
settings = newSettings;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
static void read(const APSettings & settings, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, APSettings & settings);
|
||||
};
|
||||
|
||||
class APSettingsService : public StatefulService<APSettings> {
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#include <APStatus.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
#include "APStatus.h"
|
||||
|
||||
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));
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { apStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void APStatus::apStatus(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
root["status"] = _apSettingsService->getAPNetworkStatus();
|
||||
root["ip_address"] = WiFi.softAPIP().toString();
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <IPAddress.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <APSettingsService.h>
|
||||
|
||||
#include "SecurityManager.h"
|
||||
#include "APSettingsService.h"
|
||||
|
||||
#define AP_STATUS_SERVICE_PATH "/rest/apStatus"
|
||||
|
||||
|
||||
@@ -1,47 +1,27 @@
|
||||
#include "ArduinoJsonJWT.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
ArduinoJsonJWT::ArduinoJsonJWT(String secret)
|
||||
: _secret(secret) {
|
||||
: _secret(std::move(secret)) {
|
||||
}
|
||||
|
||||
void ArduinoJsonJWT::setSecret(String secret) {
|
||||
_secret = secret;
|
||||
_secret = std::move(secret);
|
||||
}
|
||||
|
||||
String ArduinoJsonJWT::getSecret() {
|
||||
return _secret;
|
||||
}
|
||||
|
||||
/*
|
||||
* ESP32 uses mbedtls, ESP2866 uses bearssl.
|
||||
*
|
||||
* Both come with decent HMAC implementations supporting sha256, as well as others.
|
||||
*
|
||||
* No need to pull in additional crypto libraries - lets use what we already have.
|
||||
*/
|
||||
String ArduinoJsonJWT::sign(String & payload) {
|
||||
unsigned char hmacResult[32];
|
||||
{
|
||||
mbedtls_md_context_t ctx;
|
||||
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
|
||||
mbedtls_md_init(&ctx);
|
||||
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
|
||||
mbedtls_md_hmac_starts(&ctx, (unsigned char *)_secret.c_str(), _secret.length());
|
||||
mbedtls_md_hmac_update(&ctx, (unsigned char *)payload.c_str(), payload.length());
|
||||
mbedtls_md_hmac_finish(&ctx, hmacResult);
|
||||
mbedtls_md_free(&ctx);
|
||||
}
|
||||
return encode((char *)hmacResult, 32);
|
||||
}
|
||||
|
||||
String ArduinoJsonJWT::buildJWT(JsonObject payload) {
|
||||
// serialize, then encode payload
|
||||
String jwt;
|
||||
serializeJson(payload, jwt);
|
||||
jwt = encode(jwt.c_str(), jwt.length());
|
||||
jwt = encode(jwt.c_str(), static_cast<int>(jwt.length()));
|
||||
|
||||
// add the header to payload
|
||||
jwt = JWT_HEADER + '.' + jwt;
|
||||
jwt = getJWTHeader() + '.' + jwt;
|
||||
|
||||
// add signature
|
||||
jwt += '.' + sign(jwt);
|
||||
@@ -53,65 +33,88 @@ void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument & jsonDocument) {
|
||||
// clear json document before we begin, jsonDocument wil be null on failure
|
||||
jsonDocument.clear();
|
||||
|
||||
const String & jwt_header = getJWTHeader();
|
||||
const unsigned int jwt_header_size = jwt_header.length();
|
||||
|
||||
// must have the correct header and delimiter
|
||||
if (!jwt.startsWith(JWT_HEADER) || jwt.indexOf('.') != JWT_HEADER_SIZE) {
|
||||
if (!jwt.startsWith(jwt_header) || jwt.indexOf('.') != static_cast<int>(jwt_header_size)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check there is a signature delimieter
|
||||
int signatureDelimiterIndex = jwt.lastIndexOf('.');
|
||||
if (signatureDelimiterIndex == JWT_HEADER_SIZE) {
|
||||
const int signatureDelimiterIndex = jwt.lastIndexOf('.');
|
||||
if (signatureDelimiterIndex == static_cast<int>(jwt_header_size)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check the signature is valid
|
||||
String signature = jwt.substring(signatureDelimiterIndex + 1);
|
||||
jwt = jwt.substring(0, signatureDelimiterIndex);
|
||||
const String signature = jwt.substring(static_cast<unsigned int>(signatureDelimiterIndex) + 1);
|
||||
jwt = jwt.substring(0, static_cast<unsigned int>(signatureDelimiterIndex));
|
||||
if (sign(jwt) != signature) {
|
||||
return;
|
||||
}
|
||||
|
||||
// decode payload
|
||||
jwt = jwt.substring(JWT_HEADER_SIZE + 1);
|
||||
jwt = jwt.substring(jwt_header_size + 1);
|
||||
jwt = decode(jwt);
|
||||
|
||||
// parse payload, clearing json document after failure
|
||||
DeserializationError error = deserializeJson(jsonDocument, jwt);
|
||||
const DeserializationError error = deserializeJson(jsonDocument, jwt);
|
||||
if (error != DeserializationError::Ok || !jsonDocument.is<JsonObject>()) {
|
||||
jsonDocument.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ESP32 uses mbedtls, ESP2866 uses bearssl.
|
||||
*
|
||||
* Both come with decent HMAC implementations supporting sha256, as well as others.
|
||||
*
|
||||
* No need to pull in additional crypto libraries - lets use what we already have.
|
||||
*/
|
||||
String ArduinoJsonJWT::sign(String & payload) {
|
||||
std::array<unsigned char, 32> hmacResult{};
|
||||
{
|
||||
mbedtls_md_context_t ctx;
|
||||
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
|
||||
mbedtls_md_init(&ctx);
|
||||
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
|
||||
mbedtls_md_hmac_starts(&ctx, reinterpret_cast<const unsigned char *>(_secret.c_str()), _secret.length());
|
||||
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char *>(payload.c_str()), payload.length());
|
||||
mbedtls_md_hmac_finish(&ctx, hmacResult.data());
|
||||
mbedtls_md_free(&ctx);
|
||||
}
|
||||
return encode(reinterpret_cast<const char *>(hmacResult.data()), hmacResult.size());
|
||||
}
|
||||
|
||||
String ArduinoJsonJWT::encode(const char * cstr, int inputLen) {
|
||||
// prepare encoder
|
||||
base64_encodestate _state;
|
||||
base64_init_encodestate(&_state);
|
||||
size_t encodedLength = base64_encode_expected_len(inputLen) + 1;
|
||||
// prepare buffer of correct length, returning an empty string on failure
|
||||
char * buffer = (char *)malloc(encodedLength * sizeof(char));
|
||||
if (buffer == nullptr) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// prepare buffer of correct length
|
||||
const auto bufferLength = static_cast<std::size_t>(base64_encode_expected_len(inputLen)) + 1;
|
||||
auto * buffer = new char[bufferLength];
|
||||
|
||||
// encode to buffer
|
||||
int len = base64_encode_block(cstr, inputLen, &buffer[0], &_state);
|
||||
len += base64_encode_blockend(&buffer[len], &_state);
|
||||
buffer[len] = 0;
|
||||
buffer[len] = '\0';
|
||||
|
||||
// convert to arduino string, freeing buffer
|
||||
String value = String(buffer);
|
||||
free(buffer);
|
||||
auto result = String(buffer);
|
||||
delete[] buffer;
|
||||
buffer = nullptr;
|
||||
|
||||
// remove padding and convert to URL safe form
|
||||
while (value.length() > 0 && value.charAt(value.length() - 1) == '=') {
|
||||
value.remove(value.length() - 1);
|
||||
while (result.length() > 0 && result.charAt(result.length() - 1) == '=') {
|
||||
result.remove(result.length() - 1);
|
||||
}
|
||||
value.replace('+', '-');
|
||||
value.replace('/', '_');
|
||||
result.replace('+', '-');
|
||||
result.replace('/', '_');
|
||||
|
||||
// return as string
|
||||
return value;
|
||||
return result;
|
||||
}
|
||||
|
||||
String ArduinoJsonJWT::decode(String value) {
|
||||
@@ -120,12 +123,18 @@ String ArduinoJsonJWT::decode(String value) {
|
||||
value.replace('_', '/');
|
||||
|
||||
// prepare buffer of correct length
|
||||
char buffer[base64_decode_expected_len(value.length()) + 1];
|
||||
const auto bufferLength = static_cast<std::size_t>(base64_decode_expected_len(value.length()) + 1);
|
||||
auto * buffer = new char[bufferLength];
|
||||
|
||||
// decode
|
||||
int len = base64_decode_chars(value.c_str(), value.length(), &buffer[0]);
|
||||
buffer[len] = 0;
|
||||
const int len = base64_decode_chars(value.c_str(), static_cast<int>(value.length()), &buffer[0]);
|
||||
buffer[len] = '\0';
|
||||
|
||||
// convert to arduino string, freeing buffer
|
||||
auto result = String(buffer);
|
||||
delete[] buffer;
|
||||
buffer = nullptr;
|
||||
|
||||
// return as string
|
||||
return String(buffer);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -3,30 +3,33 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <libb64/cdecode.h>
|
||||
#include <libb64/cencode.h>
|
||||
#include <mbedtls/md.h>
|
||||
|
||||
class ArduinoJsonJWT {
|
||||
private:
|
||||
String _secret;
|
||||
|
||||
const String JWT_HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
||||
const int JWT_HEADER_SIZE = JWT_HEADER.length();
|
||||
|
||||
String sign(String & value);
|
||||
|
||||
static String encode(const char * cstr, int len);
|
||||
static String decode(String value);
|
||||
|
||||
public:
|
||||
ArduinoJsonJWT(String secret);
|
||||
explicit ArduinoJsonJWT(String secret);
|
||||
|
||||
void setSecret(String secret);
|
||||
String getSecret();
|
||||
|
||||
String buildJWT(JsonObject payload);
|
||||
void parseJWT(String jwt, JsonDocument & jsonDocument);
|
||||
|
||||
private:
|
||||
String _secret;
|
||||
|
||||
String sign(String & value);
|
||||
|
||||
static String encode(const char * cstr, int len);
|
||||
static String decode(String value);
|
||||
|
||||
static const String & getJWTHeader() {
|
||||
static const String JWT_HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
||||
return JWT_HEADER;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,11 +1,9 @@
|
||||
#include <AuthenticationService.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
#include "AuthenticationService.h"
|
||||
|
||||
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(SIGN_IN_PATH, [this](AsyncWebServerRequest * request, JsonVariant json) { signIn(request, json); }) {
|
||||
server->on(VERIFY_AUTHORIZATION_PATH, HTTP_GET, [this](AsyncWebServerRequest * request) { verifyAuthorization(request); });
|
||||
_signInHandler.setMethod(HTTP_POST);
|
||||
_signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE);
|
||||
server->addHandler(&_signInHandler);
|
||||
@@ -29,10 +27,10 @@ void AuthenticationService::signIn(AsyncWebServerRequest * request, JsonVariant
|
||||
String password = json["password"];
|
||||
Authentication authentication = _securityManager->authenticate(username, password);
|
||||
if (authentication.authenticated) {
|
||||
User * user = authentication.user;
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false);
|
||||
JsonObject jsonObject = response->getRoot();
|
||||
jsonObject["access_token"] = _securityManager->generateJWT(user);
|
||||
User * user = authentication.user;
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject jsonObject = response->getRoot();
|
||||
jsonObject["access_token"] = _securityManager->generateJWT(user);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifndef AuthenticationService_H_
|
||||
#define AuthenticationService_H_
|
||||
|
||||
#include <Features.h>
|
||||
#include "Features.h"
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
|
||||
#define SIGN_IN_PATH "/rest/signIn"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <ESP8266React.h>
|
||||
#include "ESP8266React.h"
|
||||
|
||||
#include <WWWData.h>
|
||||
#include "WWWData.h"
|
||||
|
||||
ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
|
||||
: _securitySettingsService(server, fs)
|
||||
@@ -19,15 +19,33 @@ ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
|
||||
, _restartService(server, &_securitySettingsService)
|
||||
, _factoryResetService(server, fs, &_securitySettingsService)
|
||||
, _systemStatus(server, &_securitySettingsService) {
|
||||
// Serve static resources
|
||||
WWWData::registerRoutes([server, this](const String & uri, const String & contentType, const uint8_t * content, size_t len) {
|
||||
ArRequestHandlerFunction requestHandler = [contentType, content, len](AsyncWebServerRequest * request) {
|
||||
//
|
||||
// Serve static web resources
|
||||
//
|
||||
|
||||
// Populate the last modification date based on build datetime
|
||||
static char last_modified[50];
|
||||
sprintf(last_modified, "%s %s CET", __DATE__, __TIME__);
|
||||
|
||||
WWWData::registerRoutes([server](const String & uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
|
||||
ArRequestHandlerFunction requestHandler = [contentType, content, len, hash](AsyncWebServerRequest * request) {
|
||||
// Check if the client already has the same version and respond with a 304 (Not modified)
|
||||
if (request->header("If-Modified-Since").indexOf(last_modified) > 0) {
|
||||
return request->send(304);
|
||||
} else if (request->header("If-None-Match").equals(hash)) {
|
||||
return request->send(304);
|
||||
}
|
||||
|
||||
AsyncWebServerResponse * response = request->beginResponse_P(200, contentType, content, len);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", "public, immutable, max-age=31536000");
|
||||
// response->addHeader("Content-Encoding", "br"); // only works over HTTPS
|
||||
// response->addHeader("Cache-Control", "public, immutable, max-age=31536000");
|
||||
response->addHeader("Last-Modified", last_modified);
|
||||
response->addHeader("ETag", hash);
|
||||
|
||||
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
|
||||
@@ -48,12 +66,13 @@ ESP8266React::ESP8266React(AsyncWebServer * server, FS * fs)
|
||||
void ESP8266React::begin() {
|
||||
_networkSettingsService.begin();
|
||||
_networkSettingsService.read([&](NetworkSettings & networkSettings) {
|
||||
DefaultHeaders & defaultHeaders = DefaultHeaders::Instance();
|
||||
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");
|
||||
defaultHeaders.addHeader("Access-Control-Allow-Origin", networkSettings.CORSOrigin);
|
||||
defaultHeaders.addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
|
||||
defaultHeaders.addHeader("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
DefaultHeaders::Instance().addHeader("Server", networkSettings.hostname);
|
||||
defaultHeaders.addHeader("Server", networkSettings.hostname);
|
||||
});
|
||||
_apSettingsService.begin();
|
||||
_ntpSettingsService.begin();
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
#ifndef ESP8266React_h
|
||||
#define ESP8266React_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "APSettingsService.h"
|
||||
#include "APStatus.h"
|
||||
#include "AuthenticationService.h"
|
||||
#include "FactoryResetService.h"
|
||||
#include "MqttSettingsService.h"
|
||||
#include "MqttStatus.h"
|
||||
#include "NTPSettingsService.h"
|
||||
#include "NTPStatus.h"
|
||||
#include "OTASettingsService.h"
|
||||
#include "UploadFileService.h"
|
||||
#include "RestartService.h"
|
||||
#include "SecuritySettingsService.h"
|
||||
#include "SystemStatus.h"
|
||||
#include "WiFiScanner.h"
|
||||
#include "NetworkSettingsService.h"
|
||||
#include "NetworkStatus.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include <APSettingsService.h>
|
||||
#include <APStatus.h>
|
||||
#include <AuthenticationService.h>
|
||||
#include <FactoryResetService.h>
|
||||
#include <MqttSettingsService.h>
|
||||
#include <MqttStatus.h>
|
||||
#include <NTPSettingsService.h>
|
||||
#include <NTPStatus.h>
|
||||
#include <OTASettingsService.h>
|
||||
#include <UploadFileService.h>
|
||||
#include <RestartService.h>
|
||||
#include <SecuritySettingsService.h>
|
||||
#include <SystemStatus.h>
|
||||
#include <WiFiScanner.h>
|
||||
#include <NetworkSettingsService.h>
|
||||
#include <NetworkStatus.h>
|
||||
|
||||
class ESP8266React {
|
||||
public:
|
||||
ESP8266React(AsyncWebServer * server, FS * fs);
|
||||
@@ -66,9 +65,11 @@ class ESP8266React {
|
||||
_mqttSettingsService.setWill(will_topic);
|
||||
}
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
void factoryReset() {
|
||||
_factoryResetService.factoryReset();
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
SecuritySettingsService _securitySettingsService;
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef ESPUtils_h
|
||||
#define ESPUtils_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class ESPUtils {
|
||||
public:
|
||||
static String defaultDeviceValue(String prefix = "") {
|
||||
return prefix + String((uint32_t)ESP.getEfuseMac(), HEX);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,8 +1,8 @@
|
||||
#ifndef FSPersistence_h
|
||||
#define FSPersistence_h
|
||||
|
||||
#include <StatefulService.h>
|
||||
#include <FS.h>
|
||||
#include "StatefulService.h"
|
||||
#include "FS.h"
|
||||
|
||||
template <class T>
|
||||
class FSPersistence {
|
||||
@@ -47,8 +47,8 @@ class FSPersistence {
|
||||
// make directories if required, for new IDF4.2 & LittleFS
|
||||
String path(_filePath);
|
||||
int index = 0;
|
||||
while ((index = path.indexOf('/', index + 1)) != -1) {
|
||||
String segment = path.substring(0, index);
|
||||
while ((index = path.indexOf('/', static_cast<unsigned int>(index) + 1)) != -1) {
|
||||
String segment = path.substring(0, static_cast<unsigned int>(index));
|
||||
if (!_fs->exists(segment)) {
|
||||
_fs->mkdir(segment);
|
||||
}
|
||||
@@ -80,7 +80,7 @@ class FSPersistence {
|
||||
|
||||
void enableUpdateHandler() {
|
||||
if (!_updateHandlerId) {
|
||||
_updateHandlerId = _statefulService->addUpdateHandler([&](const String & originId) { writeToFS(); });
|
||||
_updateHandlerId = _statefulService->addUpdateHandler([&] { writeToFS(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
#include <FactoryResetService.h>
|
||||
|
||||
using namespace std::placeholders;
|
||||
#include "FactoryResetService.h"
|
||||
|
||||
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));
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { handleRequest(request); }, AuthenticationPredicates::IS_ADMIN));
|
||||
}
|
||||
|
||||
void FactoryResetService::handleRequest(AsyncWebServerRequest * request) {
|
||||
request->onDisconnect(std::bind(&FactoryResetService::factoryReset, this));
|
||||
request->onDisconnect([this] { factoryReset(); });
|
||||
request->send(200);
|
||||
}
|
||||
|
||||
@@ -21,7 +19,7 @@ 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);
|
||||
File file;
|
||||
while (file = root.openNextFile()) {
|
||||
while ((file = root.openNextFile())) {
|
||||
String path = file.path();
|
||||
file.close();
|
||||
fs->remove(path);
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <RestartService.h>
|
||||
#include <FS.h>
|
||||
|
||||
#include "SecurityManager.h"
|
||||
#include "RestartService.h"
|
||||
|
||||
#define FS_CONFIG_DIRECTORY "/config"
|
||||
#define FACTORY_RESET_SERVICE_PATH "/rest/factoryReset"
|
||||
|
||||
class FactoryResetService {
|
||||
FS * fs;
|
||||
|
||||
public:
|
||||
FactoryResetService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||
|
||||
void factoryReset();
|
||||
|
||||
private:
|
||||
FS * fs;
|
||||
void handleRequest(AsyncWebServerRequest * request);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
#define HttpEndpoint_h
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include <SecurityManager.h>
|
||||
#include <StatefulService.h>
|
||||
#include "SecurityManager.h"
|
||||
#include "StatefulService.h"
|
||||
|
||||
#define HTTP_ENDPOINT_ORIGIN_ID "http"
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
#define HTTPS_ENDPOINT_ORIGIN_ID "https"
|
||||
|
||||
template <class T>
|
||||
class HttpEndpoint {
|
||||
@@ -19,8 +17,7 @@ class HttpEndpoint {
|
||||
JsonStateUpdater<T> _stateUpdater;
|
||||
StatefulService<T> * _statefulService;
|
||||
|
||||
AsyncCallbackWebHandler * GEThandler;
|
||||
AsyncCallbackJsonWebHandler * POSThandler;
|
||||
AsyncCallbackJsonWebHandler * handler;
|
||||
|
||||
public:
|
||||
HttpEndpoint(JsonStateReader<T> stateReader,
|
||||
@@ -33,12 +30,12 @@ class HttpEndpoint {
|
||||
: _stateReader(stateReader)
|
||||
, _stateUpdater(stateUpdater)
|
||||
, _statefulService(statefulService) {
|
||||
// Create the GET and POST endpoints
|
||||
POSThandler = new AsyncCallbackJsonWebHandler(servicePath,
|
||||
securityManager->wrapCallback([this](AsyncWebServerRequest * request,
|
||||
JsonVariant json) { handleRequest(request, json); },
|
||||
authenticationPredicate));
|
||||
server->addHandler(POSThandler);
|
||||
// Create hander for both GET and POST endpoints
|
||||
handler = new AsyncCallbackJsonWebHandler(servicePath,
|
||||
securityManager->wrapCallback([this](AsyncWebServerRequest * request,
|
||||
JsonVariant json) { handleRequest(request, json); },
|
||||
authenticationPredicate));
|
||||
server->addHandler(handler);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -56,16 +53,18 @@ class HttpEndpoint {
|
||||
if (outcome == StateUpdateResult::ERROR) {
|
||||
request->send(400); // error
|
||||
return;
|
||||
} else if (outcome == StateUpdateResult::CHANGED_RESTART) {
|
||||
request->send(205); // reboot required
|
||||
return;
|
||||
} else if (outcome == StateUpdateResult::CHANGED) {
|
||||
request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); });
|
||||
} else if (outcome == StateUpdateResult::CHANGED || outcome == StateUpdateResult::CHANGED_RESTART) {
|
||||
// persist changes
|
||||
request->onDisconnect([this] { _statefulService->callUpdateHandlers(); });
|
||||
if (outcome == StateUpdateResult::CHANGED_RESTART) {
|
||||
request->send(205); // reboot required
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false);
|
||||
JsonObject jsonObject = response->getRoot().to<JsonObject>();
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject jsonObject = response->getRoot().to<JsonObject>();
|
||||
_statefulService->read(jsonObject, _stateReader);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@@ -3,16 +3,20 @@
|
||||
|
||||
#include <IPAddress.h>
|
||||
|
||||
const IPAddress IP_NOT_SET = IPAddress(INADDR_NONE);
|
||||
|
||||
class IPUtils {
|
||||
public:
|
||||
static bool isSet(const IPAddress & ip) {
|
||||
return ip != IP_NOT_SET;
|
||||
return ip != getNotSetIP();
|
||||
}
|
||||
static bool isNotSet(const IPAddress & ip) {
|
||||
return ip == IP_NOT_SET;
|
||||
return ip == getNotSetIP();
|
||||
}
|
||||
|
||||
private:
|
||||
static const IPAddress & getNotSetIP() {
|
||||
static const IPAddress IP_NOT_SET = IPAddress(INADDR_NONE);
|
||||
return IP_NOT_SET;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -2,9 +2,10 @@
|
||||
#define JsonUtils_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
#include <IPUtils.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <IPAddress.h>
|
||||
|
||||
#include "IPUtils.h"
|
||||
|
||||
class JsonUtils {
|
||||
public:
|
||||
|
||||
@@ -1,45 +1,27 @@
|
||||
#include <MqttSettingsService.h>
|
||||
|
||||
#include "MqttSettingsService.h"
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
|
||||
/**
|
||||
* Retains a copy of the cstr provided in the pointer provided using dynamic allocation.
|
||||
*
|
||||
* Frees the pointer before allocation and leaves it as nullptr if cstr == nullptr.
|
||||
*/
|
||||
static char * retainCstr(const char * cstr, char ** ptr) {
|
||||
// free up previously retained value if exists
|
||||
free(*ptr);
|
||||
*ptr = nullptr;
|
||||
|
||||
// dynamically allocate and copy cstr (if non null)
|
||||
if (cstr != nullptr) {
|
||||
*ptr = (char *)malloc(strlen(cstr) + 1);
|
||||
strcpy(*ptr, cstr);
|
||||
}
|
||||
|
||||
// return reference to pointer for convenience
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
MqttSettingsService::MqttSettingsService(AsyncWebServer * server, FS * fs, 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)
|
||||
, _retainedUsername(nullptr)
|
||||
, _retainedPassword(nullptr)
|
||||
, _reconfigureMqtt(false)
|
||||
, _disconnectedAt(0)
|
||||
, _disconnectReason(espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED)
|
||||
, _mqttClient(nullptr) {
|
||||
WiFi.onEvent(std::bind(&MqttSettingsService::WiFiEvent, this, _1, _2));
|
||||
addUpdateHandler([&](const String & originId) { onConfigUpdated(); }, false);
|
||||
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
|
||||
addUpdateHandler([this] { onConfigUpdated(); }, false);
|
||||
}
|
||||
|
||||
static String generateClientId() {
|
||||
#ifdef EMSESP_STANDALONE
|
||||
return "ems-esp";
|
||||
#else
|
||||
return "esp32-" + String(static_cast<uint32_t>(ESP.getEfuseMac()), HEX);
|
||||
#endif
|
||||
}
|
||||
|
||||
MqttSettingsService::~MqttSettingsService() {
|
||||
delete _mqttClient;
|
||||
}
|
||||
|
||||
void MqttSettingsService::begin() {
|
||||
@@ -55,32 +37,41 @@ void MqttSettingsService::startClient() {
|
||||
return;
|
||||
}
|
||||
delete _mqttClient;
|
||||
_mqttClient = nullptr;
|
||||
}
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
if (_state.enableTLS) {
|
||||
isSecure = true;
|
||||
_mqttClient = static_cast<MqttClient *>(new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO));
|
||||
_mqttClient = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO);
|
||||
if (_state.rootCA == "insecure") {
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setInsecure();
|
||||
} else {
|
||||
String certificate = "-----BEGIN CERTIFICATE-----\n" + _state.rootCA + "\n-----END CERTIFICATE-----\n";
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setCACert(retainCstr(certificate.c_str(), &_retainedRootCA));
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setCACert(certificate.c_str());
|
||||
}
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, _1));
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, _1));
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->onMessage(std::bind(&MqttSettingsService::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->onConnect([this](bool sessionPresent) { onMqttConnect(sessionPresent); });
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->onDisconnect([this](espMqttClientTypes::DisconnectReason reason) { onMqttDisconnect(reason); });
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)
|
||||
->onMessage(
|
||||
[this](const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total) {
|
||||
onMqttMessage(properties, topic, payload, len, index, total);
|
||||
});
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
isSecure = false;
|
||||
_mqttClient = static_cast<MqttClient *>(new espMqttClient(espMqttClientTypes::UseInternalTask::NO));
|
||||
static_cast<espMqttClient *>(_mqttClient)->onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, _1));
|
||||
static_cast<espMqttClient *>(_mqttClient)->onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, _1));
|
||||
static_cast<espMqttClient *>(_mqttClient)->onMessage(std::bind(&MqttSettingsService::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
_mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO);
|
||||
static_cast<espMqttClient *>(_mqttClient)->onConnect([this](bool sessionPresent) { onMqttConnect(sessionPresent); });
|
||||
static_cast<espMqttClient *>(_mqttClient)->onDisconnect([this](espMqttClientTypes::DisconnectReason reason) { onMqttDisconnect(reason); });
|
||||
static_cast<espMqttClient *>(_mqttClient)
|
||||
->onMessage(
|
||||
[this](const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total) {
|
||||
onMqttMessage(properties, topic, payload, len, index, total);
|
||||
});
|
||||
}
|
||||
|
||||
void MqttSettingsService::loop() {
|
||||
if (_reconfigureMqtt || (_disconnectedAt && (uint32_t)(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
|
||||
if (_reconfigureMqtt || (_disconnectedAt && static_cast<uint32_t>(uuid::get_uptime() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
|
||||
// reconfigure MQTT client
|
||||
_disconnectedAt = configureMqtt() ? 0 : uuid::get_uptime();
|
||||
_reconfigureMqtt = false;
|
||||
@@ -116,6 +107,9 @@ void MqttSettingsService::onMqttMessage(const espMqttClientTypes::MessagePropert
|
||||
size_t len,
|
||||
size_t index,
|
||||
size_t total) {
|
||||
(void)properties;
|
||||
(void)index;
|
||||
(void)total;
|
||||
emsesp::EMSESP::mqtt_.on_message(topic, payload, len);
|
||||
}
|
||||
|
||||
@@ -128,9 +122,12 @@ MqttClient * MqttSettingsService::getMqttClient() {
|
||||
}
|
||||
|
||||
void MqttSettingsService::onMqttConnect(bool sessionPresent) {
|
||||
(void)sessionPresent;
|
||||
// _disconnectedAt = 0;
|
||||
emsesp::EMSESP::mqtt_.on_connect();
|
||||
// emsesp::EMSESP::logger().info("Connected to MQTT, %s", (sessionPresent) ? ("with persistent session") : ("without persistent session"));
|
||||
#ifdef EMSESP_DEBUG
|
||||
emsesp::EMSESP::logger().debug("Connected to MQTT, %s", (sessionPresent) ? ("with persistent session") : ("without persistent session"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void MqttSettingsService::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) {
|
||||
@@ -149,7 +146,7 @@ void MqttSettingsService::onConfigUpdated() {
|
||||
emsesp::EMSESP::mqtt_.start(); // reload EMS-ESP MQTT settings
|
||||
}
|
||||
|
||||
void MqttSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
void MqttSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
@@ -183,31 +180,25 @@ bool MqttSettingsService::configureMqtt() {
|
||||
_reconfigureMqtt = false;
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
if (_state.enableTLS) {
|
||||
// emsesp::EMSESP::logger().info("Start secure MQTT with rootCA");
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port);
|
||||
#if EMSESP_DEBUG
|
||||
emsesp::EMSESP::logger().debug("Start secure MQTT with rootCA");
|
||||
#endif
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setServer(_state.host.c_str(), _state.port);
|
||||
if (_state.username.length() > 0) {
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)
|
||||
->setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername),
|
||||
retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword));
|
||||
} else {
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setCredentials(retainCstr(nullptr, &_retainedUsername), retainCstr(nullptr, &_retainedPassword));
|
||||
->setCredentials(_state.username.c_str(), _state.password.length() > 0 ? _state.password.c_str() : nullptr);
|
||||
}
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setClientId(retainCstr(_state.clientId.c_str(), &_retainedClientId));
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setClientId(_state.clientId.c_str());
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setKeepAlive(_state.keepAlive);
|
||||
static_cast<espMqttClientSecure *>(_mqttClient)->setCleanSession(_state.cleanSession);
|
||||
return _mqttClient->connect();
|
||||
}
|
||||
#endif
|
||||
// emsesp::EMSESP::logger().info("Configuring MQTT client");
|
||||
static_cast<espMqttClient *>(_mqttClient)->setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port);
|
||||
static_cast<espMqttClient *>(_mqttClient)->setServer(_state.host.c_str(), _state.port);
|
||||
if (_state.username.length() > 0) {
|
||||
static_cast<espMqttClient *>(_mqttClient)
|
||||
->setCredentials(retainCstr(_state.username.c_str(), &_retainedUsername),
|
||||
retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword));
|
||||
} else {
|
||||
static_cast<espMqttClient *>(_mqttClient)->setCredentials(retainCstr(nullptr, &_retainedUsername), retainCstr(nullptr, &_retainedPassword));
|
||||
static_cast<espMqttClient *>(_mqttClient)->setCredentials(_state.username.c_str(), _state.password.length() > 0 ? _state.password.c_str() : nullptr);
|
||||
}
|
||||
static_cast<espMqttClient *>(_mqttClient)->setClientId(retainCstr(_state.clientId.c_str(), &_retainedClientId));
|
||||
static_cast<espMqttClient *>(_mqttClient)->setClientId(_state.clientId.c_str());
|
||||
static_cast<espMqttClient *>(_mqttClient)->setKeepAlive(_state.keepAlive);
|
||||
static_cast<espMqttClient *>(_mqttClient)->setCleanSession(_state.cleanSession);
|
||||
return _mqttClient->connect();
|
||||
@@ -265,33 +256,33 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
|
||||
#endif
|
||||
newSettings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
|
||||
newSettings.host = root["host"] | FACTORY_MQTT_HOST;
|
||||
newSettings.port = root["port"] | FACTORY_MQTT_PORT;
|
||||
newSettings.port = static_cast<uint16_t>(root["port"] | FACTORY_MQTT_PORT);
|
||||
newSettings.base = root["base"] | FACTORY_MQTT_BASE;
|
||||
newSettings.username = root["username"] | FACTORY_MQTT_USERNAME;
|
||||
newSettings.password = root["password"] | FACTORY_MQTT_PASSWORD;
|
||||
newSettings.clientId = root["client_id"] | FACTORY_MQTT_CLIENT_ID;
|
||||
newSettings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE;
|
||||
newSettings.clientId = root["client_id"] | generateClientId();
|
||||
newSettings.keepAlive = static_cast<uint16_t>(root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE);
|
||||
newSettings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
|
||||
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS;
|
||||
newSettings.mqtt_qos = static_cast<uint8_t>(root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS);
|
||||
newSettings.mqtt_retain = root["mqtt_retain"] | EMSESP_DEFAULT_MQTT_RETAIN;
|
||||
|
||||
newSettings.publish_time_boiler = root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
newSettings.publish_time_thermostat = root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
newSettings.publish_time_solar = root["publish_time_solar"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
newSettings.publish_time_mixer = root["publish_time_mixer"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
newSettings.publish_time_water = root["publish_time_water"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
newSettings.publish_time_heartbeat = root["publish_time_heartbeat"] | EMSESP_DEFAULT_PUBLISH_HEARTBEAT;
|
||||
newSettings.publish_time_boiler = static_cast<uint16_t>(root["publish_time_boiler"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_thermostat = static_cast<uint16_t>(root["publish_time_thermostat"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_solar = static_cast<uint16_t>(root["publish_time_solar"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_mixer = static_cast<uint16_t>(root["publish_time_mixer"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_water = static_cast<uint16_t>(root["publish_time_water"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_other = static_cast<uint16_t>(root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_sensor = static_cast<uint16_t>(root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME);
|
||||
newSettings.publish_time_heartbeat = static_cast<uint16_t>(root["publish_time_heartbeat"] | EMSESP_DEFAULT_PUBLISH_HEARTBEAT);
|
||||
|
||||
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
|
||||
newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT;
|
||||
newSettings.nested_format = static_cast<uint8_t>(root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT);
|
||||
newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX;
|
||||
newSettings.discovery_type = root["discovery_type"] | EMSESP_DEFAULT_DISCOVERY_TYPE;
|
||||
newSettings.discovery_type = static_cast<uint8_t>(root["discovery_type"] | EMSESP_DEFAULT_DISCOVERY_TYPE);
|
||||
newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE;
|
||||
newSettings.publish_single2cmd = root["publish_single2cmd"] | EMSESP_DEFAULT_PUBLISH_SINGLE2CMD;
|
||||
newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE;
|
||||
newSettings.entity_format = root["entity_format"] | EMSESP_DEFAULT_ENTITY_FORMAT;
|
||||
newSettings.entity_format = static_cast<uint8_t>(root["entity_format"] | EMSESP_DEFAULT_ENTITY_FORMAT);
|
||||
|
||||
if (newSettings.enabled != settings.enabled) {
|
||||
changed = true;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#ifndef MqttSettingsService_h
|
||||
#define MqttSettingsService_h
|
||||
|
||||
#include <StatefulService.h>
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
#include "StatefulService.h"
|
||||
#include "HttpEndpoint.h"
|
||||
#include "FSPersistence.h"
|
||||
|
||||
#include <espMqttClient.h>
|
||||
#include <ESPUtils.h>
|
||||
|
||||
#include <uuid/common.h>
|
||||
|
||||
@@ -38,13 +38,6 @@
|
||||
#define FACTORY_MQTT_PASSWORD ""
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_MQTT_CLIENT_ID
|
||||
#define FACTORY_MQTT_CLIENT_ID generateClientId()
|
||||
static String generateClientId() {
|
||||
return ESPUtils::defaultDeviceValue("esp32-");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef FACTORY_MQTT_KEEP_ALIVE
|
||||
#define FACTORY_MQTT_KEEP_ALIVE 16
|
||||
#endif
|
||||
@@ -59,21 +52,14 @@ static String generateClientId() {
|
||||
|
||||
class MqttSettings {
|
||||
public:
|
||||
// host and port - if enabled
|
||||
bool enabled;
|
||||
String host;
|
||||
uint16_t port;
|
||||
String rootCA;
|
||||
bool enableTLS;
|
||||
|
||||
// username and password
|
||||
String username;
|
||||
String password;
|
||||
|
||||
// client id settings
|
||||
String clientId;
|
||||
|
||||
// connection settings
|
||||
String username;
|
||||
String password;
|
||||
String clientId;
|
||||
uint16_t keepAlive;
|
||||
bool cleanSession;
|
||||
|
||||
@@ -124,14 +110,6 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
|
||||
HttpEndpoint<MqttSettings> _httpEndpoint;
|
||||
FSPersistence<MqttSettings> _fsPersistence;
|
||||
|
||||
// Pointers to hold retained copies of the mqtt client connection strings.
|
||||
// This is required as espMqttClient holds references to the supplied connection strings.
|
||||
char * _retainedHost;
|
||||
char * _retainedClientId;
|
||||
char * _retainedUsername;
|
||||
char * _retainedPassword;
|
||||
char * _retainedRootCA;
|
||||
|
||||
// variable to help manage connection
|
||||
bool _reconfigureMqtt;
|
||||
unsigned long _disconnectedAt;
|
||||
@@ -142,11 +120,12 @@ class MqttSettingsService : public StatefulService<MqttSettings> {
|
||||
// the MQTT client instance
|
||||
MqttClient * _mqttClient;
|
||||
|
||||
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
void WiFiEvent(WiFiEvent_t event);
|
||||
void onMqttConnect(bool sessionPresent);
|
||||
void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason);
|
||||
void
|
||||
onMqttMessage(const espMqttClientTypes::MessageProperties & properties, const char * topic, const uint8_t * payload, size_t len, size_t index, size_t total);
|
||||
|
||||
bool configureMqtt();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
#include <MqttStatus.h>
|
||||
#include "MqttStatus.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
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));
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { mqttStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void MqttStatus::mqttStatus(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
root["enabled"] = _mqttSettingsService->isEnabled();
|
||||
root["connected"] = _mqttSettingsService->isConnected();
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
#define MqttStatus_h
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <MqttSettingsService.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#include "MqttSettingsService.h"
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#define MQTT_STATUS_SERVICE_PATH "/rest/mqttStatus"
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
#include <NTPSettingsService.h>
|
||||
#include "NTPSettingsService.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
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(TIME_PATH,
|
||||
securityManager->wrapCallback([this](AsyncWebServerRequest * request, JsonVariant json) { configureTime(request, json); },
|
||||
AuthenticationPredicates::IS_ADMIN))
|
||||
, _connected(false) {
|
||||
_timeHandler.setMethod(HTTP_POST);
|
||||
_timeHandler.setMaxContentLength(MAX_TIME_SIZE);
|
||||
server->addHandler(&_timeHandler);
|
||||
|
||||
WiFi.onEvent(std::bind(&NTPSettingsService::WiFiEvent, this, _1));
|
||||
|
||||
addUpdateHandler([&](const String & originId) { configureNTP(); }, false);
|
||||
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
|
||||
addUpdateHandler([this] { configureNTP(); }, false);
|
||||
}
|
||||
|
||||
void NTPSettingsService::begin() {
|
||||
@@ -27,9 +27,9 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
if (connected_) {
|
||||
if (_connected) {
|
||||
emsesp::EMSESP::logger().info("WiFi connection dropped, stopping NTP");
|
||||
connected_ = false;
|
||||
_connected = false;
|
||||
configureNTP();
|
||||
}
|
||||
break;
|
||||
@@ -37,7 +37,7 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
// emsesp::EMSESP::logger().info("Got IP address, starting NTP synchronization");
|
||||
connected_ = true;
|
||||
_connected = true;
|
||||
configureNTP();
|
||||
break;
|
||||
|
||||
@@ -49,7 +49,7 @@ void NTPSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
// https://werner.rothschopf.net/microcontroller/202103_arduino_esp32_ntp_en.htm
|
||||
void NTPSettingsService::configureNTP() {
|
||||
emsesp::EMSESP::system_.ntp_connected(false);
|
||||
if (connected_ && _state.enabled) {
|
||||
if (_connected && _state.enabled) {
|
||||
emsesp::EMSESP::logger().info("Starting NTP service");
|
||||
esp_sntp_set_sync_interval(3600000); // one hour
|
||||
esp_sntp_set_time_sync_notification_cb(ntp_received);
|
||||
@@ -63,13 +63,13 @@ void NTPSettingsService::configureNTP() {
|
||||
|
||||
void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVariant json) {
|
||||
if (json.is<JsonObject>()) {
|
||||
struct tm tm = {0};
|
||||
struct tm tm = {};
|
||||
String timeLocal = json["local_time"];
|
||||
char * s = strptime(timeLocal.c_str(), "%Y-%m-%dT%H:%M:%S", &tm);
|
||||
if (s != nullptr) {
|
||||
tm.tm_isdst = -1; // not set by strptime, tells mktime to determine daylightsaving
|
||||
time_t time = mktime(&tm);
|
||||
struct timeval now = {.tv_sec = time};
|
||||
struct timeval now = {.tv_sec = time, .tv_usec = {}};
|
||||
settimeofday(&now, nullptr);
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
@@ -82,6 +82,22 @@ void NTPSettingsService::configureTime(AsyncWebServerRequest * request, JsonVari
|
||||
}
|
||||
|
||||
void NTPSettingsService::ntp_received(struct timeval * tv) {
|
||||
(void)tv;
|
||||
// emsesp::EMSESP::logger().info("NTP sync to %d sec", tv->tv_sec);
|
||||
emsesp::EMSESP::system_.ntp_connected(true);
|
||||
}
|
||||
|
||||
void NTPSettings::read(NTPSettings & settings, JsonObject root) {
|
||||
root["enabled"] = settings.enabled;
|
||||
root["server"] = settings.server;
|
||||
root["tz_label"] = settings.tzLabel;
|
||||
root["tz_format"] = settings.tzFormat;
|
||||
}
|
||||
|
||||
StateUpdateResult NTPSettings::update(JsonObject root, NTPSettings & settings) {
|
||||
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
|
||||
settings.server = root["server"] | FACTORY_NTP_SERVER;
|
||||
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
||||
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
#ifndef NTPSettingsService_h
|
||||
#define NTPSettingsService_h
|
||||
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
#include "HttpEndpoint.h"
|
||||
#include "FSPersistence.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <ctime>
|
||||
#include <esp_sntp.h>
|
||||
|
||||
#ifndef FACTORY_NTP_ENABLED
|
||||
@@ -36,20 +36,8 @@ class NTPSettings {
|
||||
String tzFormat;
|
||||
String server;
|
||||
|
||||
static void read(NTPSettings & settings, JsonObject root) {
|
||||
root["enabled"] = settings.enabled;
|
||||
root["server"] = settings.server;
|
||||
root["tz_label"] = settings.tzLabel;
|
||||
root["tz_format"] = settings.tzFormat;
|
||||
}
|
||||
|
||||
static StateUpdateResult update(JsonObject root, NTPSettings & settings) {
|
||||
settings.enabled = root["enabled"] | FACTORY_NTP_ENABLED;
|
||||
settings.server = root["server"] | FACTORY_NTP_SERVER;
|
||||
settings.tzLabel = root["tz_label"] | FACTORY_NTP_TIME_ZONE_LABEL;
|
||||
settings.tzFormat = root["tz_format"] | FACTORY_NTP_TIME_ZONE_FORMAT;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
static void read(NTPSettings & settings, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, NTPSettings & settings);
|
||||
};
|
||||
|
||||
class NTPSettingsService : public StatefulService<NTPSettings> {
|
||||
@@ -63,8 +51,8 @@ class NTPSettingsService : public StatefulService<NTPSettings> {
|
||||
HttpEndpoint<NTPSettings> _httpEndpoint;
|
||||
FSPersistence<NTPSettings> _fsPersistence;
|
||||
AsyncCallbackJsonWebHandler _timeHandler;
|
||||
bool _connected;
|
||||
|
||||
bool connected_ = false;
|
||||
void WiFiEvent(WiFiEvent_t event);
|
||||
void configureNTP();
|
||||
void configureTime(AsyncWebServerRequest * request, JsonVariant json);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include <NTPStatus.h>
|
||||
#include "NTPStatus.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
#include <array>
|
||||
|
||||
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));
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { ntpStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -15,9 +16,9 @@ NTPStatus::NTPStatus(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
* Uses a 25 byte buffer, large enough to fit an ISO time string with offset.
|
||||
*/
|
||||
String formatTime(tm * time, const char * format) {
|
||||
char time_string[25];
|
||||
strftime(time_string, 25, format, time);
|
||||
return String(time_string);
|
||||
std::array<char, 25> time_string{};
|
||||
strftime(time_string.data(), time_string.size(), format, time);
|
||||
return {time_string.data()};
|
||||
}
|
||||
|
||||
String toUTCTimeString(tm * time) {
|
||||
@@ -29,14 +30,23 @@ String toLocalTimeString(tm * time) {
|
||||
}
|
||||
|
||||
void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
// grab the current instant in unix seconds
|
||||
time_t now = time(nullptr);
|
||||
|
||||
// only provide enabled/disabled status for now
|
||||
root["status"] = esp_sntp_enabled() ? emsesp::EMSESP::system_.ntp_connected() ? 2 : 1 : 0;
|
||||
root["status"] = [] {
|
||||
if (esp_sntp_enabled()) {
|
||||
if (emsesp::EMSESP::system_.ntp_connected()) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}();
|
||||
|
||||
// the current time in UTC
|
||||
root["utc_time"] = toUTCTimeString(gmtime(&now));
|
||||
@@ -49,4 +59,4 @@ void NTPStatus::ntpStatus(AsyncWebServerRequest * request) {
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
#ifndef NTPStatus_h
|
||||
#define NTPStatus_h
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <ctime>
|
||||
#include <WiFi.h>
|
||||
#include <esp_sntp.h>
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#include <uuid/common.h>
|
||||
|
||||
#define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus"
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
#include <NetworkSettingsService.h>
|
||||
#include "NetworkSettingsService.h"
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
NetworkSettingsService::NetworkSettingsService(AsyncWebServer * 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) {
|
||||
addUpdateHandler([&](const String & originId) { reconfigureWiFiConnection(); }, false);
|
||||
WiFi.onEvent(std::bind(&NetworkSettingsService::WiFiEvent, this, _1));
|
||||
, _lastConnectionAttempt(0)
|
||||
, _stopping(false) {
|
||||
addUpdateHandler([this] { reconfigureWiFiConnection(); }, false);
|
||||
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event, info); });
|
||||
}
|
||||
|
||||
static bool formatBssid(const String & bssid, uint8_t (&mac)[6]) {
|
||||
uint tmp[6];
|
||||
if (bssid.isEmpty() || sscanf(bssid.c_str(), "%X:%X:%X:%X:%X:%X", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != 6) {
|
||||
return false;
|
||||
}
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
mac[i] = static_cast<uint8_t>(tmp[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkSettingsService::begin() {
|
||||
@@ -44,7 +56,7 @@ void NetworkSettingsService::reconfigureWiFiConnection() {
|
||||
|
||||
void NetworkSettingsService::loop() {
|
||||
unsigned long currentMillis = millis();
|
||||
if (!_lastConnectionAttempt || (uint32_t)(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) {
|
||||
if (!_lastConnectionAttempt || static_cast<uint32_t>(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) {
|
||||
_lastConnectionAttempt = currentMillis;
|
||||
manageSTA();
|
||||
}
|
||||
@@ -65,49 +77,379 @@ void NetworkSettingsService::manageSTA() {
|
||||
|
||||
// www.esp32.com/viewtopic.php?t=12055
|
||||
if (_state.bandwidth20) {
|
||||
esp_wifi_set_bandwidth((wifi_interface_t)ESP_IF_WIFI_STA, WIFI_BW_HT20);
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT20);
|
||||
} else {
|
||||
esp_wifi_set_bandwidth((wifi_interface_t)ESP_IF_WIFI_STA, WIFI_BW_HT40);
|
||||
esp_wifi_set_bandwidth(static_cast<wifi_interface_t>(ESP_IF_WIFI_STA), WIFI_BW_HT40);
|
||||
}
|
||||
if (_state.nosleep) {
|
||||
WiFi.setSleep(false); // turn off sleep - WIFI_PS_NONE
|
||||
}
|
||||
|
||||
// attempt to connect to the network
|
||||
uint mac[6];
|
||||
if (!_state.bssid.isEmpty() && sscanf(_state.bssid.c_str(), "%X:%X:%X:%X:%X:%X", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6) {
|
||||
uint8_t mac1[6];
|
||||
for (uint8_t i = 0; i < 6; i++) {
|
||||
mac1[i] = (uint8_t)mac[i];
|
||||
}
|
||||
WiFi.begin(_state.ssid.c_str(), _state.password.c_str(), 0, mac1);
|
||||
uint8_t bssid[6];
|
||||
if (formatBssid(_state.bssid, bssid)) {
|
||||
WiFi.begin(_state.ssid.c_str(), _state.password.c_str(), 0, bssid);
|
||||
} else {
|
||||
WiFi.begin(_state.ssid.c_str(), _state.password.c_str());
|
||||
}
|
||||
// set power after wifi is startet, fixed value for C3_V1
|
||||
// if (WiFi.isConnected()) {
|
||||
|
||||
#ifdef BOARD_C3_MINI_V1
|
||||
// always hardcode Tx power for Wemos CS Mini v1
|
||||
// v1 needs this value, see https://github.com/emsesp/EMS-ESP32/pull/620#discussion_r993173979
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm); // https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
|
||||
// https://www.wemos.cc/en/latest/c3/c3_mini_1_0_0.html#about-wifi
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm);
|
||||
#else
|
||||
// esp_wifi_set_max_tx_power(_state.tx_power * 4);
|
||||
// TODO make it dynamic
|
||||
WiFi.setTxPower((wifi_power_t)(_state.tx_power * 4));
|
||||
if (_state.tx_power != 0) {
|
||||
// if not set to Auto (0) set the Tx power now
|
||||
if (!WiFi.setTxPower(static_cast<wifi_power_t>(_state.tx_power))) {
|
||||
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// }
|
||||
} else { // not connected but STA-mode active => disconnect
|
||||
reconfigureWiFiConnection();
|
||||
}
|
||||
}
|
||||
|
||||
// handles if wifi stopped
|
||||
void NetworkSettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
if (event == ARDUINO_EVENT_WIFI_STA_STOP) {
|
||||
// set the TxPower based on the RSSI (signal strength), picking the lowest value
|
||||
// code is based of RSSI (signal strength) and copied from Tasmota's WiFiSetTXpowerBasedOnRssi() which is copied ESPEasy's ESPEasyWifi.SetWiFiTXpower() function
|
||||
void NetworkSettingsService::setWiFiPowerOnRSSI() {
|
||||
// Range ESP32 : 2dBm - 20dBm
|
||||
// 802.11b - wifi1
|
||||
// 802.11a - wifi2
|
||||
// 802.11g - wifi3
|
||||
// 802.11n - wifi4
|
||||
// 802.11ac - wifi5
|
||||
// 802.11ax - wifi6
|
||||
|
||||
int max_tx_pwr = MAX_TX_PWR_DBM_n; // assume wifi4
|
||||
int threshold = WIFI_SENSITIVITY_n + 70; // Margin in dBm * 10 on top of threshold
|
||||
|
||||
// Assume AP sends with max set by ETSI standard.
|
||||
// 2.4 GHz: 100 mWatt (20 dBm)
|
||||
// US and some other countries allow 1000 mW (30 dBm)
|
||||
int rssi = WiFi.RSSI() * 10;
|
||||
int newrssi = rssi - 200; // We cannot send with over 20 dBm, thus it makes no sense to force higher TX power all the time.
|
||||
|
||||
int min_tx_pwr = 0;
|
||||
if (newrssi < threshold) {
|
||||
min_tx_pwr = threshold - newrssi;
|
||||
}
|
||||
if (min_tx_pwr > max_tx_pwr) {
|
||||
min_tx_pwr = max_tx_pwr;
|
||||
}
|
||||
|
||||
uint8_t set_power = min_tx_pwr / 10; // this is the recommended power setting to use
|
||||
|
||||
// from WiFIGeneric.h use:
|
||||
// WIFI_POWER_19_5dBm = 78,// 19.5dBm
|
||||
// WIFI_POWER_19dBm = 76,// 19dBm
|
||||
// WIFI_POWER_18_5dBm = 74,// 18.5dBm
|
||||
// WIFI_POWER_17dBm = 68,// 17dBm
|
||||
// WIFI_POWER_15dBm = 60,// 15dBm
|
||||
// WIFI_POWER_13dBm = 52,// 13dBm
|
||||
// WIFI_POWER_11dBm = 44,// 11dBm
|
||||
// WIFI_POWER_8_5dBm = 34,// 8.5dBm
|
||||
// WIFI_POWER_7dBm = 28,// 7dBm
|
||||
// WIFI_POWER_5dBm = 20,// 5dBm
|
||||
// WIFI_POWER_2dBm = 8,// 2dBm
|
||||
// WIFI_POWER_MINUS_1dBm = -4// -1dBm
|
||||
wifi_power_t p = WIFI_POWER_2dBm;
|
||||
if (min_tx_pwr > 185)
|
||||
p = WIFI_POWER_19_5dBm;
|
||||
else if (min_tx_pwr > 170)
|
||||
p = WIFI_POWER_18_5dBm;
|
||||
else if (min_tx_pwr > 150)
|
||||
p = WIFI_POWER_17dBm;
|
||||
else if (min_tx_pwr > 130)
|
||||
p = WIFI_POWER_15dBm;
|
||||
else if (min_tx_pwr > 110)
|
||||
p = WIFI_POWER_13dBm;
|
||||
else if (min_tx_pwr > 85)
|
||||
p = WIFI_POWER_11dBm;
|
||||
else if (min_tx_pwr > 70)
|
||||
p = WIFI_POWER_8_5dBm;
|
||||
else if (min_tx_pwr > 50)
|
||||
p = WIFI_POWER_7dBm;
|
||||
else if (min_tx_pwr > 20)
|
||||
p = WIFI_POWER_5dBm;
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
emsesp::EMSESP::logger().debug("Recommended set WiFi Tx Power (set_power %d, new power %d, rssi %d, threshold %d)", set_power, p, rssi, threshold);
|
||||
#else
|
||||
char result[10];
|
||||
emsesp::EMSESP::logger().info("Setting WiFi Tx Power to %s dBm", emsesp::Helpers::render_value(result, ((double)(p) / 4), 1));
|
||||
#endif
|
||||
|
||||
if (!WiFi.setTxPower(p)) {
|
||||
emsesp::EMSESP::logger().warning("Failed to set WiFi Tx Power");
|
||||
}
|
||||
}
|
||||
|
||||
// start the multicast UDP service so EMS-ESP is discoverable via .local
|
||||
void NetworkSettingsService::mDNS_start() const {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
MDNS.end();
|
||||
|
||||
if (_state.enableMDNS) {
|
||||
if (!MDNS.begin(emsesp::EMSESP::system_.hostname().c_str())) {
|
||||
emsesp::EMSESP::logger().warning("Failed to start mDNS Responder service");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string address_s = emsesp::EMSESP::system_.hostname() + ".local";
|
||||
|
||||
MDNS.addService("http", "tcp", 80); // add our web server and rest API
|
||||
MDNS.addService("telnet", "tcp", 23); // add our telnet console
|
||||
|
||||
MDNS.addServiceTxt("http", "tcp", "version", EMSESP_APP_VERSION);
|
||||
MDNS.addServiceTxt("http", "tcp", "address", address_s.c_str());
|
||||
|
||||
emsesp::EMSESP::logger().info("Starting mDNS Responder service");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const char * NetworkSettingsService::disconnectReason(uint8_t code) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
switch (code) {
|
||||
case WIFI_REASON_UNSPECIFIED: // = 1,
|
||||
return "unspecified";
|
||||
case WIFI_REASON_AUTH_EXPIRE: // = 2,
|
||||
return "auth expire";
|
||||
case WIFI_REASON_AUTH_LEAVE: // = 3,
|
||||
return "auth leave";
|
||||
case WIFI_REASON_ASSOC_EXPIRE: // = 4,
|
||||
return "assoc expired";
|
||||
case WIFI_REASON_ASSOC_TOOMANY: // = 5,
|
||||
return "assoc too many";
|
||||
case WIFI_REASON_NOT_AUTHED: // = 6,
|
||||
return "not authenticated";
|
||||
case WIFI_REASON_NOT_ASSOCED: // = 7,
|
||||
return "not assoc";
|
||||
case WIFI_REASON_ASSOC_LEAVE: // = 8,
|
||||
return "assoc leave";
|
||||
case WIFI_REASON_ASSOC_NOT_AUTHED: // = 9,
|
||||
return "assoc not authed";
|
||||
case WIFI_REASON_DISASSOC_PWRCAP_BAD: // = 10,
|
||||
return "disassoc powerCAP bad";
|
||||
case WIFI_REASON_DISASSOC_SUPCHAN_BAD: // = 11,
|
||||
return "disassoc supchan bad";
|
||||
case WIFI_REASON_IE_INVALID: // = 13,
|
||||
return "IE invalid";
|
||||
case WIFI_REASON_MIC_FAILURE: // = 14,
|
||||
return "MIC failure";
|
||||
case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT: // = 15,
|
||||
return "4way handshake timeout";
|
||||
case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT: // = 16,
|
||||
return "group key-update timeout";
|
||||
case WIFI_REASON_IE_IN_4WAY_DIFFERS: // = 17,
|
||||
return "IE in 4way differs";
|
||||
case WIFI_REASON_GROUP_CIPHER_INVALID: // = 18,
|
||||
return "group cipher invalid";
|
||||
case WIFI_REASON_PAIRWISE_CIPHER_INVALID: // = 19,
|
||||
return "pairwise cipher invalid";
|
||||
case WIFI_REASON_AKMP_INVALID: // = 20,
|
||||
return "AKMP invalid";
|
||||
case WIFI_REASON_UNSUPP_RSN_IE_VERSION: // = 21,
|
||||
return "unsupported RSN_IE version";
|
||||
case WIFI_REASON_INVALID_RSN_IE_CAP: // = 22,
|
||||
return "invalid RSN_IE_CAP";
|
||||
case WIFI_REASON_802_1X_AUTH_FAILED: // = 23,
|
||||
return "802 X1 auth failed";
|
||||
case WIFI_REASON_CIPHER_SUITE_REJECTED: // = 24,
|
||||
return "cipher suite rejected";
|
||||
case WIFI_REASON_BEACON_TIMEOUT: // = 200,
|
||||
return "beacon timeout";
|
||||
case WIFI_REASON_NO_AP_FOUND: // = 201,
|
||||
return "no AP found";
|
||||
case WIFI_REASON_AUTH_FAIL: // = 202,
|
||||
return "auth fail";
|
||||
case WIFI_REASON_ASSOC_FAIL: // = 203,
|
||||
return "assoc fail";
|
||||
case WIFI_REASON_HANDSHAKE_TIMEOUT: // = 204,
|
||||
return "handshake timeout";
|
||||
case WIFI_REASON_CONNECTION_FAIL: // 205,
|
||||
return "connection fail";
|
||||
case WIFI_REASON_AP_TSF_RESET: // 206,
|
||||
return "AP tsf reset";
|
||||
case WIFI_REASON_ROAMING: // 207,
|
||||
return "roaming";
|
||||
case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG: // 208,
|
||||
return "assoc comeback time too long";
|
||||
case WIFI_REASON_SA_QUERY_TIMEOUT: // 209,
|
||||
return "sa query timeout";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
#endif
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// handles both WiFI and Ethernet
|
||||
void NetworkSettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_STOP:
|
||||
if (_stopping) {
|
||||
_lastConnectionAttempt = 0;
|
||||
_stopping = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
emsesp::EMSESP::logger().warning("WiFi disconnected. Reason: %s (%d)",
|
||||
disconnectReason(info.wifi_sta_disconnected.reason),
|
||||
info.wifi_sta_disconnected.reason); // IDF 4.0
|
||||
emsesp::EMSESP::system_.has_ipv6(false);
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
char result[10];
|
||||
emsesp::EMSESP::logger().info("WiFi connected (IP=%s, hostname=%s, TxPower=%s dBm)",
|
||||
WiFi.localIP().toString().c_str(),
|
||||
WiFi.getHostname(),
|
||||
emsesp::Helpers::render_value(result, ((double)(WiFi.getTxPower()) / 4), 1));
|
||||
mDNS_start();
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
ETH.setHostname(emsesp::EMSESP::system_.hostname().c_str());
|
||||
|
||||
// configure for static IP
|
||||
if (_state.staticIPConfig) {
|
||||
ETH.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
|
||||
}
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
// prevent double calls
|
||||
if (!emsesp::EMSESP::system_.ethernet_connected()) {
|
||||
emsesp::EMSESP::logger().info("Ethernet connected (IP=%s, speed %d Mbps)", ETH.localIP().toString().c_str(), ETH.linkSpeed());
|
||||
emsesp::EMSESP::system_.ethernet_connected(true);
|
||||
mDNS_start();
|
||||
}
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
emsesp::EMSESP::logger().warning("Ethernet disconnected");
|
||||
emsesp::EMSESP::system_.ethernet_connected(false);
|
||||
emsesp::EMSESP::system_.has_ipv6(false);
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_STOP:
|
||||
emsesp::EMSESP::logger().info("Ethernet stopped");
|
||||
emsesp::EMSESP::system_.ethernet_connected(false);
|
||||
emsesp::EMSESP::system_.has_ipv6(false);
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
// Set the TxPower after the connection is established, if we're using TxPower = 0 (Auto)
|
||||
if (_state.tx_power == 0) {
|
||||
setWiFiPowerOnRSSI();
|
||||
}
|
||||
if (_state.enableIPv6) {
|
||||
WiFi.enableIpV6();
|
||||
}
|
||||
break;
|
||||
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
if (_state.enableIPv6) {
|
||||
ETH.enableIpV6();
|
||||
}
|
||||
break;
|
||||
|
||||
// IPv6 specific
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||
case ARDUINO_EVENT_ETH_GOT_IP6:
|
||||
if (emsesp::EMSESP::system_.ethernet_connected()) {
|
||||
emsesp::EMSESP::logger().info("Ethernet connected (IPv6=%s, speed %d Mbps)", ETH.localIPv6().toString().c_str(), ETH.linkSpeed());
|
||||
} else {
|
||||
emsesp::EMSESP::logger().info("WiFi connected (IPv6=%s, hostname=%s, TxPower=%s dBm)",
|
||||
WiFi.localIPv6().toString().c_str(),
|
||||
WiFi.getHostname(),
|
||||
emsesp::Helpers::render_value(result, ((double)(WiFi.getTxPower()) / 4), 1));
|
||||
}
|
||||
mDNS_start();
|
||||
emsesp::EMSESP::system_.has_ipv6(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// if (!_stopping && (event == ARDUINO_EVENT_WIFI_STA_LOST_IP || event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED)) {
|
||||
// reconfigureWiFiConnection();
|
||||
// }
|
||||
#endif
|
||||
}
|
||||
|
||||
void NetworkSettings::read(NetworkSettings & settings, JsonObject root) {
|
||||
// connection settings
|
||||
root["ssid"] = settings.ssid;
|
||||
root["bssid"] = settings.bssid;
|
||||
root["password"] = settings.password;
|
||||
root["hostname"] = settings.hostname;
|
||||
root["static_ip_config"] = settings.staticIPConfig;
|
||||
root["enableIPv6"] = settings.enableIPv6;
|
||||
root["bandwidth20"] = settings.bandwidth20;
|
||||
root["nosleep"] = settings.nosleep;
|
||||
root["enableMDNS"] = settings.enableMDNS;
|
||||
root["enableCORS"] = settings.enableCORS;
|
||||
root["CORSOrigin"] = settings.CORSOrigin;
|
||||
root["tx_power"] = settings.tx_power;
|
||||
|
||||
// extended settings
|
||||
JsonUtils::writeIP(root, "local_ip", settings.localIP);
|
||||
JsonUtils::writeIP(root, "gateway_ip", settings.gatewayIP);
|
||||
JsonUtils::writeIP(root, "subnet_mask", settings.subnetMask);
|
||||
JsonUtils::writeIP(root, "dns_ip_1", settings.dnsIP1);
|
||||
JsonUtils::writeIP(root, "dns_ip_2", settings.dnsIP2);
|
||||
}
|
||||
|
||||
StateUpdateResult NetworkSettings::update(JsonObject root, NetworkSettings & settings) {
|
||||
// keep copy of original settings
|
||||
auto enableCORS = settings.enableCORS;
|
||||
auto CORSOrigin = settings.CORSOrigin;
|
||||
auto ssid = settings.ssid;
|
||||
auto tx_power = settings.tx_power;
|
||||
|
||||
settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
|
||||
settings.bssid = root["bssid"] | "";
|
||||
settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
|
||||
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
|
||||
settings.staticIPConfig = root["static_ip_config"] | false;
|
||||
settings.enableIPv6 = root["enableIPv6"] | false;
|
||||
settings.bandwidth20 = root["bandwidth20"] | false;
|
||||
settings.tx_power = static_cast<uint8_t>(root["tx_power"] | 0);
|
||||
settings.nosleep = root["nosleep"] | false;
|
||||
settings.enableMDNS = root["enableMDNS"] | true;
|
||||
settings.enableCORS = root["enableCORS"] | false;
|
||||
settings.CORSOrigin = root["CORSOrigin"] | "*";
|
||||
|
||||
// extended settings
|
||||
JsonUtils::readIP(root, "local_ip", settings.localIP);
|
||||
JsonUtils::readIP(root, "gateway_ip", settings.gatewayIP);
|
||||
JsonUtils::readIP(root, "subnet_mask", settings.subnetMask);
|
||||
JsonUtils::readIP(root, "dns_ip_1", settings.dnsIP1);
|
||||
JsonUtils::readIP(root, "dns_ip_2", settings.dnsIP2);
|
||||
|
||||
// Swap around the dns servers if 2 is populated but 1 is not
|
||||
if (IPUtils::isNotSet(settings.dnsIP1) && IPUtils::isSet(settings.dnsIP2)) {
|
||||
settings.dnsIP1 = settings.dnsIP2;
|
||||
settings.dnsIP2 = INADDR_NONE;
|
||||
}
|
||||
|
||||
// Turning off static ip config if we don't meet the minimum requirements
|
||||
// of ipAddress, gateway and subnet. This may change to static ip only
|
||||
// as sensible defaults can be assumed for gateway and subnet
|
||||
if (settings.staticIPConfig && (IPUtils::isNotSet(settings.localIP) || IPUtils::isNotSet(settings.gatewayIP) || IPUtils::isNotSet(settings.subnetMask))) {
|
||||
settings.staticIPConfig = false;
|
||||
}
|
||||
|
||||
// see if we need to inform the user of a restart
|
||||
if (tx_power != settings.tx_power || enableCORS != settings.enableCORS || CORSOrigin != settings.CORSOrigin
|
||||
|| (ssid != settings.ssid && settings.ssid.isEmpty())) {
|
||||
return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed
|
||||
}
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
#ifndef NetworkSettingsService_h
|
||||
#define NetworkSettingsService_h
|
||||
|
||||
#include <StatefulService.h>
|
||||
#include <FSPersistence.h>
|
||||
#include <HttpEndpoint.h>
|
||||
#include <JsonUtils.h>
|
||||
#include "StatefulService.h"
|
||||
#include "FSPersistence.h"
|
||||
#include "HttpEndpoint.h"
|
||||
#include "JsonUtils.h"
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include <esp_wifi.h>
|
||||
#include <ETH.h>
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <ESPmDNS.h>
|
||||
#endif
|
||||
|
||||
#define NETWORK_SETTINGS_FILE "/config/networkSettings.json"
|
||||
#define NETWORK_SETTINGS_SERVICE_PATH "/rest/networkSettings"
|
||||
#define WIFI_RECONNECTION_DELAY 1000 * 3
|
||||
#define WIFI_RECONNECTION_DELAY (1000 * 3)
|
||||
|
||||
#ifndef FACTORY_WIFI_SSID
|
||||
#define FACTORY_WIFI_SSID ""
|
||||
@@ -27,21 +30,52 @@
|
||||
#define FACTORY_WIFI_HOSTNAME ""
|
||||
#endif
|
||||
|
||||
// copied from Tasmota
|
||||
#if CONFIG_IDF_TARGET_ESP32S2
|
||||
#define MAX_TX_PWR_DBM_11b 195
|
||||
#define MAX_TX_PWR_DBM_54g 150
|
||||
#define MAX_TX_PWR_DBM_n 130
|
||||
#define WIFI_SENSITIVITY_11b -880
|
||||
#define WIFI_SENSITIVITY_54g -750
|
||||
#define WIFI_SENSITIVITY_n -720
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
#define MAX_TX_PWR_DBM_11b 210
|
||||
#define MAX_TX_PWR_DBM_54g 190
|
||||
#define MAX_TX_PWR_DBM_n 185
|
||||
#define WIFI_SENSITIVITY_11b -880
|
||||
#define WIFI_SENSITIVITY_54g -760
|
||||
#define WIFI_SENSITIVITY_n -720
|
||||
#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3
|
||||
#define MAX_TX_PWR_DBM_11b 210
|
||||
#define MAX_TX_PWR_DBM_54g 190
|
||||
#define MAX_TX_PWR_DBM_n 185
|
||||
#define WIFI_SENSITIVITY_11b -880
|
||||
#define WIFI_SENSITIVITY_54g -760
|
||||
#define WIFI_SENSITIVITY_n -730
|
||||
#else
|
||||
#define MAX_TX_PWR_DBM_11b 195
|
||||
#define MAX_TX_PWR_DBM_54g 160
|
||||
#define MAX_TX_PWR_DBM_n 140
|
||||
#define WIFI_SENSITIVITY_11b -880
|
||||
#define WIFI_SENSITIVITY_54g -750
|
||||
#define WIFI_SENSITIVITY_n -700
|
||||
#endif
|
||||
|
||||
class NetworkSettings {
|
||||
public:
|
||||
// core wifi configuration
|
||||
String ssid;
|
||||
String bssid;
|
||||
String password;
|
||||
String hostname;
|
||||
bool staticIPConfig;
|
||||
bool enableIPv6;
|
||||
bool bandwidth20;
|
||||
int8_t tx_power;
|
||||
bool nosleep;
|
||||
bool enableMDNS;
|
||||
bool enableCORS;
|
||||
String CORSOrigin;
|
||||
String ssid;
|
||||
String bssid;
|
||||
String password;
|
||||
String hostname;
|
||||
bool staticIPConfig;
|
||||
bool enableIPv6;
|
||||
bool bandwidth20;
|
||||
uint8_t tx_power;
|
||||
bool nosleep;
|
||||
bool enableMDNS;
|
||||
bool enableCORS;
|
||||
String CORSOrigin;
|
||||
|
||||
// optional configuration for static IP address
|
||||
IPAddress localIP;
|
||||
@@ -50,73 +84,11 @@ class NetworkSettings {
|
||||
IPAddress dnsIP1;
|
||||
IPAddress dnsIP2;
|
||||
|
||||
static void read(NetworkSettings & settings, JsonObject root) {
|
||||
// connection settings
|
||||
root["ssid"] = settings.ssid;
|
||||
root["bssid"] = settings.bssid;
|
||||
root["password"] = settings.password;
|
||||
root["hostname"] = settings.hostname;
|
||||
root["static_ip_config"] = settings.staticIPConfig;
|
||||
root["enableIPv6"] = settings.enableIPv6;
|
||||
root["bandwidth20"] = settings.bandwidth20;
|
||||
root["tx_power"] = settings.tx_power;
|
||||
root["nosleep"] = settings.nosleep;
|
||||
root["enableMDNS"] = settings.enableMDNS;
|
||||
root["enableCORS"] = settings.enableCORS;
|
||||
root["CORSOrigin"] = settings.CORSOrigin;
|
||||
|
||||
// extended settings
|
||||
JsonUtils::writeIP(root, "local_ip", settings.localIP);
|
||||
JsonUtils::writeIP(root, "gateway_ip", settings.gatewayIP);
|
||||
JsonUtils::writeIP(root, "subnet_mask", settings.subnetMask);
|
||||
JsonUtils::writeIP(root, "dns_ip_1", settings.dnsIP1);
|
||||
JsonUtils::writeIP(root, "dns_ip_2", settings.dnsIP2);
|
||||
}
|
||||
|
||||
static StateUpdateResult update(JsonObject root, NetworkSettings & settings) {
|
||||
auto enableCORS = settings.enableCORS;
|
||||
auto CORSOrigin = settings.CORSOrigin;
|
||||
auto ssid = settings.ssid;
|
||||
settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
|
||||
settings.bssid = root["bssid"] | "";
|
||||
settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
|
||||
settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
|
||||
settings.staticIPConfig = root["static_ip_config"] | false;
|
||||
settings.enableIPv6 = root["enableIPv6"] | false;
|
||||
settings.bandwidth20 = root["bandwidth20"] | false;
|
||||
settings.tx_power = root["tx_power"] | 20;
|
||||
settings.nosleep = root["nosleep"] | false;
|
||||
settings.enableMDNS = root["enableMDNS"] | true;
|
||||
settings.enableCORS = root["enableCORS"] | false;
|
||||
settings.CORSOrigin = root["CORSOrigin"] | "*";
|
||||
|
||||
// extended settings
|
||||
JsonUtils::readIP(root, "local_ip", settings.localIP);
|
||||
JsonUtils::readIP(root, "gateway_ip", settings.gatewayIP);
|
||||
JsonUtils::readIP(root, "subnet_mask", settings.subnetMask);
|
||||
JsonUtils::readIP(root, "dns_ip_1", settings.dnsIP1);
|
||||
JsonUtils::readIP(root, "dns_ip_2", settings.dnsIP2);
|
||||
|
||||
// Swap around the dns servers if 2 is populated but 1 is not
|
||||
if (IPUtils::isNotSet(settings.dnsIP1) && IPUtils::isSet(settings.dnsIP2)) {
|
||||
settings.dnsIP1 = settings.dnsIP2;
|
||||
settings.dnsIP2 = INADDR_NONE;
|
||||
}
|
||||
|
||||
// Turning off static ip config if we don't meet the minimum requirements
|
||||
// of ipAddress, gateway and subnet. This may change to static ip only
|
||||
// as sensible defaults can be assumed for gateway and subnet
|
||||
if (settings.staticIPConfig && (IPUtils::isNotSet(settings.localIP) || IPUtils::isNotSet(settings.gatewayIP) || IPUtils::isNotSet(settings.subnetMask))) {
|
||||
settings.staticIPConfig = false;
|
||||
}
|
||||
if (enableCORS != settings.enableCORS || CORSOrigin != settings.CORSOrigin || (ssid != settings.ssid && settings.ssid == "")) {
|
||||
return StateUpdateResult::CHANGED_RESTART; // tell WebUI that a restart is needed
|
||||
}
|
||||
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
static void read(NetworkSettings & settings, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, NetworkSettings & settings);
|
||||
};
|
||||
|
||||
|
||||
class NetworkSettingsService : public StatefulService<NetworkSettings> {
|
||||
public:
|
||||
NetworkSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||
@@ -127,13 +99,16 @@ class NetworkSettingsService : public StatefulService<NetworkSettings> {
|
||||
private:
|
||||
HttpEndpoint<NetworkSettings> _httpEndpoint;
|
||||
FSPersistence<NetworkSettings> _fsPersistence;
|
||||
unsigned long _lastConnectionAttempt;
|
||||
|
||||
bool _stopping;
|
||||
void WiFiEvent(WiFiEvent_t event);
|
||||
unsigned long _lastConnectionAttempt;
|
||||
bool _stopping;
|
||||
|
||||
void reconfigureWiFiConnection();
|
||||
void manageSTA();
|
||||
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
void mDNS_start() const;
|
||||
const char * disconnectReason(uint8_t code);
|
||||
void reconfigureWiFiConnection();
|
||||
void manageSTA();
|
||||
void setWiFiPowerOnRSSI();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
#include <NetworkStatus.h>
|
||||
#include "NetworkStatus.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
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));
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { networkStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
bool ethernet_connected = emsesp::EMSESP::system_.ethernet_connected();
|
||||
wl_status_t wifi_status = WiFi.status();
|
||||
@@ -22,7 +20,7 @@ void NetworkStatus::networkStatus(AsyncWebServerRequest * request) {
|
||||
root["status"] = 10; // custom code #10 - ETHERNET_STATUS_CONNECTED
|
||||
root["hostname"] = ETH.getHostname();
|
||||
} else {
|
||||
root["status"] = (uint8_t)wifi_status;
|
||||
root["status"] = static_cast<uint8_t>(wifi_status);
|
||||
root["hostname"] = WiFi.getHostname();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
#ifndef NetworkStatus_h
|
||||
#define NetworkStatus_h
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
|
||||
#include <ETH.h>
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <IPAddress.h>
|
||||
#include <IPUtils.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#include "IPUtils.h"
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#define MAX_NETWORK_STATUS_SIZE 1024
|
||||
#define NETWORK_STATUS_SERVICE_PATH "/rest/networkStatus"
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
#include <OTASettingsService.h>
|
||||
#include "OTASettingsService.h"
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
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)
|
||||
, _fsPersistence(OTASettings::read, OTASettings::update, this, fs, OTA_SETTINGS_FILE)
|
||||
, _arduinoOTA(nullptr) {
|
||||
WiFi.onEvent(std::bind(&OTASettingsService::WiFiEvent, this, _1, _2));
|
||||
addUpdateHandler([&](const String & originId) { configureArduinoOTA(); }, false);
|
||||
WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { WiFiEvent(event); });
|
||||
addUpdateHandler([this] { configureArduinoOTA(); }, false);
|
||||
}
|
||||
|
||||
void OTASettingsService::begin() {
|
||||
@@ -32,11 +30,11 @@ void OTASettingsService::configureArduinoOTA() {
|
||||
|
||||
if (_state.enabled) {
|
||||
_arduinoOTA = new ArduinoOTAClass;
|
||||
_arduinoOTA->setPort(_state.port);
|
||||
_arduinoOTA->setPort(static_cast<uint16_t>(_state.port));
|
||||
_arduinoOTA->setPassword(_state.password.c_str());
|
||||
|
||||
_arduinoOTA->onStart([]() { emsesp::EMSESP::system_.upload_status(true); });
|
||||
_arduinoOTA->onEnd([]() { emsesp::EMSESP::system_.upload_status(false); });
|
||||
_arduinoOTA->onStart([] { emsesp::EMSESP::system_.upload_status(true); });
|
||||
_arduinoOTA->onEnd([] { emsesp::EMSESP::system_.upload_status(false); });
|
||||
|
||||
_arduinoOTA->onProgress([](unsigned int progress, unsigned int total) {
|
||||
// Serial.printf("Progress: %u%%\r\n", (progress / (total / 100)));
|
||||
@@ -64,7 +62,7 @@ void OTASettingsService::configureArduinoOTA() {
|
||||
}
|
||||
}
|
||||
|
||||
void OTASettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
void OTASettingsService::WiFiEvent(WiFiEvent_t event) {
|
||||
switch (event) {
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
@@ -74,3 +72,16 @@ void OTASettingsService::WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OTASettings::read(OTASettings & settings, JsonObject root) {
|
||||
root["enabled"] = settings.enabled;
|
||||
root["port"] = settings.port;
|
||||
root["password"] = settings.password;
|
||||
}
|
||||
|
||||
StateUpdateResult OTASettings::update(JsonObject root, OTASettings & settings) {
|
||||
settings.enabled = root["enabled"] | FACTORY_OTA_ENABLED;
|
||||
settings.port = root["port"] | FACTORY_OTA_PORT;
|
||||
settings.password = root["password"] | FACTORY_OTA_PASSWORD;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#ifndef OTASettingsService_h
|
||||
#define OTASettingsService_h
|
||||
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
#include "HttpEndpoint.h"
|
||||
#include "FSPersistence.h"
|
||||
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WiFiUdp.h>
|
||||
@@ -28,18 +28,8 @@ class OTASettings {
|
||||
int port;
|
||||
String password;
|
||||
|
||||
static void read(OTASettings & settings, JsonObject root) {
|
||||
root["enabled"] = settings.enabled;
|
||||
root["port"] = settings.port;
|
||||
root["password"] = settings.password;
|
||||
}
|
||||
|
||||
static StateUpdateResult update(JsonObject root, OTASettings & settings) {
|
||||
settings.enabled = root["enabled"] | FACTORY_OTA_ENABLED;
|
||||
settings.port = root["port"] | FACTORY_OTA_PORT;
|
||||
settings.password = root["password"] | FACTORY_OTA_PASSWORD;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
static void read(OTASettings & settings, JsonObject root);
|
||||
static StateUpdateResult update(JsonObject root, OTASettings & settings);
|
||||
};
|
||||
|
||||
class OTASettingsService : public StatefulService<OTASettings> {
|
||||
@@ -55,7 +45,7 @@ class OTASettingsService : public StatefulService<OTASettings> {
|
||||
ArduinoOTAClass * _arduinoOTA;
|
||||
|
||||
void configureArduinoOTA();
|
||||
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
void WiFiEvent(WiFiEvent_t event);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
#include <RestartService.h>
|
||||
#include "RestartService.h"
|
||||
|
||||
#include <esp_ota_ops.h>
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
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(RESTART_SERVICE_PATH,
|
||||
HTTP_POST,
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { restart(request); }, AuthenticationPredicates::IS_ADMIN));
|
||||
server->on(PARTITION_SERVICE_PATH,
|
||||
HTTP_POST,
|
||||
securityManager->wrapRequest(std::bind(&RestartService::partition, this, _1), AuthenticationPredicates::IS_ADMIN));
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { partition(request); }, AuthenticationPredicates::IS_ADMIN));
|
||||
}
|
||||
|
||||
void RestartService::restartNow() {
|
||||
WiFi.disconnect(true);
|
||||
delay(500);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
void RestartService::restart(AsyncWebServerRequest * request) {
|
||||
@@ -19,7 +26,7 @@ void RestartService::restart(AsyncWebServerRequest * request) {
|
||||
}
|
||||
|
||||
void RestartService::partition(AsyncWebServerRequest * request) {
|
||||
const esp_partition_t * factory_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
|
||||
const esp_partition_t * factory_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, nullptr);
|
||||
if (factory_partition) {
|
||||
esp_ota_set_boot_partition(factory_partition);
|
||||
emsesp::EMSESP::system_.store_nvs_values();
|
||||
@@ -27,7 +34,7 @@ void RestartService::partition(AsyncWebServerRequest * request) {
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
const esp_partition_t * ota_partition = esp_ota_get_next_update_partition(NULL);
|
||||
const esp_partition_t * ota_partition = esp_ota_get_next_update_partition(nullptr);
|
||||
if (!ota_partition) {
|
||||
request->send(400); // bad request
|
||||
return;
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#define RESTART_SERVICE_PATH "/rest/restart"
|
||||
#define PARTITION_SERVICE_PATH "/rest/partition"
|
||||
@@ -14,11 +14,7 @@ class RestartService {
|
||||
public:
|
||||
RestartService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
static void restartNow() {
|
||||
WiFi.disconnect(true);
|
||||
delay(500);
|
||||
ESP.restart();
|
||||
}
|
||||
static void restartNow();
|
||||
|
||||
private:
|
||||
void restart(AsyncWebServerRequest * request);
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
#ifndef SecurityManager_h
|
||||
#define SecurityManager_h
|
||||
|
||||
#include <Features.h>
|
||||
#include <ArduinoJsonJWT.h>
|
||||
#include "Features.h"
|
||||
#include "ArduinoJsonJWT.h"
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ESPUtils.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <list>
|
||||
|
||||
#ifndef FACTORY_JWT_SECRET
|
||||
#define FACTORY_JWT_SECRET ESPUtils::defaultDeviceValue()
|
||||
#endif
|
||||
|
||||
#define ACCESS_TOKEN_PARAMATER "access_token"
|
||||
|
||||
#define AUTHORIZATION_HEADER "Authorization"
|
||||
@@ -26,28 +22,27 @@ class User {
|
||||
|
||||
public:
|
||||
User(String username, String password, bool admin)
|
||||
: username(username)
|
||||
, password(password)
|
||||
: username(std::move(username))
|
||||
, password(std::move(password))
|
||||
, admin(admin) {
|
||||
}
|
||||
};
|
||||
|
||||
class Authentication {
|
||||
public:
|
||||
User * user;
|
||||
boolean authenticated;
|
||||
User * user = nullptr;
|
||||
boolean authenticated = false;
|
||||
|
||||
public:
|
||||
Authentication(User & user)
|
||||
explicit Authentication(const User & user)
|
||||
: user(new User(user))
|
||||
, authenticated(true) {
|
||||
}
|
||||
Authentication()
|
||||
: user(nullptr)
|
||||
, authenticated(false) {
|
||||
}
|
||||
|
||||
Authentication() = default;
|
||||
|
||||
~Authentication() {
|
||||
delete (user);
|
||||
delete user;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,13 +50,14 @@ typedef std::function<boolean(Authentication & authentication)> AuthenticationPr
|
||||
|
||||
class AuthenticationPredicates {
|
||||
public:
|
||||
static bool NONE_REQUIRED(Authentication & authentication) {
|
||||
static bool NONE_REQUIRED(const Authentication & authentication) {
|
||||
(void)authentication;
|
||||
return true;
|
||||
};
|
||||
static bool IS_AUTHENTICATED(Authentication & authentication) {
|
||||
static bool IS_AUTHENTICATED(const Authentication & authentication) {
|
||||
return authentication.authenticated;
|
||||
};
|
||||
static bool IS_ADMIN(Authentication & authentication) {
|
||||
static bool IS_ADMIN(const Authentication & authentication) {
|
||||
return authentication.authenticated && authentication.user->admin;
|
||||
};
|
||||
};
|
||||
@@ -76,7 +72,7 @@ class SecurityManager {
|
||||
/*
|
||||
* Generate a JWT for the user provided
|
||||
*/
|
||||
virtual String generateJWT(User * user) = 0;
|
||||
virtual String generateJWT(const User * user) = 0;
|
||||
|
||||
/*
|
||||
* Check the request header for the Authorization token
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
#include <SecuritySettingsService.h>
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
#include "SecuritySettingsService.h"
|
||||
|
||||
SecuritySettingsService::SecuritySettingsService(AsyncWebServer * server, FS * fs)
|
||||
: _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);
|
||||
addUpdateHandler([this] { configureJWTHandler(); }, false);
|
||||
server->on(GENERATE_TOKEN_PATH,
|
||||
HTTP_GET,
|
||||
wrapRequest(std::bind(&SecuritySettingsService::generateToken, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN));
|
||||
SecuritySettingsService::wrapRequest([this](AsyncWebServerRequest * request) { generateToken(request); }, AuthenticationPredicates::IS_ADMIN));
|
||||
}
|
||||
|
||||
void SecuritySettingsService::begin() {
|
||||
@@ -30,7 +28,7 @@ Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerReques
|
||||
String value = tokenParamater->value();
|
||||
return authenticateJWT(value);
|
||||
}
|
||||
return Authentication();
|
||||
return {};
|
||||
}
|
||||
|
||||
void SecuritySettingsService::configureJWTHandler() {
|
||||
@@ -43,37 +41,37 @@ Authentication SecuritySettingsService::authenticateJWT(String & jwt) {
|
||||
if (payloadDocument.is<JsonObject>()) {
|
||||
JsonObject parsedPayload = payloadDocument.as<JsonObject>();
|
||||
String username = parsedPayload["username"];
|
||||
for (User _user : _state.users) {
|
||||
for (const User & _user : _state.users) {
|
||||
if (_user.username == username && validatePayload(parsedPayload, &_user)) {
|
||||
return Authentication(_user);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Authentication();
|
||||
return {};
|
||||
}
|
||||
|
||||
Authentication SecuritySettingsService::authenticate(const String & username, const String & password) {
|
||||
for (User _user : _state.users) {
|
||||
for (const User & _user : _state.users) {
|
||||
if (_user.username == username && _user.password == password) {
|
||||
return Authentication(_user);
|
||||
}
|
||||
}
|
||||
return Authentication();
|
||||
return {};
|
||||
}
|
||||
|
||||
inline void populateJWTPayload(JsonObject payload, User * user) {
|
||||
inline void populateJWTPayload(JsonObject payload, const User * user) {
|
||||
payload["username"] = user->username;
|
||||
payload["admin"] = user->admin;
|
||||
}
|
||||
|
||||
boolean SecuritySettingsService::validatePayload(JsonObject parsedPayload, User * user) {
|
||||
boolean SecuritySettingsService::validatePayload(JsonObject parsedPayload, const User * user) {
|
||||
JsonDocument jsonDocument;
|
||||
JsonObject payload = jsonDocument.to<JsonObject>();
|
||||
populateJWTPayload(payload, user);
|
||||
return payload == parsedPayload;
|
||||
}
|
||||
|
||||
String SecuritySettingsService::generateJWT(User * user) {
|
||||
String SecuritySettingsService::generateJWT(const User * user) {
|
||||
JsonDocument jsonDocument;
|
||||
JsonObject payload = jsonDocument.to<JsonObject>();
|
||||
populateJWTPayload(payload, user);
|
||||
@@ -111,11 +109,11 @@ ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequest
|
||||
|
||||
void SecuritySettingsService::generateToken(AsyncWebServerRequest * request) {
|
||||
AsyncWebParameter * usernameParam = request->getParam("username");
|
||||
for (User _user : _state.users) {
|
||||
for (const User & _user : _state.users) {
|
||||
if (_user.username == usernameParam->value()) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
root["token"] = generateJWT(&_user);
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
root["token"] = generateJWT(&_user);
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#ifndef SecuritySettingsService_h
|
||||
#define SecuritySettingsService_h
|
||||
|
||||
#include <Features.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
#include "Features.h"
|
||||
#include "SecurityManager.h"
|
||||
#include "HttpEndpoint.h"
|
||||
#include "FSPersistence.h"
|
||||
|
||||
#ifndef FACTORY_ADMIN_USERNAME
|
||||
#define FACTORY_ADMIN_USERNAME "admin"
|
||||
@@ -32,7 +32,6 @@ class SecuritySettings {
|
||||
public:
|
||||
String jwtSecret;
|
||||
std::vector<User> users;
|
||||
// std::list<User> users;
|
||||
|
||||
static void read(SecuritySettings & settings, JsonObject root) {
|
||||
// secret
|
||||
@@ -40,7 +39,7 @@ class SecuritySettings {
|
||||
|
||||
// users
|
||||
JsonArray users = root["users"].to<JsonArray>();
|
||||
for (User user : settings.users) {
|
||||
for (const User & user : settings.users) {
|
||||
JsonObject userRoot = users.add<JsonObject>();
|
||||
userRoot["username"] = user.username;
|
||||
userRoot["password"] = user.password;
|
||||
@@ -56,29 +55,29 @@ class SecuritySettings {
|
||||
settings.users.clear();
|
||||
if (root["users"].is<JsonArray>()) {
|
||||
for (JsonVariant user : root["users"].as<JsonArray>()) {
|
||||
settings.users.push_back(User(user["username"], user["password"], user["admin"]));
|
||||
settings.users.emplace_back(user["username"], user["password"], user["admin"]);
|
||||
}
|
||||
} else {
|
||||
settings.users.push_back(User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true));
|
||||
settings.users.push_back(User(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false));
|
||||
settings.users.emplace_back(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
|
||||
settings.users.emplace_back(FACTORY_GUEST_USERNAME, FACTORY_GUEST_PASSWORD, false);
|
||||
}
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
};
|
||||
|
||||
class SecuritySettingsService : public StatefulService<SecuritySettings>, public SecurityManager {
|
||||
class SecuritySettingsService final : public StatefulService<SecuritySettings>, public SecurityManager {
|
||||
public:
|
||||
SecuritySettingsService(AsyncWebServer * server, FS * fs);
|
||||
|
||||
void begin();
|
||||
|
||||
// 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) override;
|
||||
Authentication authenticateRequest(AsyncWebServerRequest * request) override;
|
||||
String generateJWT(const User * user) override;
|
||||
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) override;
|
||||
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) override;
|
||||
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate) override;
|
||||
|
||||
private:
|
||||
HttpEndpoint<SecuritySettings> _httpEndpoint;
|
||||
@@ -97,7 +96,7 @@ class SecuritySettingsService : public StatefulService<SecuritySettings>, public
|
||||
/*
|
||||
* Verify the payload is correct
|
||||
*/
|
||||
boolean validatePayload(JsonObject parsedPayload, User * user);
|
||||
boolean validatePayload(JsonObject parsedPayload, const User * user);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,3 +1,3 @@
|
||||
#include <StatefulService.h>
|
||||
#include "StatefulService.h"
|
||||
|
||||
update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0;
|
||||
|
||||
@@ -23,8 +23,8 @@ using JsonStateUpdater = std::function<StateUpdateResult(JsonObject root, T & se
|
||||
template <typename T>
|
||||
using JsonStateReader = std::function<void(T & settings, JsonObject root)>;
|
||||
|
||||
typedef size_t update_handler_id_t;
|
||||
typedef std::function<void(const String & originId)> StateUpdateCallback;
|
||||
typedef size_t update_handler_id_t;
|
||||
typedef std::function<void()> StateUpdateCallback;
|
||||
|
||||
typedef struct StateUpdateHandlerInfo {
|
||||
static update_handler_id_t currentUpdatedHandlerId;
|
||||
@@ -33,7 +33,7 @@ typedef struct StateUpdateHandlerInfo {
|
||||
bool _allowRemove;
|
||||
StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove)
|
||||
: _id(++currentUpdatedHandlerId)
|
||||
, _cb(cb)
|
||||
, _cb(std::move(cb))
|
||||
, _allowRemove(allowRemove){};
|
||||
} StateUpdateHandlerInfo_t;
|
||||
|
||||
@@ -41,7 +41,7 @@ template <class T>
|
||||
class StatefulService {
|
||||
public:
|
||||
template <typename... Args>
|
||||
StatefulService(Args &&... args)
|
||||
explicit StatefulService(Args &&... args)
|
||||
: _state(std::forward<Args>(args)...)
|
||||
, _accessMutex(xSemaphoreCreateRecursiveMutex()) {
|
||||
}
|
||||
@@ -50,27 +50,28 @@ class StatefulService {
|
||||
if (!cb) {
|
||||
return 0;
|
||||
}
|
||||
StateUpdateHandlerInfo_t updateHandler(cb, allowRemove);
|
||||
_updateHandlers.push_back(updateHandler);
|
||||
StateUpdateHandlerInfo_t updateHandler(std::move(cb), allowRemove);
|
||||
_updateHandlers.push_back(std::move(updateHandler));
|
||||
return updateHandler._id;
|
||||
}
|
||||
|
||||
void removeUpdateHandler(update_handler_id_t id) {
|
||||
for (auto i = _updateHandlers.begin(); i != _updateHandlers.end();) {
|
||||
if ((*i)._allowRemove && (*i)._id == id) {
|
||||
i = _updateHandlers.erase(i);
|
||||
for (auto it = _updateHandlers.begin(); it != _updateHandlers.end();) {
|
||||
auto & elem = *it;
|
||||
if (elem._allowRemove && elem._id == id) {
|
||||
it = _updateHandlers.erase(it);
|
||||
} else {
|
||||
++i;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater, const String & originId) {
|
||||
StateUpdateResult update(std::function<StateUpdateResult(T &)> stateUpdater) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(_state);
|
||||
endTransaction();
|
||||
if (result == StateUpdateResult::CHANGED) {
|
||||
callUpdateHandlers(originId);
|
||||
callUpdateHandlers();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -82,12 +83,12 @@ class StatefulService {
|
||||
return result;
|
||||
}
|
||||
|
||||
StateUpdateResult update(JsonObject jsonObject, JsonStateUpdater<T> stateUpdater, const String & originId) {
|
||||
StateUpdateResult update(JsonObject jsonObject, JsonStateUpdater<T> stateUpdater) {
|
||||
beginTransaction();
|
||||
StateUpdateResult result = stateUpdater(jsonObject, _state);
|
||||
endTransaction();
|
||||
if (result == StateUpdateResult::CHANGED) {
|
||||
callUpdateHandlers(originId);
|
||||
callUpdateHandlers();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -111,9 +112,9 @@ class StatefulService {
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
void callUpdateHandlers(const String & originId) {
|
||||
void callUpdateHandlers() {
|
||||
for (const StateUpdateHandlerInfo_t & updateHandler : _updateHandlers) {
|
||||
updateHandler._cb(originId);
|
||||
updateHandler._cb();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,9 +130,8 @@ class StatefulService {
|
||||
}
|
||||
|
||||
private:
|
||||
SemaphoreHandle_t _accessMutex;
|
||||
SemaphoreHandle_t _accessMutex;
|
||||
std::vector<StateUpdateHandlerInfo_t> _updateHandlers;
|
||||
// std::list<StateUpdateHandlerInfo_t> _updateHandlers;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
#include <SystemStatus.h>
|
||||
#include "SystemStatus.h"
|
||||
|
||||
#include <esp_ota_ops.h>
|
||||
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
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));
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { systemStatus(request); }, AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
|
||||
emsesp::EMSESP::system_.refreshHeapMem(); // refresh free heap and max alloc heap
|
||||
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
root["emsesp_version"] = EMSESP_APP_VERSION;
|
||||
#ifdef EMSESP_DEBUG
|
||||
root["emsesp_version"] = std::string(EMSESP_APP_VERSION) + " (DEBUG)";
|
||||
#else
|
||||
#ifdef EMSESP_TEST
|
||||
root["emsesp_version"] = std::string(EMSESP_APP_VERSION) + " (TEST)";
|
||||
#else
|
||||
root["emsesp_version"] = EMSESP_APP_VERSION;
|
||||
#endif
|
||||
#endif
|
||||
root["esp_platform"] = EMSESP_PLATFORM;
|
||||
root["cpu_type"] = ESP.getChipModel();
|
||||
root["cpu_rev"] = ESP.getChipRevision();
|
||||
@@ -41,11 +48,11 @@ void SystemStatus::systemStatus(AsyncWebServerRequest * request) {
|
||||
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);
|
||||
const esp_partition_t * partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, nullptr);
|
||||
if (partition != NULL) { // factory partition found
|
||||
root["has_loader"] = true;
|
||||
} else { // check for not empty, smaller OTA partition
|
||||
partition = esp_ota_get_next_update_partition(NULL);
|
||||
partition = esp_ota_get_next_update_partition(nullptr);
|
||||
if (partition) {
|
||||
uint64_t buffer;
|
||||
esp_partition_read(partition, 0, &buffer, 8);
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#include <AsyncTCP.h>
|
||||
#include <FS.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#define SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"
|
||||
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
#include <UploadFileService.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_app_format.h>
|
||||
|
||||
#include "UploadFileService.h"
|
||||
#include "../../src/emsesp_stub.hpp"
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
#include <esp_app_format.h>
|
||||
|
||||
static bool is_firmware = false;
|
||||
static char md5[33] = "\0";
|
||||
static String getFilenameExtension(const String & filename) {
|
||||
const auto pos = filename.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
return filename.substring(static_cast<unsigned int>(pos) + 1);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
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));
|
||||
: _securityManager(securityManager)
|
||||
, _is_firmware(false)
|
||||
, _md5() {
|
||||
server->on(
|
||||
UPLOAD_FILE_PATH,
|
||||
HTTP_POST,
|
||||
[this](AsyncWebServerRequest * request) { uploadComplete(request); },
|
||||
[this](AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
|
||||
handleUpload(request, filename, index, data, len, final);
|
||||
});
|
||||
}
|
||||
|
||||
void UploadFileService::handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) {
|
||||
@@ -28,29 +35,27 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
|
||||
// at init
|
||||
if (!index) {
|
||||
// check details of the file, to see if its a valid bin or json file
|
||||
std::string fname(filename.c_str());
|
||||
auto position = fname.find_last_of(".");
|
||||
std::string extension = fname.substr(position + 1);
|
||||
size_t fsize = request->contentLength();
|
||||
const String extension = getFilenameExtension(filename);
|
||||
const std::size_t filesize = request->contentLength();
|
||||
|
||||
is_firmware = false;
|
||||
if ((extension == "bin") && (fsize > 1000000)) {
|
||||
is_firmware = true;
|
||||
_is_firmware = false;
|
||||
if ((extension == "bin") && (filesize > 1000000)) {
|
||||
_is_firmware = true;
|
||||
} else if (extension == "json") {
|
||||
md5[0] = '\0'; // clear md5
|
||||
_md5[0] = '\0'; // clear md5
|
||||
} else if (extension == "md5") {
|
||||
if (len == 32) {
|
||||
memcpy(md5, data, 32);
|
||||
md5[32] = '\0';
|
||||
if (len == _md5.size() - 1) {
|
||||
std::memcpy(_md5.data(), data, _md5.size() - 1);
|
||||
_md5.back() = '\0';
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
md5[0] = '\0';
|
||||
_md5.front() = '\0';
|
||||
handleError(request, 406); // Not Acceptable - unsupported file type
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_firmware) {
|
||||
if (_is_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 (len > 12 && (data[0] != 0xE9 || data[12] != 0)) {
|
||||
@@ -74,12 +79,12 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
|
||||
}
|
||||
#endif
|
||||
// it's firmware - initialize the ArduinoOTA updater
|
||||
if (Update.begin(fsize - sizeof(esp_image_header_t))) {
|
||||
if (strlen(md5) == 32) {
|
||||
Update.setMD5(md5);
|
||||
md5[0] = '\0';
|
||||
if (Update.begin(filesize - sizeof(esp_image_header_t))) {
|
||||
if (strlen(_md5.data()) == _md5.size() - 1) {
|
||||
Update.setMD5(_md5.data());
|
||||
_md5.front() = '\0';
|
||||
}
|
||||
request->onDisconnect(UploadFileService::handleEarlyDisconnect); // success, let's make sure we end the update if the client hangs up
|
||||
request->onDisconnect([this] { 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;
|
||||
@@ -90,23 +95,17 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_firmware) {
|
||||
if (len) {
|
||||
if (len != request->_tempFile.write(data, len)) { // stream the incoming chunk to the opened file
|
||||
handleError(request, 507); // 507-Insufficient Storage
|
||||
}
|
||||
if (!_is_firmware) {
|
||||
if (len && len != request->_tempFile.write(data, len)) { // stream the incoming chunk to the opened file
|
||||
handleError(request, 507); // 507-Insufficient Storage
|
||||
}
|
||||
} else {
|
||||
// if we haven't delt with an error, continue with the firmware update
|
||||
if (!request->_tempObject) {
|
||||
if (Update.write(data, len) != len) {
|
||||
handleError(request, 500);
|
||||
}
|
||||
if (final) {
|
||||
if (!Update.end(true)) {
|
||||
handleError(request, 500);
|
||||
}
|
||||
}
|
||||
} else if (!request->_tempObject) { // if we haven't delt with an error, continue with the firmware update
|
||||
if (Update.write(data, len) != len) {
|
||||
handleError(request, 500);
|
||||
return;
|
||||
}
|
||||
if (final && !Update.end(true)) {
|
||||
handleError(request, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +123,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
|
||||
|
||||
// check if it was a firmware upgrade
|
||||
// if no error, send the success response as a JSON
|
||||
if (is_firmware && !request->_tempObject) {
|
||||
if (_is_firmware && !request->_tempObject) {
|
||||
emsesp::EMSESP::system_.store_nvs_values();
|
||||
request->onDisconnect(RestartService::restartNow);
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
@@ -132,10 +131,10 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(md5) == 32) {
|
||||
if (strlen(_md5.data()) == _md5.size() - 1) {
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
root["md5"] = md5;
|
||||
root["md5"] = _md5.data();
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
@@ -163,6 +162,6 @@ void UploadFileService::handleError(AsyncWebServerRequest * request, int code) {
|
||||
}
|
||||
|
||||
void UploadFileService::handleEarlyDisconnect() {
|
||||
is_firmware = false;
|
||||
_is_firmware = false;
|
||||
Update.abort();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
#ifndef UploadFileService_h
|
||||
#define UploadFileService_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "RestartService.h"
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <LittleFS.h>
|
||||
#include <Update.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include <LittleFS.h>
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
#include <RestartService.h>
|
||||
#include <array>
|
||||
|
||||
#define UPLOAD_FILE_PATH "/rest/uploadFile"
|
||||
#define TEMP_FILENAME_PATH "/tmp_upload"
|
||||
@@ -20,11 +20,14 @@ class UploadFileService {
|
||||
UploadFileService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
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;
|
||||
bool _is_firmware;
|
||||
std::array<char, 33> _md5;
|
||||
|
||||
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);
|
||||
void handleEarlyDisconnect();
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,14 +1,12 @@
|
||||
#include <WiFiScanner.h>
|
||||
|
||||
using namespace std::placeholders; // for `_1` etc
|
||||
#include "WiFiScanner.h"
|
||||
|
||||
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));
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { scanNetworks(request); }, AuthenticationPredicates::IS_ADMIN));
|
||||
server->on(LIST_NETWORKS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, _1), AuthenticationPredicates::IS_ADMIN));
|
||||
securityManager->wrapRequest([this](AsyncWebServerRequest * request) { listNetworks(request); }, AuthenticationPredicates::IS_ADMIN));
|
||||
};
|
||||
|
||||
void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {
|
||||
@@ -21,18 +19,18 @@ void WiFiScanner::scanNetworks(AsyncWebServerRequest * request) {
|
||||
}
|
||||
|
||||
void WiFiScanner::listNetworks(AsyncWebServerRequest * request) {
|
||||
int numNetworks = WiFi.scanComplete();
|
||||
const int numNetworks = WiFi.scanComplete();
|
||||
if (numNetworks > -1) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
JsonArray networks = root["networks"].to<JsonArray>();
|
||||
for (int i = 0; i < numNetworks; i++) {
|
||||
auto * response = new AsyncJsonResponse(false);
|
||||
JsonObject root = response->getRoot();
|
||||
JsonArray networks = root["networks"].to<JsonArray>();
|
||||
for (uint8_t i = 0; i < numNetworks; i++) {
|
||||
JsonObject network = networks.add<JsonObject>();
|
||||
network["rssi"] = WiFi.RSSI(i);
|
||||
network["ssid"] = WiFi.SSID(i);
|
||||
network["bssid"] = WiFi.BSSIDstr(i);
|
||||
network["channel"] = WiFi.channel(i);
|
||||
network["encryption_type"] = (uint8_t)WiFi.encryptionType(i);
|
||||
network["encryption_type"] = static_cast<uint8_t>(WiFi.encryptionType(i));
|
||||
}
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <AsyncTCP.h>
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
#include "SecurityManager.h"
|
||||
|
||||
#define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks"
|
||||
#define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks"
|
||||
|
||||
Reference in New Issue
Block a user