Deleted .idea/.gitignore, .idea/clion.iml, .idea/misc.xml, .idea/modules.xml, .idea/platformio.iml, .idea/serialmonitor_settings.xml, .idea/vcs.xml, .idea/watcherTasks.xml files
This commit is contained in:
		
							
								
								
									
										83
									
								
								lib/framework/APSettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								lib/framework/APSettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
#include <APSettingsService.h>
 | 
			
		||||
 | 
			
		||||
APSettingsService::APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
 | 
			
		||||
    _httpEndpoint(APSettings::read, APSettings::update, this, server, AP_SETTINGS_SERVICE_PATH, securityManager),
 | 
			
		||||
    _fsPersistence(APSettings::read, APSettings::update, this, fs, AP_SETTINGS_FILE),
 | 
			
		||||
    _dnsServer(nullptr),
 | 
			
		||||
    _lastManaged(0),
 | 
			
		||||
    _reconfigureAp(false) {
 | 
			
		||||
  addUpdateHandler([&](const String& originId) { reconfigureAP(); }, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APSettingsService::begin() {
 | 
			
		||||
  _fsPersistence.readFromFS();
 | 
			
		||||
  reconfigureAP();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APSettingsService::reconfigureAP() {
 | 
			
		||||
  _lastManaged = millis() - MANAGE_NETWORK_DELAY;
 | 
			
		||||
  _reconfigureAp = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APSettingsService::loop() {
 | 
			
		||||
  unsigned long currentMillis = millis();
 | 
			
		||||
  unsigned long manageElapsed = (unsigned long)(currentMillis - _lastManaged);
 | 
			
		||||
  if (manageElapsed >= MANAGE_NETWORK_DELAY) {
 | 
			
		||||
    _lastManaged = currentMillis;
 | 
			
		||||
    manageAP();
 | 
			
		||||
  }
 | 
			
		||||
  handleDNS();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APSettingsService::manageAP() {
 | 
			
		||||
  WiFiMode_t currentWiFiMode = WiFi.getMode();
 | 
			
		||||
  if (_state.provisionMode == AP_MODE_ALWAYS ||
 | 
			
		||||
      (_state.provisionMode == AP_MODE_DISCONNECTED && WiFi.status() != WL_CONNECTED)) {
 | 
			
		||||
    if (_reconfigureAp || currentWiFiMode == WIFI_OFF || currentWiFiMode == WIFI_STA) {
 | 
			
		||||
      startAP();
 | 
			
		||||
    }
 | 
			
		||||
  } else if ((currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA) &&
 | 
			
		||||
             (_reconfigureAp || !WiFi.softAPgetStationNum())) {
 | 
			
		||||
    stopAP();
 | 
			
		||||
  }
 | 
			
		||||
  _reconfigureAp = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APSettingsService::startAP() {
 | 
			
		||||
  Serial.println(F("Starting software access point"));
 | 
			
		||||
  WiFi.softAPConfig(_state.localIP, _state.gatewayIP, _state.subnetMask);
 | 
			
		||||
  WiFi.softAP(_state.ssid.c_str(), _state.password.c_str());
 | 
			
		||||
  if (!_dnsServer) {
 | 
			
		||||
    IPAddress apIp = WiFi.softAPIP();
 | 
			
		||||
    Serial.print(F("Starting captive portal on "));
 | 
			
		||||
    Serial.println(apIp);
 | 
			
		||||
    _dnsServer = new DNSServer;
 | 
			
		||||
    _dnsServer->start(DNS_PORT, "*", apIp);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APSettingsService::stopAP() {
 | 
			
		||||
  if (_dnsServer) {
 | 
			
		||||
    Serial.println(F("Stopping captive portal"));
 | 
			
		||||
    _dnsServer->stop();
 | 
			
		||||
    delete _dnsServer;
 | 
			
		||||
    _dnsServer = nullptr;
 | 
			
		||||
  }
 | 
			
		||||
  Serial.println(F("Stopping software access point"));
 | 
			
		||||
  WiFi.softAPdisconnect(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APSettingsService::handleDNS() {
 | 
			
		||||
  if (_dnsServer) {
 | 
			
		||||
    _dnsServer->processNextRequest();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										123
									
								
								lib/framework/APSettingsService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								lib/framework/APSettingsService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
#ifndef APSettingsConfig_h
 | 
			
		||||
#define APSettingsConfig_h
 | 
			
		||||
 | 
			
		||||
#include <HttpEndpoint.h>
 | 
			
		||||
#include <FSPersistence.h>
 | 
			
		||||
#include <JsonUtils.h>
 | 
			
		||||
 | 
			
		||||
#include <DNSServer.h>
 | 
			
		||||
#include <IPAddress.h>
 | 
			
		||||
 | 
			
		||||
#define MANAGE_NETWORK_DELAY 10000
 | 
			
		||||
 | 
			
		||||
#define AP_MODE_ALWAYS 0
 | 
			
		||||
#define AP_MODE_DISCONNECTED 1
 | 
			
		||||
#define AP_MODE_NEVER 2
 | 
			
		||||
 | 
			
		||||
#define DNS_PORT 53
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_AP_PROVISION_MODE
 | 
			
		||||
#define FACTORY_AP_PROVISION_MODE AP_MODE_DISCONNECTED
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_AP_SSID
 | 
			
		||||
#define FACTORY_AP_SSID "ESP8266-React"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_AP_PASSWORD
 | 
			
		||||
#define FACTORY_AP_PASSWORD "esp-react"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_AP_LOCAL_IP
 | 
			
		||||
#define FACTORY_AP_LOCAL_IP "192.168.4.1"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_AP_GATEWAY_IP
 | 
			
		||||
#define FACTORY_AP_GATEWAY_IP "192.168.4.1"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_AP_SUBNET_MASK
 | 
			
		||||
#define FACTORY_AP_SUBNET_MASK "255.255.255.0"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define AP_SETTINGS_FILE "/config/apSettings.json"
 | 
			
		||||
#define AP_SETTINGS_SERVICE_PATH "/rest/apSettings"
 | 
			
		||||
 | 
			
		||||
enum APNetworkStatus { ACTIVE = 0, INACTIVE, LINGERING };
 | 
			
		||||
 | 
			
		||||
class APSettings {
 | 
			
		||||
 public:
 | 
			
		||||
  uint8_t provisionMode;
 | 
			
		||||
  String ssid;
 | 
			
		||||
  String password;
 | 
			
		||||
  IPAddress localIP;
 | 
			
		||||
  IPAddress gatewayIP;
 | 
			
		||||
  IPAddress subnetMask;
 | 
			
		||||
 | 
			
		||||
  bool operator==(const APSettings& settings) const {
 | 
			
		||||
    return provisionMode == settings.provisionMode && ssid == settings.ssid && password == settings.password &&
 | 
			
		||||
           localIP == settings.localIP && gatewayIP == settings.gatewayIP && subnetMask == settings.subnetMask;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static void read(APSettings& settings, JsonObject& root) {
 | 
			
		||||
    root["provision_mode"] = settings.provisionMode;
 | 
			
		||||
    root["ssid"] = settings.ssid;
 | 
			
		||||
    root["password"] = settings.password;
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class APSettingsService : public StatefulService<APSettings> {
 | 
			
		||||
 public:
 | 
			
		||||
  APSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
  void begin();
 | 
			
		||||
  void loop();
 | 
			
		||||
  APNetworkStatus getAPNetworkStatus();
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  HttpEndpoint<APSettings> _httpEndpoint;
 | 
			
		||||
  FSPersistence<APSettings> _fsPersistence;
 | 
			
		||||
 | 
			
		||||
  // for the captive portal
 | 
			
		||||
  DNSServer* _dnsServer;
 | 
			
		||||
 | 
			
		||||
  // for the mangement delay loop
 | 
			
		||||
  volatile unsigned long _lastManaged;
 | 
			
		||||
  volatile boolean _reconfigureAp;
 | 
			
		||||
 | 
			
		||||
  void reconfigureAP();
 | 
			
		||||
  void manageAP();
 | 
			
		||||
  void startAP();
 | 
			
		||||
  void stopAP();
 | 
			
		||||
  void handleDNS();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end APSettingsConfig_h
 | 
			
		||||
							
								
								
									
										22
									
								
								lib/framework/APStatus.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								lib/framework/APStatus.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
#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, std::placeholders::_1),
 | 
			
		||||
                                          AuthenticationPredicates::IS_AUTHENTICATED));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APStatus::apStatus(AsyncWebServerRequest* request) {
 | 
			
		||||
  AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_AP_STATUS_SIZE);
 | 
			
		||||
  JsonObject root = response->getRoot();
 | 
			
		||||
 | 
			
		||||
  root["status"] = _apSettingsService->getAPNetworkStatus();
 | 
			
		||||
  root["ip_address"] = WiFi.softAPIP().toString();
 | 
			
		||||
  root["mac_address"] = WiFi.softAPmacAddress();
 | 
			
		||||
  root["station_num"] = WiFi.softAPgetStationNum();
 | 
			
		||||
 | 
			
		||||
  response->setLength();
 | 
			
		||||
  request->send(response);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								lib/framework/APStatus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lib/framework/APStatus.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
#ifndef APStatus_h
 | 
			
		||||
#define APStatus_h
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
#include <AsyncJson.h>
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <IPAddress.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
#include <APSettingsService.h>
 | 
			
		||||
 | 
			
		||||
#define MAX_AP_STATUS_SIZE 1024
 | 
			
		||||
#define AP_STATUS_SERVICE_PATH "/rest/apStatus"
 | 
			
		||||
 | 
			
		||||
class APStatus {
 | 
			
		||||
 public:
 | 
			
		||||
  APStatus(AsyncWebServer* server, SecurityManager* securityManager, APSettingsService* apSettingsService);
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  APSettingsService* _apSettingsService;
 | 
			
		||||
  void apStatus(AsyncWebServerRequest* request);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end APStatus_h
 | 
			
		||||
							
								
								
									
										144
									
								
								lib/framework/ArduinoJsonJWT.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								lib/framework/ArduinoJsonJWT.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
#include "ArduinoJsonJWT.h"
 | 
			
		||||
 | 
			
		||||
ArduinoJsonJWT::ArduinoJsonJWT(String secret) : _secret(secret) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoJsonJWT::setSecret(String secret) {
 | 
			
		||||
  _secret = secret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String ArduinoJsonJWT::getSecret() {
 | 
			
		||||
  return _secret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * ESP32 uses mbedtls, ESP2866 uses bearssl.
 | 
			
		||||
 *
 | 
			
		||||
 * Both come with decent HMAC implmentations 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];
 | 
			
		||||
  {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
    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);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
    br_hmac_key_context keyCtx;
 | 
			
		||||
    br_hmac_key_init(&keyCtx, &br_sha256_vtable, _secret.c_str(), _secret.length());
 | 
			
		||||
    br_hmac_context hmacCtx;
 | 
			
		||||
    br_hmac_init(&hmacCtx, &keyCtx, 0);
 | 
			
		||||
    br_hmac_update(&hmacCtx, payload.c_str(), payload.length());
 | 
			
		||||
    br_hmac_out(&hmacCtx, hmacResult);
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
  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());
 | 
			
		||||
 | 
			
		||||
  // add the header to payload
 | 
			
		||||
  jwt = JWT_HEADER + '.' + jwt;
 | 
			
		||||
 | 
			
		||||
  // add signature
 | 
			
		||||
  jwt += '.' + sign(jwt);
 | 
			
		||||
 | 
			
		||||
  return jwt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument& jsonDocument) {
 | 
			
		||||
  // clear json document before we begin, jsonDocument wil be null on failure
 | 
			
		||||
  jsonDocument.clear();
 | 
			
		||||
 | 
			
		||||
  // must have the correct header and delimiter
 | 
			
		||||
  if (!jwt.startsWith(JWT_HEADER) || jwt.indexOf('.') != JWT_HEADER_SIZE) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // check there is a signature delimieter
 | 
			
		||||
  int signatureDelimiterIndex = jwt.lastIndexOf('.');
 | 
			
		||||
  if (signatureDelimiterIndex == JWT_HEADER_SIZE) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // check the signature is valid
 | 
			
		||||
  String signature = jwt.substring(signatureDelimiterIndex + 1);
 | 
			
		||||
  jwt = jwt.substring(0, signatureDelimiterIndex);
 | 
			
		||||
  if (sign(jwt) != signature) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // decode payload
 | 
			
		||||
  jwt = jwt.substring(JWT_HEADER_SIZE + 1);
 | 
			
		||||
  jwt = decode(jwt);
 | 
			
		||||
 | 
			
		||||
  // parse payload, clearing json document after failure
 | 
			
		||||
  DeserializationError error = deserializeJson(jsonDocument, jwt);
 | 
			
		||||
  if (error != DeserializationError::Ok || !jsonDocument.is<JsonObject>()) {
 | 
			
		||||
    jsonDocument.clear();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String ArduinoJsonJWT::encode(const char* cstr, int inputLen) {
 | 
			
		||||
  // prepare encoder
 | 
			
		||||
  base64_encodestate _state;
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  base64_init_encodestate(&_state);
 | 
			
		||||
  size_t encodedLength = base64_encode_expected_len(inputLen) + 1;
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  base64_init_encodestate_nonewlines(&_state);
 | 
			
		||||
  size_t encodedLength = base64_encode_expected_len_nonewlines(inputLen) + 1;
 | 
			
		||||
#endif
 | 
			
		||||
  // prepare buffer of correct length, returning an empty string on failure
 | 
			
		||||
  char* buffer = (char*)malloc(encodedLength * sizeof(char));
 | 
			
		||||
  if (buffer == nullptr) {
 | 
			
		||||
    return "";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // encode to buffer
 | 
			
		||||
  int len = base64_encode_block(cstr, inputLen, &buffer[0], &_state);
 | 
			
		||||
  len += base64_encode_blockend(&buffer[len], &_state);
 | 
			
		||||
  buffer[len] = 0;
 | 
			
		||||
 | 
			
		||||
  // convert to arduino string, freeing buffer
 | 
			
		||||
  String value = String(buffer);
 | 
			
		||||
  free(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);
 | 
			
		||||
  }
 | 
			
		||||
  value.replace('+', '-');
 | 
			
		||||
  value.replace('/', '_');
 | 
			
		||||
 | 
			
		||||
  // return as string
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String ArduinoJsonJWT::decode(String value) {
 | 
			
		||||
  // convert to standard base64
 | 
			
		||||
  value.replace('-', '+');
 | 
			
		||||
  value.replace('_', '/');
 | 
			
		||||
 | 
			
		||||
  // prepare buffer of correct length
 | 
			
		||||
  char buffer[base64_decode_expected_len(value.length()) + 1];
 | 
			
		||||
 | 
			
		||||
  // decode
 | 
			
		||||
  int len = base64_decode_chars(value.c_str(), value.length(), &buffer[0]);
 | 
			
		||||
  buffer[len] = 0;
 | 
			
		||||
 | 
			
		||||
  // return as string
 | 
			
		||||
  return String(buffer);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								lib/framework/ArduinoJsonJWT.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lib/framework/ArduinoJsonJWT.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
#ifndef ArduinoJsonJWT_H
 | 
			
		||||
#define ArduinoJsonJWT_H
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
#include <libb64/cdecode.h>
 | 
			
		||||
#include <libb64/cencode.h>
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <mbedtls/md.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <bearssl/bearssl_hmac.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
  void setSecret(String secret);
 | 
			
		||||
  String getSecret();
 | 
			
		||||
 | 
			
		||||
  String buildJWT(JsonObject& payload);
 | 
			
		||||
  void parseJWT(String jwt, JsonDocument& jsonDocument);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										48
									
								
								lib/framework/AuthenticationService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								lib/framework/AuthenticationService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
#include <AuthenticationService.h>
 | 
			
		||||
 | 
			
		||||
#if FT_ENABLED(FT_SECURITY)
 | 
			
		||||
 | 
			
		||||
AuthenticationService::AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager) :
 | 
			
		||||
    _securityManager(securityManager),
 | 
			
		||||
    _signInHandler(SIGN_IN_PATH,
 | 
			
		||||
                   std::bind(&AuthenticationService::signIn, this, std::placeholders::_1, std::placeholders::_2)) {
 | 
			
		||||
  server->on(VERIFY_AUTHORIZATION_PATH,
 | 
			
		||||
             HTTP_GET,
 | 
			
		||||
             std::bind(&AuthenticationService::verifyAuthorization, this, std::placeholders::_1));
 | 
			
		||||
  _signInHandler.setMethod(HTTP_POST);
 | 
			
		||||
  _signInHandler.setMaxContentLength(MAX_AUTHENTICATION_SIZE);
 | 
			
		||||
  server->addHandler(&_signInHandler);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Verifys that the request supplied a valid JWT.
 | 
			
		||||
 */
 | 
			
		||||
void AuthenticationService::verifyAuthorization(AsyncWebServerRequest* request) {
 | 
			
		||||
  Authentication authentication = _securityManager->authenticateRequest(request);
 | 
			
		||||
  request->send(authentication.authenticated ? 200 : 401);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Signs in a user if the username and password match. Provides a JWT to be used in the Authorization header in
 | 
			
		||||
 * subsequent requests.
 | 
			
		||||
 */
 | 
			
		||||
void AuthenticationService::signIn(AsyncWebServerRequest* request, JsonVariant& json) {
 | 
			
		||||
  if (json.is<JsonObject>()) {
 | 
			
		||||
    String username = json["username"];
 | 
			
		||||
    String password = json["password"];
 | 
			
		||||
    Authentication authentication = _securityManager->authenticate(username, password);
 | 
			
		||||
    if (authentication.authenticated) {
 | 
			
		||||
      User* user = authentication.user;
 | 
			
		||||
      AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_AUTHENTICATION_SIZE);
 | 
			
		||||
      JsonObject jsonObject = response->getRoot();
 | 
			
		||||
      jsonObject["access_token"] = _securityManager->generateJWT(user);
 | 
			
		||||
      response->setLength();
 | 
			
		||||
      request->send(response);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  AsyncWebServerResponse* response = request->beginResponse(401);
 | 
			
		||||
  request->send(response);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif // end FT_ENABLED(FT_SECURITY)
 | 
			
		||||
							
								
								
									
										30
									
								
								lib/framework/AuthenticationService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								lib/framework/AuthenticationService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
#ifndef AuthenticationService_H_
 | 
			
		||||
#define AuthenticationService_H_
 | 
			
		||||
 | 
			
		||||
#include <Features.h>
 | 
			
		||||
#include <AsyncJson.h>
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
 | 
			
		||||
#define VERIFY_AUTHORIZATION_PATH "/rest/verifyAuthorization"
 | 
			
		||||
#define SIGN_IN_PATH "/rest/signIn"
 | 
			
		||||
 | 
			
		||||
#define MAX_AUTHENTICATION_SIZE 256
 | 
			
		||||
 | 
			
		||||
#if FT_ENABLED(FT_SECURITY)
 | 
			
		||||
 | 
			
		||||
class AuthenticationService {
 | 
			
		||||
 public:
 | 
			
		||||
  AuthenticationService(AsyncWebServer* server, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  SecurityManager* _securityManager;
 | 
			
		||||
  AsyncCallbackJsonWebHandler _signInHandler;
 | 
			
		||||
 | 
			
		||||
  // endpoint functions
 | 
			
		||||
  void signIn(AsyncWebServerRequest* request, JsonVariant& json);
 | 
			
		||||
  void verifyAuthorization(AsyncWebServerRequest* request);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end FT_ENABLED(FT_SECURITY)
 | 
			
		||||
#endif  // end SecurityManager_h
 | 
			
		||||
							
								
								
									
										114
									
								
								lib/framework/ESP8266React.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								lib/framework/ESP8266React.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
#include <ESP8266React.h>
 | 
			
		||||
 | 
			
		||||
ESP8266React::ESP8266React(AsyncWebServer* server) :
 | 
			
		||||
    _featureService(server),
 | 
			
		||||
    _securitySettingsService(server, &ESPFS),
 | 
			
		||||
    _wifiSettingsService(server, &ESPFS, &_securitySettingsService),
 | 
			
		||||
    _wifiScanner(server, &_securitySettingsService),
 | 
			
		||||
    _wifiStatus(server, &_securitySettingsService),
 | 
			
		||||
    _apSettingsService(server, &ESPFS, &_securitySettingsService),
 | 
			
		||||
    _apStatus(server, &_securitySettingsService, &_apSettingsService),
 | 
			
		||||
#if FT_ENABLED(FT_NTP)
 | 
			
		||||
    _ntpSettingsService(server, &ESPFS, &_securitySettingsService),
 | 
			
		||||
    _ntpStatus(server, &_securitySettingsService),
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_OTA)
 | 
			
		||||
    _otaSettingsService(server, &ESPFS, &_securitySettingsService),
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_UPLOAD_FIRMWARE)
 | 
			
		||||
    _uploadFirmwareService(server, &_securitySettingsService),
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_MQTT)
 | 
			
		||||
    _mqttSettingsService(server, &ESPFS, &_securitySettingsService),
 | 
			
		||||
    _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService),
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_SECURITY)
 | 
			
		||||
    _authenticationService(server, &_securitySettingsService),
 | 
			
		||||
#endif
 | 
			
		||||
    _restartService(server, &_securitySettingsService),
 | 
			
		||||
    _factoryResetService(server, &ESPFS, &_securitySettingsService),
 | 
			
		||||
    _systemStatus(server, &_securitySettingsService) {
 | 
			
		||||
#ifdef PROGMEM_WWW
 | 
			
		||||
  // Serve static resources from PROGMEM
 | 
			
		||||
  WWWData::registerRoutes(
 | 
			
		||||
      [server, this](const String& uri, const String& contentType, const uint8_t* content, size_t len) {
 | 
			
		||||
        ArRequestHandlerFunction requestHandler = [contentType, content, len](AsyncWebServerRequest* request) {
 | 
			
		||||
          AsyncWebServerResponse* response = request->beginResponse_P(200, contentType, content, len);
 | 
			
		||||
          response->addHeader("Content-Encoding", "gzip");
 | 
			
		||||
          request->send(response);
 | 
			
		||||
        };
 | 
			
		||||
        server->on(uri.c_str(), HTTP_GET, requestHandler);
 | 
			
		||||
        // Serving non matching get requests with "/index.html"
 | 
			
		||||
        // OPTIONS get a straight up 200 response
 | 
			
		||||
        if (uri.equals("/index.html")) {
 | 
			
		||||
          server->onNotFound([requestHandler](AsyncWebServerRequest* request) {
 | 
			
		||||
            if (request->method() == HTTP_GET) {
 | 
			
		||||
              requestHandler(request);
 | 
			
		||||
            } else if (request->method() == HTTP_OPTIONS) {
 | 
			
		||||
              request->send(200);
 | 
			
		||||
            } else {
 | 
			
		||||
              request->send(404);
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
#else
 | 
			
		||||
  // Serve static resources from /www/
 | 
			
		||||
  server->serveStatic("/js/", ESPFS, "/www/js/");
 | 
			
		||||
  server->serveStatic("/css/", ESPFS, "/www/css/");
 | 
			
		||||
  server->serveStatic("/fonts/", ESPFS, "/www/fonts/");
 | 
			
		||||
  server->serveStatic("/app/", ESPFS, "/www/app/");
 | 
			
		||||
  server->serveStatic("/favicon.ico", ESPFS, "/www/favicon.ico");
 | 
			
		||||
  // Serving all other get requests with "/www/index.htm"
 | 
			
		||||
  // OPTIONS get a straight up 200 response
 | 
			
		||||
  server->onNotFound([](AsyncWebServerRequest* request) {
 | 
			
		||||
    if (request->method() == HTTP_GET) {
 | 
			
		||||
      request->send(ESPFS, "/www/index.html");
 | 
			
		||||
    } else if (request->method() == HTTP_OPTIONS) {
 | 
			
		||||
      request->send(200);
 | 
			
		||||
    } else {
 | 
			
		||||
      request->send(404);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Disable CORS if required
 | 
			
		||||
#if defined(ENABLE_CORS)
 | 
			
		||||
  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", CORS_ORIGIN);
 | 
			
		||||
  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization");
 | 
			
		||||
  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true");
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP8266React::begin() {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  ESPFS.begin(true);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  ESPFS.begin();
 | 
			
		||||
#endif
 | 
			
		||||
  _wifiSettingsService.begin();
 | 
			
		||||
  _apSettingsService.begin();
 | 
			
		||||
#if FT_ENABLED(FT_NTP)
 | 
			
		||||
  _ntpSettingsService.begin();
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_OTA)
 | 
			
		||||
  _otaSettingsService.begin();
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_MQTT)
 | 
			
		||||
  _mqttSettingsService.begin();
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_SECURITY)
 | 
			
		||||
  _securitySettingsService.begin();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP8266React::loop() {
 | 
			
		||||
  _wifiSettingsService.loop();
 | 
			
		||||
  _apSettingsService.loop();
 | 
			
		||||
#if FT_ENABLED(FT_OTA)
 | 
			
		||||
  _otaSettingsService.loop();
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_MQTT)
 | 
			
		||||
  _mqttSettingsService.loop();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								lib/framework/ESP8266React.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								lib/framework/ESP8266React.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
#ifndef ESP8266React_h
 | 
			
		||||
#define ESP8266React_h
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <FeaturesService.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 <UploadFirmwareService.h>
 | 
			
		||||
#include <RestartService.h>
 | 
			
		||||
#include <SecuritySettingsService.h>
 | 
			
		||||
#include <SystemStatus.h>
 | 
			
		||||
#include <WiFiScanner.h>
 | 
			
		||||
#include <WiFiSettingsService.h>
 | 
			
		||||
#include <WiFiStatus.h>
 | 
			
		||||
#include <ESPFS.h>
 | 
			
		||||
 | 
			
		||||
#ifdef PROGMEM_WWW
 | 
			
		||||
#include <WWWData.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
class ESP8266React {
 | 
			
		||||
 public:
 | 
			
		||||
  ESP8266React(AsyncWebServer* server);
 | 
			
		||||
 | 
			
		||||
  void begin();
 | 
			
		||||
  void loop();
 | 
			
		||||
 | 
			
		||||
  FS* getFS() {
 | 
			
		||||
    return &ESPFS;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SecurityManager* getSecurityManager() {
 | 
			
		||||
    return &_securitySettingsService;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#if FT_ENABLED(FT_SECURITY)
 | 
			
		||||
  StatefulService<SecuritySettings>* getSecuritySettingsService() {
 | 
			
		||||
    return &_securitySettingsService;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  StatefulService<WiFiSettings>* getWiFiSettingsService() {
 | 
			
		||||
    return &_wifiSettingsService;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  StatefulService<APSettings>* getAPSettingsService() {
 | 
			
		||||
    return &_apSettingsService;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#if FT_ENABLED(FT_NTP)
 | 
			
		||||
  StatefulService<NTPSettings>* getNTPSettingsService() {
 | 
			
		||||
    return &_ntpSettingsService;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if FT_ENABLED(FT_OTA)
 | 
			
		||||
  StatefulService<OTASettings>* getOTASettingsService() {
 | 
			
		||||
    return &_otaSettingsService;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if FT_ENABLED(FT_MQTT)
 | 
			
		||||
  StatefulService<MqttSettings>* getMqttSettingsService() {
 | 
			
		||||
    return &_mqttSettingsService;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AsyncMqttClient* getMqttClient() {
 | 
			
		||||
    return _mqttSettingsService.getMqttClient();
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void factoryReset() {
 | 
			
		||||
    _factoryResetService.factoryReset();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  FeaturesService _featureService;
 | 
			
		||||
  SecuritySettingsService _securitySettingsService;
 | 
			
		||||
  WiFiSettingsService _wifiSettingsService;
 | 
			
		||||
  WiFiScanner _wifiScanner;
 | 
			
		||||
  WiFiStatus _wifiStatus;
 | 
			
		||||
  APSettingsService _apSettingsService;
 | 
			
		||||
  APStatus _apStatus;
 | 
			
		||||
#if FT_ENABLED(FT_NTP)
 | 
			
		||||
  NTPSettingsService _ntpSettingsService;
 | 
			
		||||
  NTPStatus _ntpStatus;
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_OTA)
 | 
			
		||||
  OTASettingsService _otaSettingsService;
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_UPLOAD_FIRMWARE)
 | 
			
		||||
  UploadFirmwareService _uploadFirmwareService;
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_MQTT)
 | 
			
		||||
  MqttSettingsService _mqttSettingsService;
 | 
			
		||||
  MqttStatus _mqttStatus;
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_SECURITY)
 | 
			
		||||
  AuthenticationService _authenticationService;
 | 
			
		||||
#endif
 | 
			
		||||
  RestartService _restartService;
 | 
			
		||||
  FactoryResetService _factoryResetService;
 | 
			
		||||
  SystemStatus _systemStatus;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										7
									
								
								lib/framework/ESPFS.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								lib/framework/ESPFS.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <SPIFFS.h>
 | 
			
		||||
#define ESPFS SPIFFS
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <LittleFS.h>
 | 
			
		||||
#define ESPFS LittleFS
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										17
									
								
								lib/framework/ESPUtils.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								lib/framework/ESPUtils.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
#ifndef ESPUtils_h
 | 
			
		||||
#define ESPUtils_h
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
 | 
			
		||||
class ESPUtils {
 | 
			
		||||
 public:
 | 
			
		||||
  static String defaultDeviceValue(String prefix = "") {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
    return prefix + String((unsigned long)ESP.getEfuseMac(), HEX);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
    return prefix + String(ESP.getChipId(), HEX);
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end ESPUtils
 | 
			
		||||
							
								
								
									
										98
									
								
								lib/framework/FSPersistence.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								lib/framework/FSPersistence.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
#ifndef FSPersistence_h
 | 
			
		||||
#define FSPersistence_h
 | 
			
		||||
 | 
			
		||||
#include <StatefulService.h>
 | 
			
		||||
#include <FS.h>
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class FSPersistence {
 | 
			
		||||
 public:
 | 
			
		||||
  FSPersistence(JsonStateReader<T> stateReader,
 | 
			
		||||
                JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
                StatefulService<T>* statefulService,
 | 
			
		||||
                FS* fs,
 | 
			
		||||
                const char* filePath,
 | 
			
		||||
                size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      _stateReader(stateReader),
 | 
			
		||||
      _stateUpdater(stateUpdater),
 | 
			
		||||
      _statefulService(statefulService),
 | 
			
		||||
      _fs(fs),
 | 
			
		||||
      _filePath(filePath),
 | 
			
		||||
      _bufferSize(bufferSize),
 | 
			
		||||
      _updateHandlerId(0) {
 | 
			
		||||
    enableUpdateHandler();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void readFromFS() {
 | 
			
		||||
    File settingsFile = _fs->open(_filePath, "r");
 | 
			
		||||
 | 
			
		||||
    if (settingsFile) {
 | 
			
		||||
      DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
 | 
			
		||||
      DeserializationError error = deserializeJson(jsonDocument, settingsFile);
 | 
			
		||||
      if (error == DeserializationError::Ok && jsonDocument.is<JsonObject>()) {
 | 
			
		||||
        JsonObject jsonObject = jsonDocument.as<JsonObject>();
 | 
			
		||||
        _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
 | 
			
		||||
        settingsFile.close();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      settingsFile.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If we reach here we have not been successful in loading the config,
 | 
			
		||||
    // hard-coded emergency defaults are now applied.
 | 
			
		||||
    applyDefaults();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool writeToFS() {
 | 
			
		||||
    // create and populate a new json object
 | 
			
		||||
    DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
 | 
			
		||||
    JsonObject jsonObject = jsonDocument.to<JsonObject>();
 | 
			
		||||
    _statefulService->read(jsonObject, _stateReader);
 | 
			
		||||
 | 
			
		||||
    // serialize it to filesystem
 | 
			
		||||
    File settingsFile = _fs->open(_filePath, "w");
 | 
			
		||||
 | 
			
		||||
    // failed to open file, return false
 | 
			
		||||
    if (!settingsFile) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // serialize the data to the file
 | 
			
		||||
    serializeJson(jsonDocument, settingsFile);
 | 
			
		||||
    settingsFile.close();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void disableUpdateHandler() {
 | 
			
		||||
    if (_updateHandlerId) {
 | 
			
		||||
      _statefulService->removeUpdateHandler(_updateHandlerId);
 | 
			
		||||
      _updateHandlerId = 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void enableUpdateHandler() {
 | 
			
		||||
    if (!_updateHandlerId) {
 | 
			
		||||
      _updateHandlerId = _statefulService->addUpdateHandler([&](const String& originId) { writeToFS(); });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  JsonStateReader<T> _stateReader;
 | 
			
		||||
  JsonStateUpdater<T> _stateUpdater;
 | 
			
		||||
  StatefulService<T>* _statefulService;
 | 
			
		||||
  FS* _fs;
 | 
			
		||||
  const char* _filePath;
 | 
			
		||||
  size_t _bufferSize;
 | 
			
		||||
  update_handler_id_t _updateHandlerId;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  // We assume the updater supplies sensible defaults if an empty object
 | 
			
		||||
  // is supplied, this virtual function allows that to be changed.
 | 
			
		||||
  virtual void applyDefaults() {
 | 
			
		||||
    DynamicJsonDocument jsonDocument = DynamicJsonDocument(_bufferSize);
 | 
			
		||||
    JsonObject jsonObject = jsonDocument.as<JsonObject>();
 | 
			
		||||
    _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end FSPersistence
 | 
			
		||||
							
								
								
									
										37
									
								
								lib/framework/FactoryResetService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lib/framework/FactoryResetService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
#include <FactoryResetService.h>
 | 
			
		||||
 | 
			
		||||
using namespace std::placeholders;
 | 
			
		||||
 | 
			
		||||
FactoryResetService::FactoryResetService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) : fs(fs) {
 | 
			
		||||
  server->on(FACTORY_RESET_SERVICE_PATH,
 | 
			
		||||
             HTTP_POST,
 | 
			
		||||
             securityManager->wrapRequest(std::bind(&FactoryResetService::handleRequest, this, _1),
 | 
			
		||||
                                          AuthenticationPredicates::IS_ADMIN));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FactoryResetService::handleRequest(AsyncWebServerRequest* request) {
 | 
			
		||||
  request->onDisconnect(std::bind(&FactoryResetService::factoryReset, this));
 | 
			
		||||
  request->send(200);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Delete function assumes that all files are stored flat, within the config directory.
 | 
			
		||||
 */
 | 
			
		||||
void FactoryResetService::factoryReset() {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  File root = fs->open(FS_CONFIG_DIRECTORY);
 | 
			
		||||
  File file;
 | 
			
		||||
  while (file = root.openNextFile()) {
 | 
			
		||||
    fs->remove(file.name());
 | 
			
		||||
  }
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  Dir configDirectory = fs->openDir(FS_CONFIG_DIRECTORY);
 | 
			
		||||
  while (configDirectory.next()) {
 | 
			
		||||
    String path = FS_CONFIG_DIRECTORY;
 | 
			
		||||
    path.concat("/");
 | 
			
		||||
    path.concat(configDirectory.fileName());
 | 
			
		||||
    fs->remove(path);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  RestartService::restartNow();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								lib/framework/FactoryResetService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								lib/framework/FactoryResetService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
#ifndef FactoryResetService_h
 | 
			
		||||
#define FactoryResetService_h
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
#include <RestartService.h>
 | 
			
		||||
#include <FS.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:
 | 
			
		||||
  void handleRequest(AsyncWebServerRequest* request);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end FactoryResetService_h
 | 
			
		||||
							
								
								
									
										37
									
								
								lib/framework/Features.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								lib/framework/Features.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
#ifndef Features_h
 | 
			
		||||
#define Features_h
 | 
			
		||||
 | 
			
		||||
#define FT_ENABLED(feature) feature
 | 
			
		||||
 | 
			
		||||
// project feature off by default
 | 
			
		||||
#ifndef FT_PROJECT
 | 
			
		||||
#define FT_PROJECT 0
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// security feature on by default
 | 
			
		||||
#ifndef FT_SECURITY
 | 
			
		||||
#define FT_SECURITY 1
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// mqtt feature on by default
 | 
			
		||||
#ifndef FT_MQTT
 | 
			
		||||
#define FT_MQTT 1
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// ntp feature on by default
 | 
			
		||||
#ifndef FT_NTP
 | 
			
		||||
#define FT_NTP 1
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// mqtt feature on by default
 | 
			
		||||
#ifndef FT_OTA
 | 
			
		||||
#define FT_OTA 1
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// upload firmware feature off by default
 | 
			
		||||
#ifndef FT_UPLOAD_FIRMWARE
 | 
			
		||||
#define FT_UPLOAD_FIRMWARE 0
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										42
									
								
								lib/framework/FeaturesService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								lib/framework/FeaturesService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
#include <FeaturesService.h>
 | 
			
		||||
 | 
			
		||||
FeaturesService::FeaturesService(AsyncWebServer* server) {
 | 
			
		||||
  server->on(FEATURES_SERVICE_PATH, HTTP_GET, std::bind(&FeaturesService::features, this, std::placeholders::_1));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FeaturesService::features(AsyncWebServerRequest* request) {
 | 
			
		||||
  AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_FEATURES_SIZE);
 | 
			
		||||
  JsonObject root = response->getRoot();
 | 
			
		||||
#if FT_ENABLED(FT_PROJECT)
 | 
			
		||||
  root["project"] = true;
 | 
			
		||||
#else
 | 
			
		||||
  root["project"] = false;
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_SECURITY)
 | 
			
		||||
  root["security"] = true;
 | 
			
		||||
#else
 | 
			
		||||
  root["security"] = false;
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_MQTT)
 | 
			
		||||
  root["mqtt"] = true;
 | 
			
		||||
#else
 | 
			
		||||
  root["mqtt"] = false;
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_NTP)
 | 
			
		||||
  root["ntp"] = true;
 | 
			
		||||
#else
 | 
			
		||||
  root["ntp"] = false;
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_OTA)
 | 
			
		||||
  root["ota"] = true;
 | 
			
		||||
#else
 | 
			
		||||
  root["ota"] = false;
 | 
			
		||||
#endif
 | 
			
		||||
#if FT_ENABLED(FT_UPLOAD_FIRMWARE)
 | 
			
		||||
  root["upload_firmware"] = true;
 | 
			
		||||
#else
 | 
			
		||||
  root["upload_firmware"] = false;
 | 
			
		||||
#endif
 | 
			
		||||
  response->setLength();
 | 
			
		||||
  request->send(response);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								lib/framework/FeaturesService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								lib/framework/FeaturesService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
#ifndef FeaturesService_h
 | 
			
		||||
#define FeaturesService_h
 | 
			
		||||
 | 
			
		||||
#include <Features.h>
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
#include <AsyncJson.h>
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
 | 
			
		||||
#define MAX_FEATURES_SIZE 256
 | 
			
		||||
#define FEATURES_SERVICE_PATH "/rest/features"
 | 
			
		||||
 | 
			
		||||
class FeaturesService {
 | 
			
		||||
 public:
 | 
			
		||||
  FeaturesService(AsyncWebServer* server);
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  void features(AsyncWebServerRequest* request);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										165
									
								
								lib/framework/HttpEndpoint.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								lib/framework/HttpEndpoint.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,165 @@
 | 
			
		||||
#ifndef HttpEndpoint_h
 | 
			
		||||
#define HttpEndpoint_h
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
#include <AsyncJson.h>
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
#include <StatefulService.h>
 | 
			
		||||
 | 
			
		||||
#define HTTP_ENDPOINT_ORIGIN_ID "http"
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class HttpGetEndpoint {
 | 
			
		||||
 public:
 | 
			
		||||
  HttpGetEndpoint(JsonStateReader<T> stateReader,
 | 
			
		||||
                  StatefulService<T>* statefulService,
 | 
			
		||||
                  AsyncWebServer* server,
 | 
			
		||||
                  const String& servicePath,
 | 
			
		||||
                  SecurityManager* securityManager,
 | 
			
		||||
                  AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
 | 
			
		||||
                  size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      _stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) {
 | 
			
		||||
    server->on(servicePath.c_str(),
 | 
			
		||||
               HTTP_GET,
 | 
			
		||||
               securityManager->wrapRequest(std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1),
 | 
			
		||||
                                            authenticationPredicate));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  HttpGetEndpoint(JsonStateReader<T> stateReader,
 | 
			
		||||
                  StatefulService<T>* statefulService,
 | 
			
		||||
                  AsyncWebServer* server,
 | 
			
		||||
                  const String& servicePath,
 | 
			
		||||
                  size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      _stateReader(stateReader), _statefulService(statefulService), _bufferSize(bufferSize) {
 | 
			
		||||
    server->on(servicePath.c_str(), HTTP_GET, std::bind(&HttpGetEndpoint::fetchSettings, this, std::placeholders::_1));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  JsonStateReader<T> _stateReader;
 | 
			
		||||
  StatefulService<T>* _statefulService;
 | 
			
		||||
  size_t _bufferSize;
 | 
			
		||||
 | 
			
		||||
  void fetchSettings(AsyncWebServerRequest* request) {
 | 
			
		||||
    AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize);
 | 
			
		||||
    JsonObject jsonObject = response->getRoot().to<JsonObject>();
 | 
			
		||||
    _statefulService->read(jsonObject, _stateReader);
 | 
			
		||||
 | 
			
		||||
    response->setLength();
 | 
			
		||||
    request->send(response);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class HttpPostEndpoint {
 | 
			
		||||
 public:
 | 
			
		||||
  HttpPostEndpoint(JsonStateReader<T> stateReader,
 | 
			
		||||
                   JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
                   StatefulService<T>* statefulService,
 | 
			
		||||
                   AsyncWebServer* server,
 | 
			
		||||
                   const String& servicePath,
 | 
			
		||||
                   SecurityManager* securityManager,
 | 
			
		||||
                   AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
 | 
			
		||||
                   size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      _stateReader(stateReader),
 | 
			
		||||
      _stateUpdater(stateUpdater),
 | 
			
		||||
      _statefulService(statefulService),
 | 
			
		||||
      _updateHandler(
 | 
			
		||||
          servicePath,
 | 
			
		||||
          securityManager->wrapCallback(
 | 
			
		||||
              std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2),
 | 
			
		||||
              authenticationPredicate),
 | 
			
		||||
          bufferSize),
 | 
			
		||||
      _bufferSize(bufferSize) {
 | 
			
		||||
    _updateHandler.setMethod(HTTP_POST);
 | 
			
		||||
    server->addHandler(&_updateHandler);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  HttpPostEndpoint(JsonStateReader<T> stateReader,
 | 
			
		||||
                   JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
                   StatefulService<T>* statefulService,
 | 
			
		||||
                   AsyncWebServer* server,
 | 
			
		||||
                   const String& servicePath,
 | 
			
		||||
                   size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      _stateReader(stateReader),
 | 
			
		||||
      _stateUpdater(stateUpdater),
 | 
			
		||||
      _statefulService(statefulService),
 | 
			
		||||
      _updateHandler(servicePath,
 | 
			
		||||
                     std::bind(&HttpPostEndpoint::updateSettings, this, std::placeholders::_1, std::placeholders::_2),
 | 
			
		||||
                     bufferSize),
 | 
			
		||||
      _bufferSize(bufferSize) {
 | 
			
		||||
    _updateHandler.setMethod(HTTP_POST);
 | 
			
		||||
    server->addHandler(&_updateHandler);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  JsonStateReader<T> _stateReader;
 | 
			
		||||
  JsonStateUpdater<T> _stateUpdater;
 | 
			
		||||
  StatefulService<T>* _statefulService;
 | 
			
		||||
  AsyncCallbackJsonWebHandler _updateHandler;
 | 
			
		||||
  size_t _bufferSize;
 | 
			
		||||
 | 
			
		||||
  void updateSettings(AsyncWebServerRequest* request, JsonVariant& json) {
 | 
			
		||||
    if (!json.is<JsonObject>()) {
 | 
			
		||||
      request->send(400);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    JsonObject jsonObject = json.as<JsonObject>();
 | 
			
		||||
    StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
 | 
			
		||||
    if (outcome == StateUpdateResult::ERROR) {
 | 
			
		||||
      request->send(400);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (outcome == StateUpdateResult::CHANGED) {
 | 
			
		||||
      request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); });
 | 
			
		||||
    }
 | 
			
		||||
    AsyncJsonResponse* response = new AsyncJsonResponse(false, _bufferSize);
 | 
			
		||||
    jsonObject = response->getRoot().to<JsonObject>();
 | 
			
		||||
    _statefulService->read(jsonObject, _stateReader);
 | 
			
		||||
    response->setLength();
 | 
			
		||||
    request->send(response);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
 | 
			
		||||
 public:
 | 
			
		||||
  HttpEndpoint(JsonStateReader<T> stateReader,
 | 
			
		||||
               JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
               StatefulService<T>* statefulService,
 | 
			
		||||
               AsyncWebServer* server,
 | 
			
		||||
               const String& servicePath,
 | 
			
		||||
               SecurityManager* securityManager,
 | 
			
		||||
               AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
 | 
			
		||||
               size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      HttpGetEndpoint<T>(stateReader,
 | 
			
		||||
                         statefulService,
 | 
			
		||||
                         server,
 | 
			
		||||
                         servicePath,
 | 
			
		||||
                         securityManager,
 | 
			
		||||
                         authenticationPredicate,
 | 
			
		||||
                         bufferSize),
 | 
			
		||||
      HttpPostEndpoint<T>(stateReader,
 | 
			
		||||
                          stateUpdater,
 | 
			
		||||
                          statefulService,
 | 
			
		||||
                          server,
 | 
			
		||||
                          servicePath,
 | 
			
		||||
                          securityManager,
 | 
			
		||||
                          authenticationPredicate,
 | 
			
		||||
                          bufferSize) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  HttpEndpoint(JsonStateReader<T> stateReader,
 | 
			
		||||
               JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
               StatefulService<T>* statefulService,
 | 
			
		||||
               AsyncWebServer* server,
 | 
			
		||||
               const String& servicePath,
 | 
			
		||||
               size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, bufferSize),
 | 
			
		||||
      HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) {
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end HttpEndpoint
 | 
			
		||||
							
								
								
									
										29
									
								
								lib/framework/JsonUtils.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								lib/framework/JsonUtils.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
#ifndef JsonUtils_h
 | 
			
		||||
#define JsonUtils_h
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include <IPAddress.h>
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
 | 
			
		||||
class JsonUtils {
 | 
			
		||||
 public:
 | 
			
		||||
  static void readIP(JsonObject& root, const String& key, IPAddress& ip, const String& def) {
 | 
			
		||||
    IPAddress defaultIp = {};
 | 
			
		||||
    if (!defaultIp.fromString(def)) {
 | 
			
		||||
      defaultIp = INADDR_NONE;
 | 
			
		||||
    }
 | 
			
		||||
    readIP(root, key, ip, defaultIp);
 | 
			
		||||
  }
 | 
			
		||||
  static void readIP(JsonObject& root, const String& key, IPAddress& ip, const IPAddress& defaultIp = INADDR_NONE) {
 | 
			
		||||
    if (!root[key].is<String>() || !ip.fromString(root[key].as<String>())) {
 | 
			
		||||
      ip = defaultIp;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  static void writeIP(JsonObject& root, const String& key, const IPAddress& ip) {
 | 
			
		||||
    if (ip != INADDR_NONE) {
 | 
			
		||||
      root[key] = ip.toString();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end JsonUtils
 | 
			
		||||
							
								
								
									
										167
									
								
								lib/framework/MqttPubSub.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								lib/framework/MqttPubSub.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,167 @@
 | 
			
		||||
#ifndef MqttPubSub_h
 | 
			
		||||
#define MqttPubSub_h
 | 
			
		||||
 | 
			
		||||
#include <StatefulService.h>
 | 
			
		||||
#include <AsyncMqttClient.h>
 | 
			
		||||
 | 
			
		||||
#define MQTT_ORIGIN_ID "mqtt"
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class MqttConnector {
 | 
			
		||||
 protected:
 | 
			
		||||
  StatefulService<T>* _statefulService;
 | 
			
		||||
  AsyncMqttClient* _mqttClient;
 | 
			
		||||
  size_t _bufferSize;
 | 
			
		||||
 | 
			
		||||
  MqttConnector(StatefulService<T>* statefulService, AsyncMqttClient* mqttClient, size_t bufferSize) :
 | 
			
		||||
      _statefulService(statefulService), _mqttClient(mqttClient), _bufferSize(bufferSize) {
 | 
			
		||||
    _mqttClient->onConnect(std::bind(&MqttConnector::onConnect, this));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void onConnect() = 0;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  inline AsyncMqttClient* getMqttClient() const {
 | 
			
		||||
    return _mqttClient;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class MqttPub : virtual public MqttConnector<T> {
 | 
			
		||||
 public:
 | 
			
		||||
  MqttPub(JsonStateReader<T> stateReader,
 | 
			
		||||
          StatefulService<T>* statefulService,
 | 
			
		||||
          AsyncMqttClient* mqttClient,
 | 
			
		||||
          const String& pubTopic = "",
 | 
			
		||||
          size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      MqttConnector<T>(statefulService, mqttClient, bufferSize), _stateReader(stateReader), _pubTopic(pubTopic) {
 | 
			
		||||
    MqttConnector<T>::_statefulService->addUpdateHandler([&](const String& originId) { publish(); }, false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setPubTopic(const String& pubTopic) {
 | 
			
		||||
    _pubTopic = pubTopic;
 | 
			
		||||
    publish();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  virtual void onConnect() {
 | 
			
		||||
    publish();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  JsonStateReader<T> _stateReader;
 | 
			
		||||
  String _pubTopic;
 | 
			
		||||
 | 
			
		||||
  void publish() {
 | 
			
		||||
    if (_pubTopic.length() > 0 && MqttConnector<T>::_mqttClient->connected()) {
 | 
			
		||||
      // serialize to json doc
 | 
			
		||||
      DynamicJsonDocument json(MqttConnector<T>::_bufferSize);
 | 
			
		||||
      JsonObject jsonObject = json.to<JsonObject>();
 | 
			
		||||
      MqttConnector<T>::_statefulService->read(jsonObject, _stateReader);
 | 
			
		||||
 | 
			
		||||
      // serialize to string
 | 
			
		||||
      String payload;
 | 
			
		||||
      serializeJson(json, payload);
 | 
			
		||||
 | 
			
		||||
      // publish the payload
 | 
			
		||||
      MqttConnector<T>::_mqttClient->publish(_pubTopic.c_str(), 0, false, payload.c_str());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class MqttSub : virtual public MqttConnector<T> {
 | 
			
		||||
 public:
 | 
			
		||||
  MqttSub(JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
          StatefulService<T>* statefulService,
 | 
			
		||||
          AsyncMqttClient* mqttClient,
 | 
			
		||||
          const String& subTopic = "",
 | 
			
		||||
          size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      MqttConnector<T>(statefulService, mqttClient, bufferSize), _stateUpdater(stateUpdater), _subTopic(subTopic) {
 | 
			
		||||
    MqttConnector<T>::_mqttClient->onMessage(std::bind(&MqttSub::onMqttMessage,
 | 
			
		||||
                                                       this,
 | 
			
		||||
                                                       std::placeholders::_1,
 | 
			
		||||
                                                       std::placeholders::_2,
 | 
			
		||||
                                                       std::placeholders::_3,
 | 
			
		||||
                                                       std::placeholders::_4,
 | 
			
		||||
                                                       std::placeholders::_5,
 | 
			
		||||
                                                       std::placeholders::_6));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setSubTopic(const String& subTopic) {
 | 
			
		||||
    if (!_subTopic.equals(subTopic)) {
 | 
			
		||||
      // unsubscribe from the existing topic if one was set
 | 
			
		||||
      if (_subTopic.length() > 0) {
 | 
			
		||||
        MqttConnector<T>::_mqttClient->unsubscribe(_subTopic.c_str());
 | 
			
		||||
      }
 | 
			
		||||
      // set the new topic and re-configure the subscription
 | 
			
		||||
      _subTopic = subTopic;
 | 
			
		||||
      subscribe();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  virtual void onConnect() {
 | 
			
		||||
    subscribe();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  JsonStateUpdater<T> _stateUpdater;
 | 
			
		||||
  String _subTopic;
 | 
			
		||||
 | 
			
		||||
  void subscribe() {
 | 
			
		||||
    if (_subTopic.length() > 0) {
 | 
			
		||||
      MqttConnector<T>::_mqttClient->subscribe(_subTopic.c_str(), 2);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void onMqttMessage(char* topic,
 | 
			
		||||
                     char* payload,
 | 
			
		||||
                     AsyncMqttClientMessageProperties properties,
 | 
			
		||||
                     size_t len,
 | 
			
		||||
                     size_t index,
 | 
			
		||||
                     size_t total) {
 | 
			
		||||
    // we only care about the topic we are watching in this class
 | 
			
		||||
    if (strcmp(_subTopic.c_str(), topic)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // deserialize from string
 | 
			
		||||
    DynamicJsonDocument json(MqttConnector<T>::_bufferSize);
 | 
			
		||||
    DeserializationError error = deserializeJson(json, payload, len);
 | 
			
		||||
    if (!error && json.is<JsonObject>()) {
 | 
			
		||||
      JsonObject jsonObject = json.as<JsonObject>();
 | 
			
		||||
      MqttConnector<T>::_statefulService->update(jsonObject, _stateUpdater, MQTT_ORIGIN_ID);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class MqttPubSub : public MqttPub<T>, public MqttSub<T> {
 | 
			
		||||
 public:
 | 
			
		||||
  MqttPubSub(JsonStateReader<T> stateReader,
 | 
			
		||||
             JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
             StatefulService<T>* statefulService,
 | 
			
		||||
             AsyncMqttClient* mqttClient,
 | 
			
		||||
             const String& pubTopic = "",
 | 
			
		||||
             const String& subTopic = "",
 | 
			
		||||
             size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      MqttConnector<T>(statefulService, mqttClient, bufferSize),
 | 
			
		||||
      MqttPub<T>(stateReader, statefulService, mqttClient, pubTopic, bufferSize),
 | 
			
		||||
      MqttSub<T>(stateUpdater, statefulService, mqttClient, subTopic, bufferSize) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  void configureTopics(const String& pubTopic, const String& subTopic) {
 | 
			
		||||
    MqttSub<T>::setSubTopic(subTopic);
 | 
			
		||||
    MqttPub<T>::setPubTopic(pubTopic);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void onConnect() {
 | 
			
		||||
    MqttSub<T>::onConnect();
 | 
			
		||||
    MqttPub<T>::onConnect();
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end MqttPubSub
 | 
			
		||||
							
								
								
									
										161
									
								
								lib/framework/MqttSettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								lib/framework/MqttSettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
#include <MqttSettingsService.h>
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED),
 | 
			
		||||
    _mqttClient() {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  WiFi.onEvent(
 | 
			
		||||
      std::bind(&MqttSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
 | 
			
		||||
      WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
 | 
			
		||||
  WiFi.onEvent(std::bind(&MqttSettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2),
 | 
			
		||||
               WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  _onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(
 | 
			
		||||
      std::bind(&MqttSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
 | 
			
		||||
  _onStationModeGotIPHandler =
 | 
			
		||||
      WiFi.onStationModeGotIP(std::bind(&MqttSettingsService::onStationModeGotIP, this, std::placeholders::_1));
 | 
			
		||||
#endif
 | 
			
		||||
  _mqttClient.onConnect(std::bind(&MqttSettingsService::onMqttConnect, this, std::placeholders::_1));
 | 
			
		||||
  _mqttClient.onDisconnect(std::bind(&MqttSettingsService::onMqttDisconnect, this, std::placeholders::_1));
 | 
			
		||||
  addUpdateHandler([&](const String& originId) { onConfigUpdated(); }, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MqttSettingsService::~MqttSettingsService() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MqttSettingsService::begin() {
 | 
			
		||||
  _fsPersistence.readFromFS();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MqttSettingsService::loop() {
 | 
			
		||||
  if (_reconfigureMqtt || (_disconnectedAt && (unsigned long)(millis() - _disconnectedAt) >= MQTT_RECONNECTION_DELAY)) {
 | 
			
		||||
    // reconfigure MQTT client
 | 
			
		||||
    configureMqtt();
 | 
			
		||||
 | 
			
		||||
    // clear the reconnection flags
 | 
			
		||||
    _reconfigureMqtt = false;
 | 
			
		||||
    _disconnectedAt = 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MqttSettingsService::isEnabled() {
 | 
			
		||||
  return _state.enabled;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MqttSettingsService::isConnected() {
 | 
			
		||||
  return _mqttClient.connected();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const char* MqttSettingsService::getClientId() {
 | 
			
		||||
  return _mqttClient.getClientId();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AsyncMqttClientDisconnectReason MqttSettingsService::getDisconnectReason() {
 | 
			
		||||
  return _disconnectReason;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AsyncMqttClient* MqttSettingsService::getMqttClient() {
 | 
			
		||||
  return &_mqttClient;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MqttSettingsService::onMqttConnect(bool sessionPresent) {
 | 
			
		||||
  Serial.print(F("Connected to MQTT, "));
 | 
			
		||||
  if (sessionPresent) {
 | 
			
		||||
    Serial.println(F("with persistent session"));
 | 
			
		||||
  } else {
 | 
			
		||||
    Serial.println(F("without persistent session"));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MqttSettingsService::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
 | 
			
		||||
  Serial.print(F("Disconnected from MQTT reason: "));
 | 
			
		||||
  Serial.println((uint8_t)reason);
 | 
			
		||||
  _disconnectReason = reason;
 | 
			
		||||
  _disconnectedAt = millis();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MqttSettingsService::onConfigUpdated() {
 | 
			
		||||
  _reconfigureMqtt = true;
 | 
			
		||||
  _disconnectedAt = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
void MqttSettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
 | 
			
		||||
  if (_state.enabled) {
 | 
			
		||||
    Serial.println(F("WiFi connection dropped, starting MQTT client."));
 | 
			
		||||
    onConfigUpdated();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MqttSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
 | 
			
		||||
  if (_state.enabled) {
 | 
			
		||||
    Serial.println(F("WiFi connection dropped, stopping MQTT client."));
 | 
			
		||||
    onConfigUpdated();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
void MqttSettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
 | 
			
		||||
  if (_state.enabled) {
 | 
			
		||||
    Serial.println(F("WiFi connection dropped, starting MQTT client."));
 | 
			
		||||
    onConfigUpdated();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MqttSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
 | 
			
		||||
  if (_state.enabled) {
 | 
			
		||||
    Serial.println(F("WiFi connection dropped, stopping MQTT client."));
 | 
			
		||||
    onConfigUpdated();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void MqttSettingsService::configureMqtt() {
 | 
			
		||||
  // disconnect if currently connected
 | 
			
		||||
  _mqttClient.disconnect();
 | 
			
		||||
 | 
			
		||||
  // only connect if WiFi is connected and MQTT is enabled
 | 
			
		||||
  if (_state.enabled && WiFi.isConnected()) {
 | 
			
		||||
    Serial.println(F("Connecting to MQTT..."));
 | 
			
		||||
    _mqttClient.setServer(retainCstr(_state.host.c_str(), &_retainedHost), _state.port);
 | 
			
		||||
    if (_state.username.length() > 0) {
 | 
			
		||||
      _mqttClient.setCredentials(
 | 
			
		||||
          retainCstr(_state.username.c_str(), &_retainedUsername),
 | 
			
		||||
          retainCstr(_state.password.length() > 0 ? _state.password.c_str() : nullptr, &_retainedPassword));
 | 
			
		||||
    } else {
 | 
			
		||||
      _mqttClient.setCredentials(retainCstr(nullptr, &_retainedUsername), retainCstr(nullptr, &_retainedPassword));
 | 
			
		||||
    }
 | 
			
		||||
    _mqttClient.setClientId(retainCstr(_state.clientId.c_str(), &_retainedClientId));
 | 
			
		||||
    _mqttClient.setKeepAlive(_state.keepAlive);
 | 
			
		||||
    _mqttClient.setCleanSession(_state.cleanSession);
 | 
			
		||||
    _mqttClient.setMaxTopicLength(_state.maxTopicLength);
 | 
			
		||||
    _mqttClient.connect();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										156
									
								
								lib/framework/MqttSettingsService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								lib/framework/MqttSettingsService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
			
		||||
#ifndef MqttSettingsService_h
 | 
			
		||||
#define MqttSettingsService_h
 | 
			
		||||
 | 
			
		||||
#include <StatefulService.h>
 | 
			
		||||
#include <HttpEndpoint.h>
 | 
			
		||||
#include <FSPersistence.h>
 | 
			
		||||
#include <AsyncMqttClient.h>
 | 
			
		||||
#include <ESPUtils.h>
 | 
			
		||||
 | 
			
		||||
#define MQTT_RECONNECTION_DELAY 5000
 | 
			
		||||
 | 
			
		||||
#define MQTT_SETTINGS_FILE "/config/mqttSettings.json"
 | 
			
		||||
#define MQTT_SETTINGS_SERVICE_PATH "/rest/mqttSettings"
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_MQTT_ENABLED
 | 
			
		||||
#define FACTORY_MQTT_ENABLED false
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_MQTT_HOST
 | 
			
		||||
#define FACTORY_MQTT_HOST "test.mosquitto.org"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_MQTT_PORT
 | 
			
		||||
#define FACTORY_MQTT_PORT 1883
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_MQTT_USERNAME
 | 
			
		||||
#define FACTORY_MQTT_USERNAME ""
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_MQTT_PASSWORD
 | 
			
		||||
#define FACTORY_MQTT_PASSWORD ""
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_MQTT_CLIENT_ID
 | 
			
		||||
#define FACTORY_MQTT_CLIENT_ID generateClientId()
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_MQTT_KEEP_ALIVE
 | 
			
		||||
#define FACTORY_MQTT_KEEP_ALIVE 16
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_MQTT_CLEAN_SESSION
 | 
			
		||||
#define FACTORY_MQTT_CLEAN_SESSION true
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_MQTT_MAX_TOPIC_LENGTH
 | 
			
		||||
#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
static String generateClientId() {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  return ESPUtils::defaultDeviceValue("esp32-");
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  return ESPUtils::defaultDeviceValue("esp8266-");
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MqttSettings {
 | 
			
		||||
 public:
 | 
			
		||||
  // host and port - if enabled
 | 
			
		||||
  bool enabled;
 | 
			
		||||
  String host;
 | 
			
		||||
  uint16_t port;
 | 
			
		||||
 | 
			
		||||
  // username and password
 | 
			
		||||
  String username;
 | 
			
		||||
  String password;
 | 
			
		||||
 | 
			
		||||
  // client id settings
 | 
			
		||||
  String clientId;
 | 
			
		||||
 | 
			
		||||
  // connection settings
 | 
			
		||||
  uint16_t keepAlive;
 | 
			
		||||
  bool cleanSession;
 | 
			
		||||
  uint16_t maxTopicLength;
 | 
			
		||||
 | 
			
		||||
  static void read(MqttSettings& settings, JsonObject& root) {
 | 
			
		||||
    root["enabled"] = settings.enabled;
 | 
			
		||||
    root["host"] = settings.host;
 | 
			
		||||
    root["port"] = settings.port;
 | 
			
		||||
    root["username"] = settings.username;
 | 
			
		||||
    root["password"] = settings.password;
 | 
			
		||||
    root["client_id"] = settings.clientId;
 | 
			
		||||
    root["keep_alive"] = settings.keepAlive;
 | 
			
		||||
    root["clean_session"] = settings.cleanSession;
 | 
			
		||||
    root["max_topic_length"] = settings.maxTopicLength;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static StateUpdateResult update(JsonObject& root, MqttSettings& settings) {
 | 
			
		||||
    settings.enabled = root["enabled"] | FACTORY_MQTT_ENABLED;
 | 
			
		||||
    settings.host = root["host"] | FACTORY_MQTT_HOST;
 | 
			
		||||
    settings.port = root["port"] | FACTORY_MQTT_PORT;
 | 
			
		||||
    settings.username = root["username"] | FACTORY_MQTT_USERNAME;
 | 
			
		||||
    settings.password = root["password"] | FACTORY_MQTT_PASSWORD;
 | 
			
		||||
    settings.clientId = root["client_id"] | FACTORY_MQTT_CLIENT_ID;
 | 
			
		||||
    settings.keepAlive = root["keep_alive"] | FACTORY_MQTT_KEEP_ALIVE;
 | 
			
		||||
    settings.cleanSession = root["clean_session"] | FACTORY_MQTT_CLEAN_SESSION;
 | 
			
		||||
    settings.maxTopicLength = root["max_topic_length"] | FACTORY_MQTT_MAX_TOPIC_LENGTH;
 | 
			
		||||
    return StateUpdateResult::CHANGED;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MqttSettingsService : public StatefulService<MqttSettings> {
 | 
			
		||||
 public:
 | 
			
		||||
  MqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
 | 
			
		||||
  ~MqttSettingsService();
 | 
			
		||||
 | 
			
		||||
  void begin();
 | 
			
		||||
  void loop();
 | 
			
		||||
  bool isEnabled();
 | 
			
		||||
  bool isConnected();
 | 
			
		||||
  const char* getClientId();
 | 
			
		||||
  AsyncMqttClientDisconnectReason getDisconnectReason();
 | 
			
		||||
  AsyncMqttClient* getMqttClient();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void onConfigUpdated();
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  HttpEndpoint<MqttSettings> _httpEndpoint;
 | 
			
		||||
  FSPersistence<MqttSettings> _fsPersistence;
 | 
			
		||||
 | 
			
		||||
  // Pointers to hold retained copies of the mqtt client connection strings.
 | 
			
		||||
  // This is required as AsyncMqttClient holds refrences to the supplied connection strings.
 | 
			
		||||
  char* _retainedHost;
 | 
			
		||||
  char* _retainedClientId;
 | 
			
		||||
  char* _retainedUsername;
 | 
			
		||||
  char* _retainedPassword;
 | 
			
		||||
 | 
			
		||||
  // variable to help manage connection
 | 
			
		||||
  bool _reconfigureMqtt;
 | 
			
		||||
  unsigned long _disconnectedAt;
 | 
			
		||||
 | 
			
		||||
  // connection status
 | 
			
		||||
  AsyncMqttClientDisconnectReason _disconnectReason;
 | 
			
		||||
 | 
			
		||||
  // the MQTT client instance
 | 
			
		||||
  AsyncMqttClient _mqttClient;
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
			
		||||
  void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  WiFiEventHandler _onStationModeDisconnectedHandler;
 | 
			
		||||
  WiFiEventHandler _onStationModeGotIPHandler;
 | 
			
		||||
  void onStationModeGotIP(const WiFiEventStationModeGotIP& event);
 | 
			
		||||
  void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void onMqttConnect(bool sessionPresent);
 | 
			
		||||
  void onMqttDisconnect(AsyncMqttClientDisconnectReason reason);
 | 
			
		||||
  void configureMqtt();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end MqttSettingsService_h
 | 
			
		||||
							
								
								
									
										24
									
								
								lib/framework/MqttStatus.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/framework/MqttStatus.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
#include <MqttStatus.h>
 | 
			
		||||
 | 
			
		||||
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, std::placeholders::_1),
 | 
			
		||||
                                          AuthenticationPredicates::IS_AUTHENTICATED));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MqttStatus::mqttStatus(AsyncWebServerRequest* request) {
 | 
			
		||||
  AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_MQTT_STATUS_SIZE);
 | 
			
		||||
  JsonObject root = response->getRoot();
 | 
			
		||||
 | 
			
		||||
  root["enabled"] = _mqttSettingsService->isEnabled();
 | 
			
		||||
  root["connected"] = _mqttSettingsService->isConnected();
 | 
			
		||||
  root["client_id"] = _mqttSettingsService->getClientId();
 | 
			
		||||
  root["disconnect_reason"] = (uint8_t)_mqttSettingsService->getDisconnectReason();
 | 
			
		||||
 | 
			
		||||
  response->setLength();
 | 
			
		||||
  request->send(response);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								lib/framework/MqttStatus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lib/framework/MqttStatus.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
#ifndef MqttStatus_h
 | 
			
		||||
#define MqttStatus_h
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <MqttSettingsService.h>
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
#include <AsyncJson.h>
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
 | 
			
		||||
#define MAX_MQTT_STATUS_SIZE 1024
 | 
			
		||||
#define MQTT_STATUS_SERVICE_PATH "/rest/mqttStatus"
 | 
			
		||||
 | 
			
		||||
class MqttStatus {
 | 
			
		||||
 public:
 | 
			
		||||
  MqttStatus(AsyncWebServer* server, MqttSettingsService* mqttSettingsService, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  MqttSettingsService* _mqttSettingsService;
 | 
			
		||||
 | 
			
		||||
  void mqttStatus(AsyncWebServerRequest* request);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end MqttStatus_h
 | 
			
		||||
							
								
								
									
										90
									
								
								lib/framework/NTPSettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								lib/framework/NTPSettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
#include <NTPSettingsService.h>
 | 
			
		||||
 | 
			
		||||
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, std::placeholders::_1, std::placeholders::_2),
 | 
			
		||||
                     AuthenticationPredicates::IS_ADMIN)) {
 | 
			
		||||
  _timeHandler.setMethod(HTTP_POST);
 | 
			
		||||
  _timeHandler.setMaxContentLength(MAX_TIME_SIZE);
 | 
			
		||||
  server->addHandler(&_timeHandler);
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  WiFi.onEvent(
 | 
			
		||||
      std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
 | 
			
		||||
      WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
 | 
			
		||||
  WiFi.onEvent(std::bind(&NTPSettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2),
 | 
			
		||||
               WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  _onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(
 | 
			
		||||
      std::bind(&NTPSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
 | 
			
		||||
  _onStationModeGotIPHandler =
 | 
			
		||||
      WiFi.onStationModeGotIP(std::bind(&NTPSettingsService::onStationModeGotIP, this, std::placeholders::_1));
 | 
			
		||||
#endif
 | 
			
		||||
  addUpdateHandler([&](const String& originId) { configureNTP(); }, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NTPSettingsService::begin() {
 | 
			
		||||
  _fsPersistence.readFromFS();
 | 
			
		||||
  configureNTP();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
void NTPSettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
 | 
			
		||||
  Serial.println(F("Got IP address, starting NTP Synchronization"));
 | 
			
		||||
  configureNTP();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NTPSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
 | 
			
		||||
  Serial.println(F("WiFi connection dropped, stopping NTP."));
 | 
			
		||||
  configureNTP();
 | 
			
		||||
}
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
void NTPSettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
 | 
			
		||||
  Serial.println(F("Got IP address, starting NTP Synchronization"));
 | 
			
		||||
  configureNTP();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NTPSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
 | 
			
		||||
  Serial.println(F("WiFi connection dropped, stopping NTP."));
 | 
			
		||||
  configureNTP();
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void NTPSettingsService::configureNTP() {
 | 
			
		||||
  if (WiFi.isConnected() && _state.enabled) {
 | 
			
		||||
    Serial.println(F("Starting NTP..."));
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
    configTzTime(_state.tzFormat.c_str(), _state.server.c_str());
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
    configTime(_state.tzFormat.c_str(), _state.server.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  } else {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
    setenv("TZ", _state.tzFormat.c_str(), 1);
 | 
			
		||||
    tzset();
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
    setTZ(_state.tzFormat.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
    sntp_stop();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NTPSettingsService::configureTime(AsyncWebServerRequest* request, JsonVariant& json) {
 | 
			
		||||
  if (!sntp_enabled() && json.is<JsonObject>()) {
 | 
			
		||||
    String timeUtc = json["time_utc"];
 | 
			
		||||
    struct tm tm = {0};
 | 
			
		||||
    char* s = strptime(timeUtc.c_str(), "%Y-%m-%dT%H:%M:%SZ", &tm);
 | 
			
		||||
    if (s != nullptr) {
 | 
			
		||||
      time_t time = mktime(&tm);
 | 
			
		||||
      struct timeval now = {.tv_sec = time};
 | 
			
		||||
      settimeofday(&now, nullptr);
 | 
			
		||||
      AsyncWebServerResponse* response = request->beginResponse(200);
 | 
			
		||||
      request->send(response);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  AsyncWebServerResponse* response = request->beginResponse(400);
 | 
			
		||||
  request->send(response);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								lib/framework/NTPSettingsService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								lib/framework/NTPSettingsService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
#ifndef NTPSettingsService_h
 | 
			
		||||
#define NTPSettingsService_h
 | 
			
		||||
 | 
			
		||||
#include <HttpEndpoint.h>
 | 
			
		||||
#include <FSPersistence.h>
 | 
			
		||||
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <lwip/apps/sntp.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <sntp.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_NTP_ENABLED
 | 
			
		||||
#define FACTORY_NTP_ENABLED true
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_NTP_TIME_ZONE_LABEL
 | 
			
		||||
#define FACTORY_NTP_TIME_ZONE_LABEL "Europe/London"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_NTP_TIME_ZONE_FORMAT
 | 
			
		||||
#define FACTORY_NTP_TIME_ZONE_FORMAT "GMT0BST,M3.5.0/1,M10.5.0"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_NTP_SERVER
 | 
			
		||||
#define FACTORY_NTP_SERVER "time.google.com"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define NTP_SETTINGS_FILE "/config/ntpSettings.json"
 | 
			
		||||
#define NTP_SETTINGS_SERVICE_PATH "/rest/ntpSettings"
 | 
			
		||||
 | 
			
		||||
#define MAX_TIME_SIZE 256
 | 
			
		||||
#define TIME_PATH "/rest/time"
 | 
			
		||||
 | 
			
		||||
class NTPSettings {
 | 
			
		||||
 public:
 | 
			
		||||
  bool enabled;
 | 
			
		||||
  String tzLabel;
 | 
			
		||||
  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;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class NTPSettingsService : public StatefulService<NTPSettings> {
 | 
			
		||||
 public:
 | 
			
		||||
  NTPSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
  void begin();
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  HttpEndpoint<NTPSettings> _httpEndpoint;
 | 
			
		||||
  FSPersistence<NTPSettings> _fsPersistence;
 | 
			
		||||
  AsyncCallbackJsonWebHandler _timeHandler;
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
			
		||||
  void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  WiFiEventHandler _onStationModeDisconnectedHandler;
 | 
			
		||||
  WiFiEventHandler _onStationModeGotIPHandler;
 | 
			
		||||
 | 
			
		||||
  void onStationModeGotIP(const WiFiEventStationModeGotIP& event);
 | 
			
		||||
  void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
 | 
			
		||||
#endif
 | 
			
		||||
  void configureNTP();
 | 
			
		||||
  void configureTime(AsyncWebServerRequest* request, JsonVariant& json);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end NTPSettingsService_h
 | 
			
		||||
							
								
								
									
										40
									
								
								lib/framework/NTPStatus.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								lib/framework/NTPStatus.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
#include <NTPStatus.h>
 | 
			
		||||
 | 
			
		||||
NTPStatus::NTPStatus(AsyncWebServer* server, SecurityManager* securityManager) {
 | 
			
		||||
  server->on(NTP_STATUS_SERVICE_PATH,
 | 
			
		||||
             HTTP_GET,
 | 
			
		||||
             securityManager->wrapRequest(std::bind(&NTPStatus::ntpStatus, this, std::placeholders::_1),
 | 
			
		||||
                                          AuthenticationPredicates::IS_AUTHENTICATED));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String toISOString(tm* time, bool incOffset) {
 | 
			
		||||
  char time_string[25];
 | 
			
		||||
  strftime(time_string, 25, incOffset ? "%FT%T%z" : "%FT%TZ", time);
 | 
			
		||||
  return String(time_string);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NTPStatus::ntpStatus(AsyncWebServerRequest* request) {
 | 
			
		||||
  AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_NTP_STATUS_SIZE);
 | 
			
		||||
  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"] = sntp_enabled() ? 1 : 0;
 | 
			
		||||
 | 
			
		||||
  // the current time in UTC
 | 
			
		||||
  root["time_utc"] = toISOString(gmtime(&now), false);
 | 
			
		||||
 | 
			
		||||
  // local time as ISO String with TZ
 | 
			
		||||
  root["time_local"] = toISOString(localtime(&now), true);
 | 
			
		||||
 | 
			
		||||
  // the sntp server name
 | 
			
		||||
  root["server"] = sntp_getservername(0);
 | 
			
		||||
 | 
			
		||||
  // device uptime in seconds
 | 
			
		||||
  root["uptime"] = millis() / 1000;
 | 
			
		||||
 | 
			
		||||
  response->setLength();
 | 
			
		||||
  request->send(response);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								lib/framework/NTPStatus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lib/framework/NTPStatus.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
#ifndef NTPStatus_h
 | 
			
		||||
#define NTPStatus_h
 | 
			
		||||
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#include <lwip/apps/sntp.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#include <sntp.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
#include <AsyncJson.h>
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
 | 
			
		||||
#define MAX_NTP_STATUS_SIZE 1024
 | 
			
		||||
#define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus"
 | 
			
		||||
 | 
			
		||||
class NTPStatus {
 | 
			
		||||
 public:
 | 
			
		||||
  NTPStatus(AsyncWebServer* server, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  void ntpStatus(AsyncWebServerRequest* request);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end NTPStatus_h
 | 
			
		||||
							
								
								
									
										71
									
								
								lib/framework/OTASettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								lib/framework/OTASettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
#include <OTASettingsService.h>
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  WiFi.onEvent(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1, std::placeholders::_2),
 | 
			
		||||
               WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  _onStationModeGotIPHandler =
 | 
			
		||||
      WiFi.onStationModeGotIP(std::bind(&OTASettingsService::onStationModeGotIP, this, std::placeholders::_1));
 | 
			
		||||
#endif
 | 
			
		||||
  addUpdateHandler([&](const String& originId) { configureArduinoOTA(); }, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OTASettingsService::begin() {
 | 
			
		||||
  _fsPersistence.readFromFS();
 | 
			
		||||
  configureArduinoOTA();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OTASettingsService::loop() {
 | 
			
		||||
  if (_state.enabled && _arduinoOTA) {
 | 
			
		||||
    _arduinoOTA->handle();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OTASettingsService::configureArduinoOTA() {
 | 
			
		||||
  if (_arduinoOTA) {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
    _arduinoOTA->end();
 | 
			
		||||
#endif
 | 
			
		||||
    delete _arduinoOTA;
 | 
			
		||||
    _arduinoOTA = nullptr;
 | 
			
		||||
  }
 | 
			
		||||
  if (_state.enabled) {
 | 
			
		||||
    Serial.println(F("Starting OTA Update Service..."));
 | 
			
		||||
    _arduinoOTA = new ArduinoOTAClass;
 | 
			
		||||
    _arduinoOTA->setPort(_state.port);
 | 
			
		||||
    _arduinoOTA->setPassword(_state.password.c_str());
 | 
			
		||||
    _arduinoOTA->onStart([]() { Serial.println(F("Starting")); });
 | 
			
		||||
    _arduinoOTA->onEnd([]() { Serial.println(F("\r\nEnd")); });
 | 
			
		||||
    _arduinoOTA->onProgress([](unsigned int progress, unsigned int total) {
 | 
			
		||||
      Serial.printf_P(PSTR("Progress: %u%%\r\n"), (progress / (total / 100)));
 | 
			
		||||
    });
 | 
			
		||||
    _arduinoOTA->onError([](ota_error_t error) {
 | 
			
		||||
      Serial.printf("Error[%u]: ", error);
 | 
			
		||||
      if (error == OTA_AUTH_ERROR)
 | 
			
		||||
        Serial.println(F("Auth Failed"));
 | 
			
		||||
      else if (error == OTA_BEGIN_ERROR)
 | 
			
		||||
        Serial.println(F("Begin Failed"));
 | 
			
		||||
      else if (error == OTA_CONNECT_ERROR)
 | 
			
		||||
        Serial.println(F("Connect Failed"));
 | 
			
		||||
      else if (error == OTA_RECEIVE_ERROR)
 | 
			
		||||
        Serial.println(F("Receive Failed"));
 | 
			
		||||
      else if (error == OTA_END_ERROR)
 | 
			
		||||
        Serial.println(F("End Failed"));
 | 
			
		||||
    });
 | 
			
		||||
    _arduinoOTA->begin();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
void OTASettingsService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
 | 
			
		||||
  configureArduinoOTA();
 | 
			
		||||
}
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
void OTASettingsService::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
 | 
			
		||||
  configureArduinoOTA();
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										72
									
								
								lib/framework/OTASettingsService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								lib/framework/OTASettingsService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
#ifndef OTASettingsService_h
 | 
			
		||||
#define OTASettingsService_h
 | 
			
		||||
 | 
			
		||||
#include <HttpEndpoint.h>
 | 
			
		||||
#include <FSPersistence.h>
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <ESPmDNS.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266mDNS.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <ArduinoOTA.h>
 | 
			
		||||
#include <WiFiUdp.h>
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_OTA_PORT
 | 
			
		||||
#define FACTORY_OTA_PORT 8266
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_OTA_PASSWORD
 | 
			
		||||
#define FACTORY_OTA_PASSWORD "esp-react"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_OTA_ENABLED
 | 
			
		||||
#define FACTORY_OTA_ENABLED true
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define OTA_SETTINGS_FILE "/config/otaSettings.json"
 | 
			
		||||
#define OTA_SETTINGS_SERVICE_PATH "/rest/otaSettings"
 | 
			
		||||
 | 
			
		||||
class OTASettings {
 | 
			
		||||
 public:
 | 
			
		||||
  bool enabled;
 | 
			
		||||
  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;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class OTASettingsService : public StatefulService<OTASettings> {
 | 
			
		||||
 public:
 | 
			
		||||
  OTASettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
  void begin();
 | 
			
		||||
  void loop();
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  HttpEndpoint<OTASettings> _httpEndpoint;
 | 
			
		||||
  FSPersistence<OTASettings> _fsPersistence;
 | 
			
		||||
  ArduinoOTAClass* _arduinoOTA;
 | 
			
		||||
 | 
			
		||||
  void configureArduinoOTA();
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  WiFiEventHandler _onStationModeGotIPHandler;
 | 
			
		||||
  void onStationModeGotIP(const WiFiEventStationModeGotIP& event);
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end OTASettingsService_h
 | 
			
		||||
							
								
								
									
										13
									
								
								lib/framework/RestartService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lib/framework/RestartService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
#include <RestartService.h>
 | 
			
		||||
 | 
			
		||||
RestartService::RestartService(AsyncWebServer* server, SecurityManager* securityManager) {
 | 
			
		||||
  server->on(RESTART_SERVICE_PATH,
 | 
			
		||||
             HTTP_POST,
 | 
			
		||||
             securityManager->wrapRequest(std::bind(&RestartService::restart, this, std::placeholders::_1),
 | 
			
		||||
                                          AuthenticationPredicates::IS_ADMIN));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RestartService::restart(AsyncWebServerRequest* request) {
 | 
			
		||||
  request->onDisconnect(RestartService::restartNow);
 | 
			
		||||
  request->send(200);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								lib/framework/RestartService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lib/framework/RestartService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
#ifndef RestartService_h
 | 
			
		||||
#define RestartService_h
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
 | 
			
		||||
#define RESTART_SERVICE_PATH "/rest/restart"
 | 
			
		||||
 | 
			
		||||
class RestartService {
 | 
			
		||||
 public:
 | 
			
		||||
  RestartService(AsyncWebServer* server, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
  static void restartNow() {
 | 
			
		||||
    WiFi.disconnect(true);
 | 
			
		||||
    delay(500);
 | 
			
		||||
    ESP.restart();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  void restart(AsyncWebServerRequest* request);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end RestartService_h
 | 
			
		||||
							
								
								
									
										102
									
								
								lib/framework/SecurityManager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								lib/framework/SecurityManager.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
#ifndef SecurityManager_h
 | 
			
		||||
#define SecurityManager_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"
 | 
			
		||||
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
 | 
			
		||||
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
 | 
			
		||||
 | 
			
		||||
#define MAX_JWT_SIZE 128
 | 
			
		||||
 | 
			
		||||
class User {
 | 
			
		||||
 public:
 | 
			
		||||
  String username;
 | 
			
		||||
  String password;
 | 
			
		||||
  bool admin;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  User(String username, String password, bool admin) : username(username), password(password), admin(admin) {
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Authentication {
 | 
			
		||||
 public:
 | 
			
		||||
  User* user;
 | 
			
		||||
  boolean authenticated;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  Authentication(User& user) : user(new User(user)), authenticated(true) {
 | 
			
		||||
  }
 | 
			
		||||
  Authentication() : user(nullptr), authenticated(false) {
 | 
			
		||||
  }
 | 
			
		||||
  ~Authentication() {
 | 
			
		||||
    delete (user);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef std::function<boolean(Authentication& authentication)> AuthenticationPredicate;
 | 
			
		||||
 | 
			
		||||
class AuthenticationPredicates {
 | 
			
		||||
 public:
 | 
			
		||||
  static bool NONE_REQUIRED(Authentication& authentication) {
 | 
			
		||||
    return true;
 | 
			
		||||
  };
 | 
			
		||||
  static bool IS_AUTHENTICATED(Authentication& authentication) {
 | 
			
		||||
    return authentication.authenticated;
 | 
			
		||||
  };
 | 
			
		||||
  static bool IS_ADMIN(Authentication& authentication) {
 | 
			
		||||
    return authentication.authenticated && authentication.user->admin;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SecurityManager {
 | 
			
		||||
 public:
 | 
			
		||||
#if FT_ENABLED(FT_SECURITY)
 | 
			
		||||
  /*
 | 
			
		||||
   * Authenticate, returning the user if found
 | 
			
		||||
   */
 | 
			
		||||
  virtual Authentication authenticate(const String& username, const String& password) = 0;
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Generate a JWT for the user provided
 | 
			
		||||
   */
 | 
			
		||||
  virtual String generateJWT(User* user) = 0;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Check the request header for the Authorization token
 | 
			
		||||
   */
 | 
			
		||||
  virtual Authentication authenticateRequest(AsyncWebServerRequest* request) = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Filter a request with the provided predicate, only returning true if the predicate matches.
 | 
			
		||||
   */
 | 
			
		||||
  virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Wrap the provided request to provide validation against an AuthenticationPredicate.
 | 
			
		||||
   */
 | 
			
		||||
  virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest,
 | 
			
		||||
                                               AuthenticationPredicate predicate) = 0;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Wrap the provided json request callback to provide validation against an AuthenticationPredicate.
 | 
			
		||||
   */
 | 
			
		||||
  virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest,
 | 
			
		||||
                                                    AuthenticationPredicate predicate) = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end SecurityManager_h
 | 
			
		||||
							
								
								
									
										140
									
								
								lib/framework/SecuritySettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								lib/framework/SecuritySettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
#include <SecuritySettingsService.h>
 | 
			
		||||
 | 
			
		||||
#if FT_ENABLED(FT_SECURITY)
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SecuritySettingsService::begin() {
 | 
			
		||||
  _fsPersistence.readFromFS();
 | 
			
		||||
  configureJWTHandler();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) {
 | 
			
		||||
  AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
 | 
			
		||||
  if (authorizationHeader) {
 | 
			
		||||
    String value = authorizationHeader->value();
 | 
			
		||||
    if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)) {
 | 
			
		||||
      value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
 | 
			
		||||
      return authenticateJWT(value);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) {
 | 
			
		||||
    AsyncWebParameter* tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER);
 | 
			
		||||
    String value = tokenParamater->value();
 | 
			
		||||
    return authenticateJWT(value);
 | 
			
		||||
  }
 | 
			
		||||
  return Authentication();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SecuritySettingsService::configureJWTHandler() {
 | 
			
		||||
  _jwtHandler.setSecret(_state.jwtSecret);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Authentication SecuritySettingsService::authenticateJWT(String& jwt) {
 | 
			
		||||
  DynamicJsonDocument payloadDocument(MAX_JWT_SIZE);
 | 
			
		||||
  _jwtHandler.parseJWT(jwt, payloadDocument);
 | 
			
		||||
  if (payloadDocument.is<JsonObject>()) {
 | 
			
		||||
    JsonObject parsedPayload = payloadDocument.as<JsonObject>();
 | 
			
		||||
    String username = parsedPayload["username"];
 | 
			
		||||
    for (User _user : _state.users) {
 | 
			
		||||
      if (_user.username == username && validatePayload(parsedPayload, &_user)) {
 | 
			
		||||
        return Authentication(_user);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return Authentication();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Authentication SecuritySettingsService::authenticate(const String& username, const String& password) {
 | 
			
		||||
  for (User _user : _state.users) {
 | 
			
		||||
    if (_user.username == username && _user.password == password) {
 | 
			
		||||
      return Authentication(_user);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return Authentication();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void populateJWTPayload(JsonObject& payload, User* user) {
 | 
			
		||||
  payload["username"] = user->username;
 | 
			
		||||
  payload["admin"] = user->admin;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
boolean SecuritySettingsService::validatePayload(JsonObject& parsedPayload, User* user) {
 | 
			
		||||
  DynamicJsonDocument jsonDocument(MAX_JWT_SIZE);
 | 
			
		||||
  JsonObject payload = jsonDocument.to<JsonObject>();
 | 
			
		||||
  populateJWTPayload(payload, user);
 | 
			
		||||
  return payload == parsedPayload;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
String SecuritySettingsService::generateJWT(User* user) {
 | 
			
		||||
  DynamicJsonDocument jsonDocument(MAX_JWT_SIZE);
 | 
			
		||||
  JsonObject payload = jsonDocument.to<JsonObject>();
 | 
			
		||||
  populateJWTPayload(payload, user);
 | 
			
		||||
  return _jwtHandler.buildJWT(payload);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
 | 
			
		||||
  return [this, predicate](AsyncWebServerRequest* request) {
 | 
			
		||||
    Authentication authentication = authenticateRequest(request);
 | 
			
		||||
    return predicate(authentication);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest,
 | 
			
		||||
                                                              AuthenticationPredicate predicate) {
 | 
			
		||||
  return [this, onRequest, predicate](AsyncWebServerRequest* request) {
 | 
			
		||||
    Authentication authentication = authenticateRequest(request);
 | 
			
		||||
    if (!predicate(authentication)) {
 | 
			
		||||
      request->send(401);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    onRequest(request);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest,
 | 
			
		||||
                                                                   AuthenticationPredicate predicate) {
 | 
			
		||||
  return [this, onRequest, predicate](AsyncWebServerRequest* request, JsonVariant& json) {
 | 
			
		||||
    Authentication authentication = authenticateRequest(request);
 | 
			
		||||
    if (!predicate(authentication)) {
 | 
			
		||||
      request->send(401);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    onRequest(request, json);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
User ADMIN_USER = User(FACTORY_ADMIN_USERNAME, FACTORY_ADMIN_PASSWORD, true);
 | 
			
		||||
 | 
			
		||||
SecuritySettingsService::SecuritySettingsService(AsyncWebServer* server, FS* fs) : SecurityManager() {
 | 
			
		||||
}
 | 
			
		||||
SecuritySettingsService::~SecuritySettingsService() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
 | 
			
		||||
  return [this, predicate](AsyncWebServerRequest* request) { return true; };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return the admin user on all request - disabling security features
 | 
			
		||||
Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerRequest* request) {
 | 
			
		||||
  return Authentication(ADMIN_USER);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return the function unwrapped
 | 
			
		||||
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest,
 | 
			
		||||
                                                              AuthenticationPredicate predicate) {
 | 
			
		||||
  return onRequest;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ArJsonRequestHandlerFunction SecuritySettingsService::wrapCallback(ArJsonRequestHandlerFunction onRequest,
 | 
			
		||||
                                                                   AuthenticationPredicate predicate) {
 | 
			
		||||
  return onRequest;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										114
									
								
								lib/framework/SecuritySettingsService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								lib/framework/SecuritySettingsService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
#ifndef SecuritySettingsService_h
 | 
			
		||||
#define SecuritySettingsService_h
 | 
			
		||||
 | 
			
		||||
#include <Features.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
#include <HttpEndpoint.h>
 | 
			
		||||
#include <FSPersistence.h>
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_ADMIN_USERNAME
 | 
			
		||||
#define FACTORY_ADMIN_USERNAME "admin"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_ADMIN_PASSWORD
 | 
			
		||||
#define FACTORY_ADMIN_PASSWORD "admin"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_GUEST_USERNAME
 | 
			
		||||
#define FACTORY_GUEST_USERNAME "guest"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_GUEST_PASSWORD
 | 
			
		||||
#define FACTORY_GUEST_PASSWORD "guest"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define SECURITY_SETTINGS_FILE "/config/securitySettings.json"
 | 
			
		||||
#define SECURITY_SETTINGS_PATH "/rest/securitySettings"
 | 
			
		||||
 | 
			
		||||
#if FT_ENABLED(FT_SECURITY)
 | 
			
		||||
 | 
			
		||||
class SecuritySettings {
 | 
			
		||||
 public:
 | 
			
		||||
  String jwtSecret;
 | 
			
		||||
  std::list<User> users;
 | 
			
		||||
 | 
			
		||||
  static void read(SecuritySettings& settings, JsonObject& root) {
 | 
			
		||||
    // secret
 | 
			
		||||
    root["jwt_secret"] = settings.jwtSecret;
 | 
			
		||||
 | 
			
		||||
    // users
 | 
			
		||||
    JsonArray users = root.createNestedArray("users");
 | 
			
		||||
    for (User user : settings.users) {
 | 
			
		||||
      JsonObject userRoot = users.createNestedObject();
 | 
			
		||||
      userRoot["username"] = user.username;
 | 
			
		||||
      userRoot["password"] = user.password;
 | 
			
		||||
      userRoot["admin"] = user.admin;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static StateUpdateResult update(JsonObject& root, SecuritySettings& settings) {
 | 
			
		||||
    // secret
 | 
			
		||||
    settings.jwtSecret = root["jwt_secret"] | FACTORY_JWT_SECRET;
 | 
			
		||||
 | 
			
		||||
    // users
 | 
			
		||||
    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"]));
 | 
			
		||||
      }
 | 
			
		||||
    } 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));
 | 
			
		||||
    }
 | 
			
		||||
    return StateUpdateResult::CHANGED;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SecuritySettingsService : 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);
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  HttpEndpoint<SecuritySettings> _httpEndpoint;
 | 
			
		||||
  FSPersistence<SecuritySettings> _fsPersistence;
 | 
			
		||||
  ArduinoJsonJWT _jwtHandler;
 | 
			
		||||
 | 
			
		||||
  void configureJWTHandler();
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Lookup the user by JWT
 | 
			
		||||
   */
 | 
			
		||||
  Authentication authenticateJWT(String& jwt);
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Verify the payload is correct
 | 
			
		||||
   */
 | 
			
		||||
  boolean validatePayload(JsonObject& parsedPayload, User* user);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
class SecuritySettingsService : public SecurityManager {
 | 
			
		||||
 public:
 | 
			
		||||
  SecuritySettingsService(AsyncWebServer* server, FS* fs);
 | 
			
		||||
  ~SecuritySettingsService();
 | 
			
		||||
 | 
			
		||||
  // minimal set of functions to support framework with security settings disabled  
 | 
			
		||||
  Authentication authenticateRequest(AsyncWebServerRequest* request);
 | 
			
		||||
  ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
 | 
			
		||||
  ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
 | 
			
		||||
  ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end FT_ENABLED(FT_SECURITY)
 | 
			
		||||
#endif  // end SecuritySettingsService_h
 | 
			
		||||
							
								
								
									
										3
									
								
								lib/framework/StatefulService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								lib/framework/StatefulService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
#include <StatefulService.h>
 | 
			
		||||
 | 
			
		||||
update_handler_id_t StateUpdateHandlerInfo::currentUpdatedHandlerId = 0;
 | 
			
		||||
							
								
								
									
										148
									
								
								lib/framework/StatefulService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								lib/framework/StatefulService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,148 @@
 | 
			
		||||
#ifndef StatefulService_h
 | 
			
		||||
#define StatefulService_h
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <freertos/FreeRTOS.h>
 | 
			
		||||
#include <freertos/semphr.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef DEFAULT_BUFFER_SIZE
 | 
			
		||||
#define DEFAULT_BUFFER_SIZE 1024
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
enum class StateUpdateResult {
 | 
			
		||||
  CHANGED = 0,  // The update changed the state and propagation should take place if required
 | 
			
		||||
  UNCHANGED,    // The state was unchanged, propagation should not take place
 | 
			
		||||
  ERROR         // There was a problem updating the state, propagation should not take place
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
using JsonStateUpdater = std::function<StateUpdateResult(JsonObject& root, T& settings)>;
 | 
			
		||||
 | 
			
		||||
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 struct StateUpdateHandlerInfo {
 | 
			
		||||
  static update_handler_id_t currentUpdatedHandlerId;
 | 
			
		||||
  update_handler_id_t _id;
 | 
			
		||||
  StateUpdateCallback _cb;
 | 
			
		||||
  bool _allowRemove;
 | 
			
		||||
  StateUpdateHandlerInfo(StateUpdateCallback cb, bool allowRemove) :
 | 
			
		||||
      _id(++currentUpdatedHandlerId), _cb(cb), _allowRemove(allowRemove){};
 | 
			
		||||
} StateUpdateHandlerInfo_t;
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class StatefulService {
 | 
			
		||||
 public:
 | 
			
		||||
  template <typename... Args>
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  StatefulService(Args&&... args) :
 | 
			
		||||
      _state(std::forward<Args>(args)...), _accessMutex(xSemaphoreCreateRecursiveMutex()) {
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  StatefulService(Args&&... args) : _state(std::forward<Args>(args)...) {
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  update_handler_id_t addUpdateHandler(StateUpdateCallback cb, bool allowRemove = true) {
 | 
			
		||||
    if (!cb) {
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
    StateUpdateHandlerInfo_t updateHandler(cb, allowRemove);
 | 
			
		||||
    _updateHandlers.push_back(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);
 | 
			
		||||
      } else {
 | 
			
		||||
        ++i;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  StateUpdateResult update(std::function<StateUpdateResult(T&)> stateUpdater, const String& originId) {
 | 
			
		||||
    beginTransaction();
 | 
			
		||||
    StateUpdateResult result = stateUpdater(_state);
 | 
			
		||||
    endTransaction();
 | 
			
		||||
    if (result == StateUpdateResult::CHANGED) {
 | 
			
		||||
      callUpdateHandlers(originId);
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  StateUpdateResult updateWithoutPropagation(std::function<StateUpdateResult(T&)> stateUpdater) {
 | 
			
		||||
    beginTransaction();
 | 
			
		||||
    StateUpdateResult result = stateUpdater(_state);
 | 
			
		||||
    endTransaction();
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  StateUpdateResult update(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater, const String& originId) {
 | 
			
		||||
    beginTransaction();
 | 
			
		||||
    StateUpdateResult result = stateUpdater(jsonObject, _state);
 | 
			
		||||
    endTransaction();
 | 
			
		||||
    if (result == StateUpdateResult::CHANGED) {
 | 
			
		||||
      callUpdateHandlers(originId);
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  StateUpdateResult updateWithoutPropagation(JsonObject& jsonObject, JsonStateUpdater<T> stateUpdater) {
 | 
			
		||||
    beginTransaction();
 | 
			
		||||
    StateUpdateResult result = stateUpdater(jsonObject, _state);
 | 
			
		||||
    endTransaction();
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void read(std::function<void(T&)> stateReader) {
 | 
			
		||||
    beginTransaction();
 | 
			
		||||
    stateReader(_state);
 | 
			
		||||
    endTransaction();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void read(JsonObject& jsonObject, JsonStateReader<T> stateReader) {
 | 
			
		||||
    beginTransaction();
 | 
			
		||||
    stateReader(_state, jsonObject);
 | 
			
		||||
    endTransaction();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void callUpdateHandlers(const String& originId) {
 | 
			
		||||
    for (const StateUpdateHandlerInfo_t& updateHandler : _updateHandlers) {
 | 
			
		||||
      updateHandler._cb(originId);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  T _state;
 | 
			
		||||
 | 
			
		||||
  inline void beginTransaction() {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
    xSemaphoreTakeRecursive(_accessMutex, portMAX_DELAY);
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  inline void endTransaction() {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
    xSemaphoreGiveRecursive(_accessMutex);
 | 
			
		||||
#endif
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  SemaphoreHandle_t _accessMutex;
 | 
			
		||||
#endif
 | 
			
		||||
  std::list<StateUpdateHandlerInfo_t> _updateHandlers;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end StatefulService_h
 | 
			
		||||
							
								
								
									
										45
									
								
								lib/framework/SystemStatus.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/framework/SystemStatus.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
#include <SystemStatus.h>
 | 
			
		||||
 | 
			
		||||
SystemStatus::SystemStatus(AsyncWebServer* server, SecurityManager* securityManager) {
 | 
			
		||||
  server->on(SYSTEM_STATUS_SERVICE_PATH,
 | 
			
		||||
             HTTP_GET,
 | 
			
		||||
             securityManager->wrapRequest(std::bind(&SystemStatus::systemStatus, this, std::placeholders::_1),
 | 
			
		||||
                                          AuthenticationPredicates::IS_AUTHENTICATED));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SystemStatus::systemStatus(AsyncWebServerRequest* request) {
 | 
			
		||||
  AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE);
 | 
			
		||||
  JsonObject root = response->getRoot();
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  root["esp_platform"] = "esp32";
 | 
			
		||||
  root["max_alloc_heap"] = ESP.getMaxAllocHeap();
 | 
			
		||||
  root["psram_size"] = ESP.getPsramSize();
 | 
			
		||||
  root["free_psram"] = ESP.getFreePsram();  
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  root["esp_platform"] = "esp8266";
 | 
			
		||||
  root["max_alloc_heap"] = ESP.getMaxFreeBlockSize();
 | 
			
		||||
  root["heap_fragmentation"] = ESP.getHeapFragmentation();
 | 
			
		||||
#endif
 | 
			
		||||
  root["cpu_freq_mhz"] = ESP.getCpuFreqMHz();
 | 
			
		||||
  root["free_heap"] = ESP.getFreeHeap();
 | 
			
		||||
  root["sketch_size"] = ESP.getSketchSize();
 | 
			
		||||
  root["free_sketch_space"] = ESP.getFreeSketchSpace();
 | 
			
		||||
  root["sdk_version"] = ESP.getSdkVersion();
 | 
			
		||||
  root["flash_chip_size"] = ESP.getFlashChipSize();
 | 
			
		||||
  root["flash_chip_speed"] = ESP.getFlashChipSpeed();
 | 
			
		||||
 | 
			
		||||
// TODO - Ideally this class will take an *FS and extract the file system information from there.
 | 
			
		||||
// ESP8266 and ESP32 do not have feature parity in FS.h which currently makes that difficult.
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  root["fs_total"] = ESPFS.totalBytes();
 | 
			
		||||
  root["fs_used"] = ESPFS.usedBytes();
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  FSInfo fs_info;
 | 
			
		||||
  ESPFS.info(fs_info);
 | 
			
		||||
  root["fs_total"] = fs_info.totalBytes;
 | 
			
		||||
  root["fs_used"] = fs_info.usedBytes;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  response->setLength();
 | 
			
		||||
  request->send(response);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								lib/framework/SystemStatus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								lib/framework/SystemStatus.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
#ifndef SystemStatus_h
 | 
			
		||||
#define SystemStatus_h
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
#include <AsyncJson.h>
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
#include <ESPFS.h>
 | 
			
		||||
 | 
			
		||||
#define MAX_ESP_STATUS_SIZE 1024
 | 
			
		||||
#define SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"
 | 
			
		||||
 | 
			
		||||
class SystemStatus {
 | 
			
		||||
 public:
 | 
			
		||||
  SystemStatus(AsyncWebServer* server, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  void systemStatus(AsyncWebServerRequest* request);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end SystemStatus_h
 | 
			
		||||
							
								
								
									
										85
									
								
								lib/framework/UploadFirmwareService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								lib/framework/UploadFirmwareService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
#include <UploadFirmwareService.h>
 | 
			
		||||
 | 
			
		||||
UploadFirmwareService::UploadFirmwareService(AsyncWebServer* server, SecurityManager* securityManager) :
 | 
			
		||||
    _securityManager(securityManager) {
 | 
			
		||||
  server->on(UPLOAD_FIRMWARE_PATH,
 | 
			
		||||
             HTTP_POST,
 | 
			
		||||
             std::bind(&UploadFirmwareService::uploadComplete, this, std::placeholders::_1),
 | 
			
		||||
             std::bind(&UploadFirmwareService::handleUpload,
 | 
			
		||||
                       this,
 | 
			
		||||
                       std::placeholders::_1,
 | 
			
		||||
                       std::placeholders::_2,
 | 
			
		||||
                       std::placeholders::_3,
 | 
			
		||||
                       std::placeholders::_4,
 | 
			
		||||
                       std::placeholders::_5,
 | 
			
		||||
                       std::placeholders::_6));
 | 
			
		||||
#ifdef ESP8266
 | 
			
		||||
  Update.runAsync(true);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UploadFirmwareService::handleUpload(AsyncWebServerRequest* request,
 | 
			
		||||
                                         const String& filename,
 | 
			
		||||
                                         size_t index,
 | 
			
		||||
                                         uint8_t* data,
 | 
			
		||||
                                         size_t len,
 | 
			
		||||
                                         bool final) {
 | 
			
		||||
  if (!index) {
 | 
			
		||||
    Authentication authentication = _securityManager->authenticateRequest(request);
 | 
			
		||||
    if (AuthenticationPredicates::IS_ADMIN(authentication)) {
 | 
			
		||||
      if (Update.begin(request->contentLength())) {
 | 
			
		||||
        // success, let's make sure we end the update if the client hangs up
 | 
			
		||||
        request->onDisconnect(UploadFirmwareService::handleEarlyDisconnect);
 | 
			
		||||
      } else {
 | 
			
		||||
        // failed to begin, send an error response
 | 
			
		||||
        Update.printError(Serial);
 | 
			
		||||
        handleError(request, 500);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // send the forbidden response
 | 
			
		||||
      handleError(request, 403);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // if we haven't delt with an error, continue with the update
 | 
			
		||||
  if (!request->_tempObject) {
 | 
			
		||||
    if (Update.write(data, len) != len) {
 | 
			
		||||
      Update.printError(Serial);
 | 
			
		||||
      handleError(request, 500);
 | 
			
		||||
    }
 | 
			
		||||
    if (final) {
 | 
			
		||||
      if (!Update.end(true)) {
 | 
			
		||||
        Update.printError(Serial);
 | 
			
		||||
        handleError(request, 500);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UploadFirmwareService::uploadComplete(AsyncWebServerRequest* request) {
 | 
			
		||||
  // if no error, send the success response
 | 
			
		||||
  if (!request->_tempObject) {
 | 
			
		||||
    request->onDisconnect(RestartService::restartNow);
 | 
			
		||||
    AsyncWebServerResponse* response = request->beginResponse(200);
 | 
			
		||||
    request->send(response);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UploadFirmwareService::handleError(AsyncWebServerRequest* request, int code) {
 | 
			
		||||
  // if we have had an error already, do nothing
 | 
			
		||||
  if (request->_tempObject) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // send the error code to the client and record the error code in the temp object
 | 
			
		||||
  request->_tempObject = new int(code);
 | 
			
		||||
  AsyncWebServerResponse* response = request->beginResponse(code);
 | 
			
		||||
  request->send(response);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UploadFirmwareService::handleEarlyDisconnect() {
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  Update.abort();
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  Update.end();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								lib/framework/UploadFirmwareService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lib/framework/UploadFirmwareService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
#ifndef UploadFirmwareService_h
 | 
			
		||||
#define UploadFirmwareService_h
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <Update.h>
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
#include <RestartService.h>
 | 
			
		||||
 | 
			
		||||
#define UPLOAD_FIRMWARE_PATH "/rest/uploadFirmware"
 | 
			
		||||
 | 
			
		||||
class UploadFirmwareService {
 | 
			
		||||
 public:
 | 
			
		||||
  UploadFirmwareService(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();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end UploadFirmwareService_h
 | 
			
		||||
							
								
								
									
										273
									
								
								lib/framework/WebSocketTxRx.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								lib/framework/WebSocketTxRx.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,273 @@
 | 
			
		||||
#ifndef WebSocketTxRx_h
 | 
			
		||||
#define WebSocketTxRx_h
 | 
			
		||||
 | 
			
		||||
#include <StatefulService.h>
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
 | 
			
		||||
#define WEB_SOCKET_CLIENT_ID_MSG_SIZE 128
 | 
			
		||||
 | 
			
		||||
#define WEB_SOCKET_ORIGIN "websocket"
 | 
			
		||||
#define WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX "websocket:"
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class WebSocketConnector {
 | 
			
		||||
 protected:
 | 
			
		||||
  StatefulService<T>* _statefulService;
 | 
			
		||||
  AsyncWebServer* _server;
 | 
			
		||||
  AsyncWebSocket _webSocket;
 | 
			
		||||
  size_t _bufferSize;
 | 
			
		||||
 | 
			
		||||
  WebSocketConnector(StatefulService<T>* statefulService,
 | 
			
		||||
                     AsyncWebServer* server,
 | 
			
		||||
                     const char* webSocketPath,
 | 
			
		||||
                     SecurityManager* securityManager,
 | 
			
		||||
                     AuthenticationPredicate authenticationPredicate,
 | 
			
		||||
                     size_t bufferSize) :
 | 
			
		||||
      _statefulService(statefulService), _server(server), _webSocket(webSocketPath), _bufferSize(bufferSize) {
 | 
			
		||||
    _webSocket.setFilter(securityManager->filterRequest(authenticationPredicate));
 | 
			
		||||
    _webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent,
 | 
			
		||||
                                 this,
 | 
			
		||||
                                 std::placeholders::_1,
 | 
			
		||||
                                 std::placeholders::_2,
 | 
			
		||||
                                 std::placeholders::_3,
 | 
			
		||||
                                 std::placeholders::_4,
 | 
			
		||||
                                 std::placeholders::_5,
 | 
			
		||||
                                 std::placeholders::_6));
 | 
			
		||||
    _server->addHandler(&_webSocket);
 | 
			
		||||
    _server->on(webSocketPath, HTTP_GET, std::bind(&WebSocketConnector::forbidden, this, std::placeholders::_1));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WebSocketConnector(StatefulService<T>* statefulService,
 | 
			
		||||
                     AsyncWebServer* server,
 | 
			
		||||
                     const char* webSocketPath,
 | 
			
		||||
                     size_t bufferSize) :
 | 
			
		||||
      _statefulService(statefulService), _server(server), _webSocket(webSocketPath), _bufferSize(bufferSize) {
 | 
			
		||||
    _webSocket.onEvent(std::bind(&WebSocketConnector::onWSEvent,
 | 
			
		||||
                                 this,
 | 
			
		||||
                                 std::placeholders::_1,
 | 
			
		||||
                                 std::placeholders::_2,
 | 
			
		||||
                                 std::placeholders::_3,
 | 
			
		||||
                                 std::placeholders::_4,
 | 
			
		||||
                                 std::placeholders::_5,
 | 
			
		||||
                                 std::placeholders::_6));
 | 
			
		||||
    _server->addHandler(&_webSocket);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void onWSEvent(AsyncWebSocket* server,
 | 
			
		||||
                         AsyncWebSocketClient* client,
 | 
			
		||||
                         AwsEventType type,
 | 
			
		||||
                         void* arg,
 | 
			
		||||
                         uint8_t* data,
 | 
			
		||||
                         size_t len) = 0;
 | 
			
		||||
 | 
			
		||||
  String clientId(AsyncWebSocketClient* client) {
 | 
			
		||||
    return WEB_SOCKET_ORIGIN_CLIENT_ID_PREFIX + String(client->id());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  void forbidden(AsyncWebServerRequest* request) {
 | 
			
		||||
    request->send(403);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class WebSocketTx : virtual public WebSocketConnector<T> {
 | 
			
		||||
 public:
 | 
			
		||||
  WebSocketTx(JsonStateReader<T> stateReader,
 | 
			
		||||
              StatefulService<T>* statefulService,
 | 
			
		||||
              AsyncWebServer* server,
 | 
			
		||||
              const char* webSocketPath,
 | 
			
		||||
              SecurityManager* securityManager,
 | 
			
		||||
              AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
 | 
			
		||||
              size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      WebSocketConnector<T>(statefulService,
 | 
			
		||||
                            server,
 | 
			
		||||
                            webSocketPath,
 | 
			
		||||
                            securityManager,
 | 
			
		||||
                            authenticationPredicate,
 | 
			
		||||
                            bufferSize),
 | 
			
		||||
      _stateReader(stateReader) {
 | 
			
		||||
    WebSocketConnector<T>::_statefulService->addUpdateHandler(
 | 
			
		||||
        [&](const String& originId) { transmitData(nullptr, originId); }, false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WebSocketTx(JsonStateReader<T> stateReader,
 | 
			
		||||
              StatefulService<T>* statefulService,
 | 
			
		||||
              AsyncWebServer* server,
 | 
			
		||||
              const char* webSocketPath,
 | 
			
		||||
              size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), _stateReader(stateReader) {
 | 
			
		||||
    WebSocketConnector<T>::_statefulService->addUpdateHandler(
 | 
			
		||||
        [&](const String& originId) { transmitData(nullptr, originId); }, false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  virtual void onWSEvent(AsyncWebSocket* server,
 | 
			
		||||
                         AsyncWebSocketClient* client,
 | 
			
		||||
                         AwsEventType type,
 | 
			
		||||
                         void* arg,
 | 
			
		||||
                         uint8_t* data,
 | 
			
		||||
                         size_t len) {
 | 
			
		||||
    if (type == WS_EVT_CONNECT) {
 | 
			
		||||
      // when a client connects, we transmit it's id and the current payload
 | 
			
		||||
      transmitId(client);
 | 
			
		||||
      transmitData(client, WEB_SOCKET_ORIGIN);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  JsonStateReader<T> _stateReader;
 | 
			
		||||
 | 
			
		||||
  void transmitId(AsyncWebSocketClient* client) {
 | 
			
		||||
    DynamicJsonDocument jsonDocument = DynamicJsonDocument(WEB_SOCKET_CLIENT_ID_MSG_SIZE);
 | 
			
		||||
    JsonObject root = jsonDocument.to<JsonObject>();
 | 
			
		||||
    root["type"] = "id";
 | 
			
		||||
    root["id"] = WebSocketConnector<T>::clientId(client);
 | 
			
		||||
    size_t len = measureJson(jsonDocument);
 | 
			
		||||
    AsyncWebSocketMessageBuffer* buffer = WebSocketConnector<T>::_webSocket.makeBuffer(len);
 | 
			
		||||
    if (buffer) {
 | 
			
		||||
      serializeJson(jsonDocument, (char*)buffer->get(), len + 1);
 | 
			
		||||
      client->text(buffer);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Broadcasts the payload to the destination, if provided. Otherwise broadcasts to all clients except the origin, if
 | 
			
		||||
   * specified.
 | 
			
		||||
   *
 | 
			
		||||
   * Original implementation sent clients their own IDs so they could ignore updates they initiated. This approach
 | 
			
		||||
   * simplifies the client and the server implementation but may not be sufficent for all use-cases.
 | 
			
		||||
   */
 | 
			
		||||
  void transmitData(AsyncWebSocketClient* client, const String& originId) {
 | 
			
		||||
    DynamicJsonDocument jsonDocument = DynamicJsonDocument(WebSocketConnector<T>::_bufferSize);
 | 
			
		||||
    JsonObject root = jsonDocument.to<JsonObject>();
 | 
			
		||||
    root["type"] = "payload";
 | 
			
		||||
    root["origin_id"] = originId;
 | 
			
		||||
    JsonObject payload = root.createNestedObject("payload");
 | 
			
		||||
    WebSocketConnector<T>::_statefulService->read(payload, _stateReader);
 | 
			
		||||
 | 
			
		||||
    size_t len = measureJson(jsonDocument);
 | 
			
		||||
    AsyncWebSocketMessageBuffer* buffer = WebSocketConnector<T>::_webSocket.makeBuffer(len);
 | 
			
		||||
    if (buffer) {
 | 
			
		||||
      serializeJson(jsonDocument, (char*)buffer->get(), len + 1);
 | 
			
		||||
      if (client) {
 | 
			
		||||
        client->text(buffer);
 | 
			
		||||
      } else {
 | 
			
		||||
        WebSocketConnector<T>::_webSocket.textAll(buffer);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class WebSocketRx : virtual public WebSocketConnector<T> {
 | 
			
		||||
 public:
 | 
			
		||||
  WebSocketRx(JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
              StatefulService<T>* statefulService,
 | 
			
		||||
              AsyncWebServer* server,
 | 
			
		||||
              const char* webSocketPath,
 | 
			
		||||
              SecurityManager* securityManager,
 | 
			
		||||
              AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
 | 
			
		||||
              size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      WebSocketConnector<T>(statefulService,
 | 
			
		||||
                            server,
 | 
			
		||||
                            webSocketPath,
 | 
			
		||||
                            securityManager,
 | 
			
		||||
                            authenticationPredicate,
 | 
			
		||||
                            bufferSize),
 | 
			
		||||
      _stateUpdater(stateUpdater) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WebSocketRx(JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
              StatefulService<T>* statefulService,
 | 
			
		||||
              AsyncWebServer* server,
 | 
			
		||||
              const char* webSocketPath,
 | 
			
		||||
              size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize), _stateUpdater(stateUpdater) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  virtual void onWSEvent(AsyncWebSocket* server,
 | 
			
		||||
                         AsyncWebSocketClient* client,
 | 
			
		||||
                         AwsEventType type,
 | 
			
		||||
                         void* arg,
 | 
			
		||||
                         uint8_t* data,
 | 
			
		||||
                         size_t len) {
 | 
			
		||||
    if (type == WS_EVT_DATA) {
 | 
			
		||||
      AwsFrameInfo* info = (AwsFrameInfo*)arg;
 | 
			
		||||
      if (info->final && info->index == 0 && info->len == len) {
 | 
			
		||||
        if (info->opcode == WS_TEXT) {
 | 
			
		||||
          DynamicJsonDocument jsonDocument = DynamicJsonDocument(WebSocketConnector<T>::_bufferSize);
 | 
			
		||||
          DeserializationError error = deserializeJson(jsonDocument, (char*)data);
 | 
			
		||||
          if (!error && jsonDocument.is<JsonObject>()) {
 | 
			
		||||
            JsonObject jsonObject = jsonDocument.as<JsonObject>();
 | 
			
		||||
            WebSocketConnector<T>::_statefulService->update(
 | 
			
		||||
                jsonObject, _stateUpdater, WebSocketConnector<T>::clientId(client));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  JsonStateUpdater<T> _stateUpdater;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class WebSocketTxRx : public WebSocketTx<T>, public WebSocketRx<T> {
 | 
			
		||||
 public:
 | 
			
		||||
  WebSocketTxRx(JsonStateReader<T> stateReader,
 | 
			
		||||
                JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
                StatefulService<T>* statefulService,
 | 
			
		||||
                AsyncWebServer* server,
 | 
			
		||||
                const char* webSocketPath,
 | 
			
		||||
                SecurityManager* securityManager,
 | 
			
		||||
                AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
 | 
			
		||||
                size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      WebSocketConnector<T>(statefulService,
 | 
			
		||||
                            server,
 | 
			
		||||
                            webSocketPath,
 | 
			
		||||
                            securityManager,
 | 
			
		||||
                            authenticationPredicate,
 | 
			
		||||
                            bufferSize),
 | 
			
		||||
      WebSocketTx<T>(stateReader,
 | 
			
		||||
                     statefulService,
 | 
			
		||||
                     server,
 | 
			
		||||
                     webSocketPath,
 | 
			
		||||
                     securityManager,
 | 
			
		||||
                     authenticationPredicate,
 | 
			
		||||
                     bufferSize),
 | 
			
		||||
      WebSocketRx<T>(stateUpdater,
 | 
			
		||||
                     statefulService,
 | 
			
		||||
                     server,
 | 
			
		||||
                     webSocketPath,
 | 
			
		||||
                     securityManager,
 | 
			
		||||
                     authenticationPredicate,
 | 
			
		||||
                     bufferSize) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WebSocketTxRx(JsonStateReader<T> stateReader,
 | 
			
		||||
                JsonStateUpdater<T> stateUpdater,
 | 
			
		||||
                StatefulService<T>* statefulService,
 | 
			
		||||
                AsyncWebServer* server,
 | 
			
		||||
                const char* webSocketPath,
 | 
			
		||||
                size_t bufferSize = DEFAULT_BUFFER_SIZE) :
 | 
			
		||||
      WebSocketConnector<T>(statefulService, server, webSocketPath, bufferSize),
 | 
			
		||||
      WebSocketTx<T>(stateReader, statefulService, server, webSocketPath, bufferSize),
 | 
			
		||||
      WebSocketRx<T>(stateUpdater, statefulService, server, webSocketPath, bufferSize) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void onWSEvent(AsyncWebSocket* server,
 | 
			
		||||
                 AsyncWebSocketClient* client,
 | 
			
		||||
                 AwsEventType type,
 | 
			
		||||
                 void* arg,
 | 
			
		||||
                 uint8_t* data,
 | 
			
		||||
                 size_t len) {
 | 
			
		||||
    WebSocketRx<T>::onWSEvent(server, client, type, arg, data, len);
 | 
			
		||||
    WebSocketTx<T>::onWSEvent(server, client, type, arg, data, len);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										70
									
								
								lib/framework/WiFiScanner.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								lib/framework/WiFiScanner.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
#include <WiFiScanner.h>
 | 
			
		||||
 | 
			
		||||
WiFiScanner::WiFiScanner(AsyncWebServer* server, SecurityManager* securityManager) {
 | 
			
		||||
  server->on(SCAN_NETWORKS_SERVICE_PATH,
 | 
			
		||||
             HTTP_GET,
 | 
			
		||||
             securityManager->wrapRequest(std::bind(&WiFiScanner::scanNetworks, this, std::placeholders::_1),
 | 
			
		||||
                                          AuthenticationPredicates::IS_ADMIN));
 | 
			
		||||
  server->on(LIST_NETWORKS_SERVICE_PATH,
 | 
			
		||||
             HTTP_GET,
 | 
			
		||||
             securityManager->wrapRequest(std::bind(&WiFiScanner::listNetworks, this, std::placeholders::_1),
 | 
			
		||||
                                          AuthenticationPredicates::IS_ADMIN));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void WiFiScanner::scanNetworks(AsyncWebServerRequest* request) {
 | 
			
		||||
  if (WiFi.scanComplete() != -1) {
 | 
			
		||||
    WiFi.scanDelete();
 | 
			
		||||
    WiFi.scanNetworks(true);
 | 
			
		||||
  }
 | 
			
		||||
  request->send(202);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WiFiScanner::listNetworks(AsyncWebServerRequest* request) {
 | 
			
		||||
  int numNetworks = WiFi.scanComplete();
 | 
			
		||||
  if (numNetworks > -1) {
 | 
			
		||||
    AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_WIFI_SCANNER_SIZE);
 | 
			
		||||
    JsonObject root = response->getRoot();
 | 
			
		||||
    JsonArray networks = root.createNestedArray("networks");
 | 
			
		||||
    for (int i = 0; i < numNetworks; i++) {
 | 
			
		||||
      JsonObject network = networks.createNestedObject();
 | 
			
		||||
      network["rssi"] = WiFi.RSSI(i);
 | 
			
		||||
      network["ssid"] = WiFi.SSID(i);
 | 
			
		||||
      network["bssid"] = WiFi.BSSIDstr(i);
 | 
			
		||||
      network["channel"] = WiFi.channel(i);
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
      network["encryption_type"] = (uint8_t)WiFi.encryptionType(i);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
      network["encryption_type"] = convertEncryptionType(WiFi.encryptionType(i));
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
    response->setLength();
 | 
			
		||||
    request->send(response);
 | 
			
		||||
  } else if (numNetworks == -1) {
 | 
			
		||||
    request->send(202);
 | 
			
		||||
  } else {
 | 
			
		||||
    scanNetworks(request);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef ESP8266
 | 
			
		||||
/*
 | 
			
		||||
 * Convert encryption type to standard used by ESP32 rather than the translated form which the esp8266 libaries expose.
 | 
			
		||||
 *
 | 
			
		||||
 * This allows us to use a single set of mappings in the UI.
 | 
			
		||||
 */
 | 
			
		||||
uint8_t WiFiScanner::convertEncryptionType(uint8_t encryptionType) {
 | 
			
		||||
  switch (encryptionType) {
 | 
			
		||||
    case ENC_TYPE_NONE:
 | 
			
		||||
      return AUTH_OPEN;
 | 
			
		||||
    case ENC_TYPE_WEP:
 | 
			
		||||
      return AUTH_WEP;
 | 
			
		||||
    case ENC_TYPE_TKIP:
 | 
			
		||||
      return AUTH_WPA_PSK;
 | 
			
		||||
    case ENC_TYPE_CCMP:
 | 
			
		||||
      return AUTH_WPA2_PSK;
 | 
			
		||||
    case ENC_TYPE_AUTO:
 | 
			
		||||
      return AUTH_WPA_WPA2_PSK;
 | 
			
		||||
  }
 | 
			
		||||
  return -1;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										35
									
								
								lib/framework/WiFiScanner.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								lib/framework/WiFiScanner.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
#ifndef WiFiScanner_h
 | 
			
		||||
#define WiFiScanner_h
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
#include <AsyncJson.h>
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
 | 
			
		||||
#define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks"
 | 
			
		||||
#define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks"
 | 
			
		||||
 | 
			
		||||
#define MAX_WIFI_SCANNER_SIZE 1024
 | 
			
		||||
 | 
			
		||||
class WiFiScanner {
 | 
			
		||||
 public:
 | 
			
		||||
  WiFiScanner(AsyncWebServer* server, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  void scanNetworks(AsyncWebServerRequest* request);
 | 
			
		||||
  void listNetworks(AsyncWebServerRequest* request);
 | 
			
		||||
 | 
			
		||||
#ifdef ESP8266
 | 
			
		||||
  uint8_t convertEncryptionType(uint8_t encryptionType);
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end WiFiScanner_h
 | 
			
		||||
							
								
								
									
										100
									
								
								lib/framework/WiFiSettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								lib/framework/WiFiSettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
#include <WiFiSettingsService.h>
 | 
			
		||||
 | 
			
		||||
WiFiSettingsService::WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
 | 
			
		||||
    _httpEndpoint(WiFiSettings::read, WiFiSettings::update, this, server, WIFI_SETTINGS_SERVICE_PATH, securityManager),
 | 
			
		||||
    _fsPersistence(WiFiSettings::read, WiFiSettings::update, this, fs, WIFI_SETTINGS_FILE),
 | 
			
		||||
    _lastConnectionAttempt(0) {
 | 
			
		||||
  // We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default.
 | 
			
		||||
  // If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future.
 | 
			
		||||
  if (WiFi.getMode() != WIFI_OFF) {
 | 
			
		||||
    WiFi.mode(WIFI_OFF);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Disable WiFi config persistance and auto reconnect
 | 
			
		||||
  WiFi.persistent(false);
 | 
			
		||||
  WiFi.setAutoReconnect(false);
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  // Init the wifi driver on ESP32
 | 
			
		||||
  WiFi.mode(WIFI_MODE_MAX);
 | 
			
		||||
  WiFi.mode(WIFI_MODE_NULL);
 | 
			
		||||
  WiFi.onEvent(
 | 
			
		||||
      std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1, std::placeholders::_2),
 | 
			
		||||
      WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
 | 
			
		||||
  WiFi.onEvent(std::bind(&WiFiSettingsService::onStationModeStop, this, std::placeholders::_1, std::placeholders::_2),
 | 
			
		||||
               WiFiEvent_t::SYSTEM_EVENT_STA_STOP);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  _onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(
 | 
			
		||||
      std::bind(&WiFiSettingsService::onStationModeDisconnected, this, std::placeholders::_1));
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  addUpdateHandler([&](const String& originId) { reconfigureWiFiConnection(); }, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WiFiSettingsService::begin() {
 | 
			
		||||
  _fsPersistence.readFromFS();
 | 
			
		||||
  reconfigureWiFiConnection();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WiFiSettingsService::reconfigureWiFiConnection() {
 | 
			
		||||
  // reset last connection attempt to force loop to reconnect immediately
 | 
			
		||||
  _lastConnectionAttempt = 0;
 | 
			
		||||
 | 
			
		||||
// disconnect and de-configure wifi
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  if (WiFi.disconnect(true)) {
 | 
			
		||||
    _stopping = true;
 | 
			
		||||
  }
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  WiFi.disconnect(true);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WiFiSettingsService::loop() {
 | 
			
		||||
  unsigned long currentMillis = millis();
 | 
			
		||||
  if (!_lastConnectionAttempt || (unsigned long)(currentMillis - _lastConnectionAttempt) >= WIFI_RECONNECTION_DELAY) {
 | 
			
		||||
    _lastConnectionAttempt = currentMillis;
 | 
			
		||||
    manageSTA();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WiFiSettingsService::manageSTA() {
 | 
			
		||||
  // Abort if already connected, or if we have no SSID
 | 
			
		||||
  if (WiFi.isConnected() || _state.ssid.length() == 0) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // Connect or reconnect as required
 | 
			
		||||
  if ((WiFi.getMode() & WIFI_STA) == 0) {
 | 
			
		||||
    Serial.println(F("Connecting to WiFi."));
 | 
			
		||||
    if (_state.staticIPConfig) {
 | 
			
		||||
      // configure for static IP
 | 
			
		||||
      WiFi.config(_state.localIP, _state.gatewayIP, _state.subnetMask, _state.dnsIP1, _state.dnsIP2);
 | 
			
		||||
    } else {
 | 
			
		||||
      // configure for DHCP
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
      WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
 | 
			
		||||
      WiFi.setHostname(_state.hostname.c_str());
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
      WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY);
 | 
			
		||||
      WiFi.hostname(_state.hostname);
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
    // attempt to connect to the network
 | 
			
		||||
    WiFi.begin(_state.ssid.c_str(), _state.password.c_str());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
void WiFiSettingsService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
 | 
			
		||||
  WiFi.disconnect(true);
 | 
			
		||||
}
 | 
			
		||||
void WiFiSettingsService::onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info) {
 | 
			
		||||
  if (_stopping) {
 | 
			
		||||
    _lastConnectionAttempt = 0;
 | 
			
		||||
    _stopping = false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
void WiFiSettingsService::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
 | 
			
		||||
  WiFi.disconnect(true);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										110
									
								
								lib/framework/WiFiSettingsService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								lib/framework/WiFiSettingsService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
#ifndef WiFiSettingsService_h
 | 
			
		||||
#define WiFiSettingsService_h
 | 
			
		||||
 | 
			
		||||
#include <StatefulService.h>
 | 
			
		||||
#include <FSPersistence.h>
 | 
			
		||||
#include <HttpEndpoint.h>
 | 
			
		||||
#include <JsonUtils.h>
 | 
			
		||||
 | 
			
		||||
#define WIFI_SETTINGS_FILE "/config/wifiSettings.json"
 | 
			
		||||
#define WIFI_SETTINGS_SERVICE_PATH "/rest/wifiSettings"
 | 
			
		||||
#define WIFI_RECONNECTION_DELAY 1000 * 30
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_WIFI_SSID
 | 
			
		||||
#define FACTORY_WIFI_SSID ""
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_WIFI_PASSWORD
 | 
			
		||||
#define FACTORY_WIFI_PASSWORD ""
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef FACTORY_WIFI_HOSTNAME
 | 
			
		||||
#define FACTORY_WIFI_HOSTNAME ESPUtils::defaultDeviceValue("esp-react-")
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
class WiFiSettings {
 | 
			
		||||
 public:
 | 
			
		||||
  // core wifi configuration
 | 
			
		||||
  String ssid;
 | 
			
		||||
  String password;
 | 
			
		||||
  String hostname;
 | 
			
		||||
  bool staticIPConfig;
 | 
			
		||||
 | 
			
		||||
  // optional configuration for static IP address
 | 
			
		||||
  IPAddress localIP;
 | 
			
		||||
  IPAddress gatewayIP;
 | 
			
		||||
  IPAddress subnetMask;
 | 
			
		||||
  IPAddress dnsIP1;
 | 
			
		||||
  IPAddress dnsIP2;
 | 
			
		||||
 | 
			
		||||
  static void read(WiFiSettings& settings, JsonObject& root) {
 | 
			
		||||
    // connection settings
 | 
			
		||||
    root["ssid"] = settings.ssid;
 | 
			
		||||
    root["password"] = settings.password;
 | 
			
		||||
    root["hostname"] = settings.hostname;
 | 
			
		||||
    root["static_ip_config"] = settings.staticIPConfig;
 | 
			
		||||
 | 
			
		||||
    // 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, WiFiSettings& settings) {
 | 
			
		||||
    settings.ssid = root["ssid"] | FACTORY_WIFI_SSID;
 | 
			
		||||
    settings.password = root["password"] | FACTORY_WIFI_PASSWORD;
 | 
			
		||||
    settings.hostname = root["hostname"] | FACTORY_WIFI_HOSTNAME;
 | 
			
		||||
    settings.staticIPConfig = root["static_ip_config"] | false;
 | 
			
		||||
 | 
			
		||||
    // 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 (settings.dnsIP1 == INADDR_NONE && settings.dnsIP2 != INADDR_NONE) {
 | 
			
		||||
      settings.dnsIP1 = settings.dnsIP2;
 | 
			
		||||
      settings.dnsIP2 = INADDR_NONE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Turning off static ip config if we don't meet the minimum requirements
 | 
			
		||||
    // of ipAddress, gateway and subnet. This may change to static ip only
 | 
			
		||||
    // as sensible defaults can be assumed for gateway and subnet
 | 
			
		||||
    if (settings.staticIPConfig &&
 | 
			
		||||
        (settings.localIP == INADDR_NONE || settings.gatewayIP == INADDR_NONE || settings.subnetMask == INADDR_NONE)) {
 | 
			
		||||
      settings.staticIPConfig = false;
 | 
			
		||||
    }
 | 
			
		||||
    return StateUpdateResult::CHANGED;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class WiFiSettingsService : public StatefulService<WiFiSettings> {
 | 
			
		||||
 public:
 | 
			
		||||
  WiFiSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
  void begin();
 | 
			
		||||
  void loop();
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  HttpEndpoint<WiFiSettings> _httpEndpoint;
 | 
			
		||||
  FSPersistence<WiFiSettings> _fsPersistence;
 | 
			
		||||
  unsigned long _lastConnectionAttempt;
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  bool _stopping;
 | 
			
		||||
  void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
			
		||||
  void onStationModeStop(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  WiFiEventHandler _onStationModeDisconnectedHandler;
 | 
			
		||||
  void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void reconfigureWiFiConnection();
 | 
			
		||||
  void manageSTA();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end WiFiSettingsService_h
 | 
			
		||||
							
								
								
									
										75
									
								
								lib/framework/WiFiStatus.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								lib/framework/WiFiStatus.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
#include <WiFiStatus.h>
 | 
			
		||||
 | 
			
		||||
WiFiStatus::WiFiStatus(AsyncWebServer* server, SecurityManager* securityManager) {
 | 
			
		||||
  server->on(WIFI_STATUS_SERVICE_PATH,
 | 
			
		||||
             HTTP_GET,
 | 
			
		||||
             securityManager->wrapRequest(std::bind(&WiFiStatus::wifiStatus, this, std::placeholders::_1),
 | 
			
		||||
                                          AuthenticationPredicates::IS_AUTHENTICATED));
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  WiFi.onEvent(onStationModeConnected, WiFiEvent_t::SYSTEM_EVENT_STA_CONNECTED);
 | 
			
		||||
  WiFi.onEvent(onStationModeDisconnected, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
 | 
			
		||||
  WiFi.onEvent(onStationModeGotIP, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  _onStationModeConnectedHandler = WiFi.onStationModeConnected(onStationModeConnected);
 | 
			
		||||
  _onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(onStationModeDisconnected);
 | 
			
		||||
  _onStationModeGotIPHandler = WiFi.onStationModeGotIP(onStationModeGotIP);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
void WiFiStatus::onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info) {
 | 
			
		||||
  Serial.println(F("WiFi Connected."));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WiFiStatus::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
 | 
			
		||||
  Serial.print(F("WiFi Disconnected. Reason code="));
 | 
			
		||||
  Serial.println(info.disconnected.reason);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WiFiStatus::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
 | 
			
		||||
  Serial.printf_P(
 | 
			
		||||
      PSTR("WiFi Got IP. localIP=%s, hostName=%s\r\n"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
 | 
			
		||||
}
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
void WiFiStatus::onStationModeConnected(const WiFiEventStationModeConnected& event) {
 | 
			
		||||
  Serial.print(F("WiFi Connected. SSID="));
 | 
			
		||||
  Serial.println(event.ssid);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WiFiStatus::onStationModeDisconnected(const WiFiEventStationModeDisconnected& event) {
 | 
			
		||||
  Serial.print(F("WiFi Disconnected. Reason code="));
 | 
			
		||||
  Serial.println(event.reason);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WiFiStatus::onStationModeGotIP(const WiFiEventStationModeGotIP& event) {
 | 
			
		||||
  Serial.printf_P(
 | 
			
		||||
      PSTR("WiFi Got IP. localIP=%s, hostName=%s\r\n"), event.ip.toString().c_str(), WiFi.hostname().c_str());
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void WiFiStatus::wifiStatus(AsyncWebServerRequest* request) {
 | 
			
		||||
  AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_WIFI_STATUS_SIZE);
 | 
			
		||||
  JsonObject root = response->getRoot();
 | 
			
		||||
  wl_status_t status = WiFi.status();
 | 
			
		||||
  root["status"] = (uint8_t)status;
 | 
			
		||||
  if (status == WL_CONNECTED) {
 | 
			
		||||
    root["local_ip"] = WiFi.localIP().toString();
 | 
			
		||||
    root["mac_address"] = WiFi.macAddress();
 | 
			
		||||
    root["rssi"] = WiFi.RSSI();
 | 
			
		||||
    root["ssid"] = WiFi.SSID();
 | 
			
		||||
    root["bssid"] = WiFi.BSSIDstr();
 | 
			
		||||
    root["channel"] = WiFi.channel();
 | 
			
		||||
    root["subnet_mask"] = WiFi.subnetMask().toString();
 | 
			
		||||
    root["gateway_ip"] = WiFi.gatewayIP().toString();
 | 
			
		||||
    IPAddress dnsIP1 = WiFi.dnsIP(0);
 | 
			
		||||
    IPAddress dnsIP2 = WiFi.dnsIP(1);
 | 
			
		||||
    if (dnsIP1 != INADDR_NONE) {
 | 
			
		||||
      root["dns_ip_1"] = dnsIP1.toString();
 | 
			
		||||
    }
 | 
			
		||||
    if (dnsIP2 != INADDR_NONE) {
 | 
			
		||||
      root["dns_ip_2"] = dnsIP2.toString();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  response->setLength();
 | 
			
		||||
  request->send(response);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								lib/framework/WiFiStatus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/framework/WiFiStatus.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
#ifndef WiFiStatus_h
 | 
			
		||||
#define WiFiStatus_h
 | 
			
		||||
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <AsyncTCP.h>
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#include <ESP8266WiFi.h>
 | 
			
		||||
#include <ESPAsyncTCP.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
#include <AsyncJson.h>
 | 
			
		||||
#include <ESPAsyncWebServer.h>
 | 
			
		||||
#include <IPAddress.h>
 | 
			
		||||
#include <SecurityManager.h>
 | 
			
		||||
 | 
			
		||||
#define MAX_WIFI_STATUS_SIZE 1024
 | 
			
		||||
#define WIFI_STATUS_SERVICE_PATH "/rest/wifiStatus"
 | 
			
		||||
 | 
			
		||||
class WiFiStatus {
 | 
			
		||||
 public:
 | 
			
		||||
  WiFiStatus(AsyncWebServer* server, SecurityManager* securityManager);
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
  // static functions for logging WiFi events to the UART
 | 
			
		||||
  static void onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
			
		||||
  static void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
			
		||||
  static void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
  // handler refrences for logging important WiFi events over serial
 | 
			
		||||
  WiFiEventHandler _onStationModeConnectedHandler;
 | 
			
		||||
  WiFiEventHandler _onStationModeDisconnectedHandler;
 | 
			
		||||
  WiFiEventHandler _onStationModeGotIPHandler;
 | 
			
		||||
  // static functions for logging WiFi events to the UART
 | 
			
		||||
  static void onStationModeConnected(const WiFiEventStationModeConnected& event);
 | 
			
		||||
  static void onStationModeDisconnected(const WiFiEventStationModeDisconnected& event);
 | 
			
		||||
  static void onStationModeGotIP(const WiFiEventStationModeGotIP& event);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void wifiStatus(AsyncWebServerRequest* request);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end WiFiStatus_h
 | 
			
		||||
		Reference in New Issue
	
	Block a user