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