Rework backend add MQTT and WebSocket support
* Update back end to add MQTT and WebSocket support * Update demo project to demonstrate MQTT and WebSockets * Update documentation to describe newly added and modified functionallity * Introduce separate MQTT pub/sub, HTTP get/post and WebSocket rx/tx classes * Significant reanaming - more accurate class names * Use PROGMEM_WWW as default * Update README documenting PROGMEM_WWW as default * Update README with API changes
This commit is contained in:
		@@ -1,27 +0,0 @@
 | 
			
		||||
#include <DemoProject.h>
 | 
			
		||||
 | 
			
		||||
DemoProject::DemoProject(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
 | 
			
		||||
    AdminSettingsService(server, fs, securityManager, DEMO_SETTINGS_PATH, DEMO_SETTINGS_FILE) {
 | 
			
		||||
  pinMode(BLINK_LED, OUTPUT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DemoProject::~DemoProject() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DemoProject::loop() {
 | 
			
		||||
  unsigned delay = MAX_DELAY / 255 * (255 - _settings.blinkSpeed);
 | 
			
		||||
  unsigned long currentMillis = millis();
 | 
			
		||||
  if (!_lastBlink || (unsigned long)(currentMillis - _lastBlink) >= delay) {
 | 
			
		||||
    _lastBlink = currentMillis;
 | 
			
		||||
    digitalWrite(BLINK_LED, !digitalRead(BLINK_LED));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DemoProject::readFromJsonObject(JsonObject& root) {
 | 
			
		||||
  _settings.blinkSpeed = root["blink_speed"] | DEFAULT_BLINK_SPEED;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DemoProject::writeToJsonObject(JsonObject& root) {
 | 
			
		||||
  // connection settings
 | 
			
		||||
  root["blink_speed"] = _settings.blinkSpeed;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
#ifndef DemoProject_h
 | 
			
		||||
#define DemoProject_h
 | 
			
		||||
 | 
			
		||||
#include <AdminSettingsService.h>
 | 
			
		||||
#include <ESP8266React.h>
 | 
			
		||||
 | 
			
		||||
#define BLINK_LED 2
 | 
			
		||||
#define MAX_DELAY 1000
 | 
			
		||||
 | 
			
		||||
#define DEFAULT_BLINK_SPEED 100
 | 
			
		||||
#define DEMO_SETTINGS_FILE "/config/demoSettings.json"
 | 
			
		||||
#define DEMO_SETTINGS_PATH "/rest/demoSettings"
 | 
			
		||||
 | 
			
		||||
class DemoSettings {
 | 
			
		||||
 public:
 | 
			
		||||
  uint8_t blinkSpeed;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DemoProject : public AdminSettingsService<DemoSettings> {
 | 
			
		||||
 public:
 | 
			
		||||
  DemoProject(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
 | 
			
		||||
  ~DemoProject();
 | 
			
		||||
 | 
			
		||||
  void loop();
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  unsigned long _lastBlink = 0;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void readFromJsonObject(JsonObject& root);
 | 
			
		||||
  void writeToJsonObject(JsonObject& root);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										16
									
								
								src/LightMqttSettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/LightMqttSettingsService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
#include <LightMqttSettingsService.h>
 | 
			
		||||
 | 
			
		||||
LightMqttSettingsService::LightMqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager) :
 | 
			
		||||
    _httpEndpoint(LightMqttSettings::serialize,
 | 
			
		||||
                  LightMqttSettings::deserialize,
 | 
			
		||||
                  this,
 | 
			
		||||
                  server,
 | 
			
		||||
                  LIGHT_BROKER_SETTINGS_PATH,
 | 
			
		||||
                  securityManager,
 | 
			
		||||
                  AuthenticationPredicates::IS_AUTHENTICATED),
 | 
			
		||||
    _fsPersistence(LightMqttSettings::serialize, LightMqttSettings::deserialize, this, fs, LIGHT_BROKER_SETTINGS_FILE) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LightMqttSettingsService::begin() {
 | 
			
		||||
  _fsPersistence.readFromFS();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								src/LightMqttSettingsService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/LightMqttSettingsService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
#ifndef LightMqttSettingsService_h
 | 
			
		||||
#define LightMqttSettingsService_h
 | 
			
		||||
 | 
			
		||||
#include <HttpEndpoint.h>
 | 
			
		||||
#include <FSPersistence.h>
 | 
			
		||||
 | 
			
		||||
#define LIGHT_BROKER_SETTINGS_FILE "/config/brokerSettings.json"
 | 
			
		||||
#define LIGHT_BROKER_SETTINGS_PATH "/rest/brokerSettings"
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class LightMqttSettings {
 | 
			
		||||
 public:
 | 
			
		||||
  String mqttPath;
 | 
			
		||||
  String name;
 | 
			
		||||
  String uniqueId;
 | 
			
		||||
 | 
			
		||||
  static void serialize(LightMqttSettings& settings, JsonObject& root) {
 | 
			
		||||
    root["mqtt_path"] = settings.mqttPath;
 | 
			
		||||
    root["name"] = settings.name;
 | 
			
		||||
    root["unique_id"] = settings.uniqueId;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static void deserialize(JsonObject& root, LightMqttSettings& settings) {
 | 
			
		||||
    settings.mqttPath = root["mqtt_path"] | defaultDeviceValue("homeassistant/light/");
 | 
			
		||||
    settings.name = root["name"] | defaultDeviceValue("light-");
 | 
			
		||||
    settings.uniqueId = root["unique_id"] | defaultDeviceValue("light-");
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class LightMqttSettingsService : public StatefulService<LightMqttSettings> {
 | 
			
		||||
 public:
 | 
			
		||||
  LightMqttSettingsService(AsyncWebServer* server, FS* fs, SecurityManager* securityManager);
 | 
			
		||||
  void begin();
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  HttpEndpoint<LightMqttSettings> _httpEndpoint;
 | 
			
		||||
  FSPersistence<LightMqttSettings> _fsPersistence;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // end LightMqttSettingsService_h
 | 
			
		||||
							
								
								
									
										73
									
								
								src/LightStateService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/LightStateService.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
#include <LightStateService.h>
 | 
			
		||||
 | 
			
		||||
LightStateService::LightStateService(AsyncWebServer* server,
 | 
			
		||||
                                     SecurityManager* securityManager,
 | 
			
		||||
                                     AsyncMqttClient* mqttClient,
 | 
			
		||||
                                     LightMqttSettingsService* lightMqttSettingsService) :
 | 
			
		||||
    _httpEndpoint(LightState::serialize,
 | 
			
		||||
                  LightState::deserialize,
 | 
			
		||||
                  this,
 | 
			
		||||
                  server,
 | 
			
		||||
                  LIGHT_SETTINGS_ENDPOINT_PATH,
 | 
			
		||||
                  securityManager,
 | 
			
		||||
                  AuthenticationPredicates::IS_AUTHENTICATED),
 | 
			
		||||
    _mqttPubSub(LightState::haSerialize, LightState::haDeserialize, this, mqttClient),
 | 
			
		||||
    _webSocket(LightState::serialize,
 | 
			
		||||
               LightState::deserialize,
 | 
			
		||||
               this,
 | 
			
		||||
               server,
 | 
			
		||||
               LIGHT_SETTINGS_SOCKET_PATH,
 | 
			
		||||
               securityManager,
 | 
			
		||||
               AuthenticationPredicates::IS_AUTHENTICATED),
 | 
			
		||||
    _mqttClient(mqttClient),
 | 
			
		||||
    _lightMqttSettingsService(lightMqttSettingsService) {
 | 
			
		||||
  // configure blink led to be output
 | 
			
		||||
  pinMode(BLINK_LED, OUTPUT);
 | 
			
		||||
 | 
			
		||||
  // configure MQTT callback
 | 
			
		||||
  _mqttClient->onConnect(std::bind(&LightStateService::registerConfig, this));
 | 
			
		||||
 | 
			
		||||
  // configure update handler for when the light settings change
 | 
			
		||||
  _lightMqttSettingsService->addUpdateHandler([&](String originId) { registerConfig(); }, false);
 | 
			
		||||
 | 
			
		||||
  // configure settings service update handler to update LED state
 | 
			
		||||
  addUpdateHandler([&](String originId) { onConfigUpdated(); }, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LightStateService::begin() {
 | 
			
		||||
  _state.ledOn = DEFAULT_LED_STATE;
 | 
			
		||||
  onConfigUpdated();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LightStateService::onConfigUpdated() {
 | 
			
		||||
  digitalWrite(BLINK_LED, _state.ledOn ? LED_ON : LED_OFF);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LightStateService::registerConfig() {
 | 
			
		||||
  if (!_mqttClient->connected()) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  String configTopic;
 | 
			
		||||
  String setTopic;
 | 
			
		||||
  String stateTopic;
 | 
			
		||||
 | 
			
		||||
  DynamicJsonDocument doc(256);
 | 
			
		||||
  _lightMqttSettingsService->read([&](LightMqttSettings& settings) {
 | 
			
		||||
    configTopic = settings.mqttPath + "/config";
 | 
			
		||||
    setTopic = settings.mqttPath + "/set";
 | 
			
		||||
    stateTopic = settings.mqttPath + "/state";
 | 
			
		||||
    doc["~"] = settings.mqttPath;
 | 
			
		||||
    doc["name"] = settings.name;
 | 
			
		||||
    doc["unique_id"] = settings.uniqueId;
 | 
			
		||||
  });
 | 
			
		||||
  doc["cmd_t"] = "~/set";
 | 
			
		||||
  doc["stat_t"] = "~/state";
 | 
			
		||||
  doc["schema"] = "json";
 | 
			
		||||
  doc["brightness"] = false;
 | 
			
		||||
 | 
			
		||||
  String payload;
 | 
			
		||||
  serializeJson(doc, payload);
 | 
			
		||||
  _mqttClient->publish(configTopic.c_str(), 0, false, payload.c_str());
 | 
			
		||||
 | 
			
		||||
  _mqttPubSub.configureTopics(stateTopic, setTopic);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								src/LightStateService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/LightStateService.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
#ifndef LightStateService_h
 | 
			
		||||
#define LightStateService_h
 | 
			
		||||
 | 
			
		||||
#include <LightMqttSettingsService.h>
 | 
			
		||||
 | 
			
		||||
#include <HttpEndpoint.h>
 | 
			
		||||
#include <MqttPubSub.h>
 | 
			
		||||
#include <WebSocketTxRx.h>
 | 
			
		||||
 | 
			
		||||
#define BLINK_LED 2
 | 
			
		||||
#define PRINT_DELAY 5000
 | 
			
		||||
 | 
			
		||||
#define DEFAULT_LED_STATE false
 | 
			
		||||
#define OFF_STATE "OFF"
 | 
			
		||||
#define ON_STATE "ON"
 | 
			
		||||
 | 
			
		||||
// Note that the built-in LED is on when the pin is low on most NodeMCU boards.
 | 
			
		||||
// This is because the anode is tied to VCC and the cathode to the GPIO 4 (Arduino pin 2).
 | 
			
		||||
#ifdef ESP32
 | 
			
		||||
#define LED_ON 0x1
 | 
			
		||||
#define LED_OFF 0x0
 | 
			
		||||
#elif defined(ESP8266)
 | 
			
		||||
#define LED_ON 0x0
 | 
			
		||||
#define LED_OFF 0x1
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define LIGHT_SETTINGS_ENDPOINT_PATH "/rest/lightState"
 | 
			
		||||
#define LIGHT_SETTINGS_SOCKET_PATH "/ws/lightState"
 | 
			
		||||
 | 
			
		||||
class LightState {
 | 
			
		||||
 public:
 | 
			
		||||
  bool ledOn;
 | 
			
		||||
 | 
			
		||||
  static void serialize(LightState& settings, JsonObject& root) {
 | 
			
		||||
    root["led_on"] = settings.ledOn;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static void deserialize(JsonObject& root, LightState& settings) {
 | 
			
		||||
    settings.ledOn = root["led_on"] | DEFAULT_LED_STATE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static void haSerialize(LightState& settings, JsonObject& root) {
 | 
			
		||||
    root["state"] = settings.ledOn ? ON_STATE : OFF_STATE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static void haDeserialize(JsonObject& root, LightState& settings) {
 | 
			
		||||
    String state = root["state"];
 | 
			
		||||
    settings.ledOn = strcmp(ON_STATE, state.c_str()) ? false : true;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class LightStateService : public StatefulService<LightState> {
 | 
			
		||||
 public:
 | 
			
		||||
  LightStateService(AsyncWebServer* server,
 | 
			
		||||
                    SecurityManager* securityManager,
 | 
			
		||||
                    AsyncMqttClient* mqttClient,
 | 
			
		||||
                    LightMqttSettingsService* lightMqttSettingsService);
 | 
			
		||||
  void begin();
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  HttpEndpoint<LightState> _httpEndpoint;
 | 
			
		||||
  MqttPubSub<LightState> _mqttPubSub;
 | 
			
		||||
  WebSocketTxRx<LightState> _webSocket;
 | 
			
		||||
  AsyncMqttClient* _mqttClient;
 | 
			
		||||
  LightMqttSettingsService* _lightMqttSettingsService;
 | 
			
		||||
 | 
			
		||||
  void registerConfig();
 | 
			
		||||
  void onConfigUpdated();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										20
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								src/main.cpp
									
									
									
									
									
								
							@@ -1,12 +1,18 @@
 | 
			
		||||
#include <DemoProject.h>
 | 
			
		||||
#include <ESP8266React.h>
 | 
			
		||||
#include <LightMqttSettingsService.h>
 | 
			
		||||
#include <LightStateService.h>
 | 
			
		||||
#include <FS.h>
 | 
			
		||||
 | 
			
		||||
#define SERIAL_BAUD_RATE 115200
 | 
			
		||||
 | 
			
		||||
AsyncWebServer server(80);
 | 
			
		||||
ESP8266React esp8266React(&server, &SPIFFS);
 | 
			
		||||
DemoProject demoProject = DemoProject(&server, &SPIFFS, esp8266React.getSecurityManager());
 | 
			
		||||
LightMqttSettingsService lightMqttSettingsService =
 | 
			
		||||
    LightMqttSettingsService(&server, &SPIFFS, esp8266React.getSecurityManager());
 | 
			
		||||
LightStateService lightStateService = LightStateService(&server,
 | 
			
		||||
                                                        esp8266React.getSecurityManager(),
 | 
			
		||||
                                                        esp8266React.getMqttClient(),
 | 
			
		||||
                                                        &lightMqttSettingsService);
 | 
			
		||||
 | 
			
		||||
void setup() {
 | 
			
		||||
  // start serial and filesystem
 | 
			
		||||
@@ -22,8 +28,11 @@ void setup() {
 | 
			
		||||
  // start the framework and demo project
 | 
			
		||||
  esp8266React.begin();
 | 
			
		||||
 | 
			
		||||
  // start the demo project
 | 
			
		||||
  demoProject.begin();
 | 
			
		||||
  // load the initial light settings
 | 
			
		||||
  lightStateService.begin();
 | 
			
		||||
 | 
			
		||||
  // start the light service
 | 
			
		||||
  lightMqttSettingsService.begin();
 | 
			
		||||
 | 
			
		||||
  // start the server
 | 
			
		||||
  server.begin();
 | 
			
		||||
@@ -32,7 +41,4 @@ void setup() {
 | 
			
		||||
void loop() {
 | 
			
		||||
  // run the framework's loop function
 | 
			
		||||
  esp8266React.loop();
 | 
			
		||||
 | 
			
		||||
  // run the demo project's loop function
 | 
			
		||||
  demoProject.loop();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user