Add examples (brightness, color temp, temp sensor, blind control) + AlexaStatusMessage; update src/keywords/readme; drop library.json/properties

This commit is contained in:
David 2024-12-24 12:00:00 +00:00
parent 9bf63a4146
commit 1313f47856
19 changed files with 2780 additions and 2849 deletions

View file

@ -1,93 +1,93 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Alex2ESP.h>
// Define constants for WiFi and Alexa Client configuration
const char* WIFI_SSID = "";
const char* ALEXA_USERNAME = "";
const char* ALEXA_PASSWORD = "";
const char* ALEXA_ROOT_TOPIC = "";
// Create the Alexa client object
Alex2ESP alexClient;
AlexaDevice* device1;
// Initial state for the light (PowerController)
PowerController outputState = PowerController::OFF;
void setup() {
// Initialize the LED as output
pinMode(LED_BUILTIN, OUTPUT);
// Initialize serial communication for debugging
Serial.begin(74880);
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID);
Serial.print("[WIFI] Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println();
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
// Initialize the Alexa client
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
// Register a new device with a unique ID and name
device1 = alexClient.getDevice("Test Lamp", "ESP-01");
// Set the device's display category to LIGHT
device1->setDisplayCategory(DisplayCategory::LIGHT);
// Add a power controller capability to the device
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
// Register event listener for "ReportState" directive
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.send();
});
// Register event listener for "Event" directive to handle state changes
device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
if (type == AlexaInterfaceType::POWER_CONTROLLER) {
if (directive["header"]["name"] == "TurnOn") {
outputState = PowerController::ON;
Serial.println("Turning ON");
} else if (directive["header"]["name"] == "TurnOff") {
outputState = PowerController::OFF;
Serial.println("Turning OFF");
}
// Send the updated state after change
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.send();
}
});
}
void loop() {
// Process Alexa client communication
alexClient.loop();
// Update the state of the LED based on the power controller state
digitalWrite(LED_BUILTIN, outputState != PowerController::ON);
}
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Alex2ESP.h>
// Define constants for WiFi and Alexa Client configuration
const char* WIFI_SSID = "";
const char* ALEXA_USERNAME = "";
const char* ALEXA_PASSWORD = "";
const char* ALEXA_ROOT_TOPIC = "";
// Create the Alexa client object
Alex2ESP alexClient;
AlexaDevice* device1;
// Initial state for the light (PowerController)
PowerController outputState = PowerController::OFF;
void setup() {
// Initialize the LED as output
pinMode(LED_BUILTIN, OUTPUT);
// Initialize serial communication for debugging
Serial.begin(74880);
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID);
Serial.print("[WIFI] Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println();
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
// Initialize the Alexa client
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
// Register a new device with a unique ID and name
device1 = alexClient.getDevice("Test Lamp", "ESP-01");
// Set the device's display category to LIGHT
device1->setDisplayCategory(DisplayCategory::LIGHT);
// Add a power controller capability to the device
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
// Register event listener for "ReportState" directive
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.send();
});
// Register event listener for "Event" directive to handle state changes
device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
if (type == AlexaInterfaceType::POWER_CONTROLLER) {
if (directive["header"]["name"] == "TurnOn") {
outputState = PowerController::ON;
Serial.println("Turning ON");
} else if (directive["header"]["name"] == "TurnOff") {
outputState = PowerController::OFF;
Serial.println("Turning OFF");
}
// Send the updated state after change
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.send();
}
});
}
void loop() {
// Process Alexa client communication
alexClient.loop();
// Update the state of the LED based on the power controller state
digitalWrite(LED_BUILTIN, outputState != PowerController::ON);
}

View file

@ -1,127 +1,127 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Alex2ESP.h>
// Define constants for WiFi and Alexa Client configuration
const char *WIFI_SSID = "";
const char *ALEXA_USERNAME = "";
const char *ALEXA_PASSWORD = "";
const char *ALEXA_ROOT_TOPIC = "";
// Create the Alexa client object
Alex2ESP alexClient;
AlexaDevice *device1;
// Initial state for the blinds (PowerController)
PowerController outputState = PowerController::OFF;
void setup()
{
// Initialize the LED as output
pinMode(LED_BUILTIN, OUTPUT);
// Initialize serial communication for debugging
Serial.begin(74880);
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID);
Serial.print("[WIFI] Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
delay(100);
}
Serial.println();
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
// Initialize the Alexa client
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
String deviceName="Bedroom Blinds";
// Register a new device with a unique ID and name
device1 = alexClient.getDevice(deviceName, "ESP-01");
// Set the device's display category to LIGHT
device1->setDisplayCategory(DisplayCategory::LIGHT);
//a power controller will allow us to "turn on/off" the blinds (not really usefull but is possable)
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
// Add a toggle controller capability to the device
AlexaInterface* toggleController=device1->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER);
//toggle controllers are unique in that they require some additinal attrabutes by default we first need to set an instance name
toggleController->setInstance("ESP-01.Toggle");
//we also need to set a name for the interface, this is a sub device listed under the main device name
//to allow open/close commands as well as on/off commands we can use the same name as device name
toggleController->addFriendlyName(deviceName.c_str(),"en-US");
// Example of adding action mappings to map open/close to on/off
ActionMapping closeMapping({AlexaActions::Close}, "TurnOn");
ActionMapping openMapping({AlexaActions::Open}, "TurnOff");
// the full supported list of alexa action mappings can be found on
// https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery-objects.html#action-mapping
//note, only 'generic controller interffaces support action mappings for a full list visit
//https://developer.amazon.com/en-US/docs/alexa/device-apis/generic-controllers.html
toggleController->addActionMapping(closeMapping);
toggleController->addActionMapping(openMapping);
// Register event listener for "ReportState" directive
device1->registerEvent("ReportState", [](const JsonDocument &directive, const AlexaInterfaceType &type)
{
Serial.println("Report state requested.");
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddToggleControllerProp(outputState,"ESP-01.Toggle")
.AddPowerControllerProp(outputState)
.send(); });
// Register event listener for "Event" directive to handle state changes
device1->registerEvent("Event", [](const JsonDocument &directive, const AlexaInterfaceType &type)
{
if (type == AlexaInterfaceType::TOGGLE_CONTROLLER||type == AlexaInterfaceType::POWER_CONTROLLER) {
if (directive["header"]["name"] == "TurnOn") {
outputState = PowerController::ON;
Serial.println("Turning ON");
} else if (directive["header"]["name"] == "TurnOff") {
outputState = PowerController::OFF;
Serial.println("Turning OFF");
}
// Send the updated state after change
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
.AddHealthProp(EndpointHealth::OK)
.AddToggleControllerProp(outputState,"ESP-01.Toggle")
.AddPowerControllerProp(outputState)
.send();
} });
}
void loop()
{
// Process Alexa client communication
alexClient.loop();
// Update the state of the LED based on the power controller state
digitalWrite(LED_BUILTIN, outputState != PowerController::ON);
}
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Alex2ESP.h>
// Define constants for WiFi and Alexa Client configuration
const char *WIFI_SSID = "";
const char *ALEXA_USERNAME = "";
const char *ALEXA_PASSWORD = "";
const char *ALEXA_ROOT_TOPIC = "";
// Create the Alexa client object
Alex2ESP alexClient;
AlexaDevice *device1;
// Initial state for the blinds (PowerController)
PowerController outputState = PowerController::OFF;
void setup()
{
// Initialize the LED as output
pinMode(LED_BUILTIN, OUTPUT);
// Initialize serial communication for debugging
Serial.begin(74880);
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID);
Serial.print("[WIFI] Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
delay(100);
}
Serial.println();
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
// Initialize the Alexa client
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
String deviceName="Bedroom Blinds";
// Register a new device with a unique ID and name
device1 = alexClient.getDevice(deviceName, "ESP-01");
// Set the device's display category to LIGHT
device1->setDisplayCategory(DisplayCategory::LIGHT);
//a power controller will allow us to "turn on/off" the blinds (not really usefull but is possable)
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
// Add a toggle controller capability to the device
AlexaInterface* toggleController=device1->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER);
//toggle controllers are unique in that they require some additinal attrabutes by default we first need to set an instance name
toggleController->setInstance("ESP-01.Toggle");
//we also need to set a name for the interface, this is a sub device listed under the main device name
//to allow open/close commands as well as on/off commands we can use the same name as device name
toggleController->addFriendlyName(deviceName.c_str(),"en-US");
// Example of adding action mappings to map open/close to on/off
ActionMapping closeMapping({AlexaActions::Close}, "TurnOn");
ActionMapping openMapping({AlexaActions::Open}, "TurnOff");
// the full supported list of alexa action mappings can be found on
// https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery-objects.html#action-mapping
//note, only 'generic controller interffaces support action mappings for a full list visit
//https://developer.amazon.com/en-US/docs/alexa/device-apis/generic-controllers.html
toggleController->addActionMapping(closeMapping);
toggleController->addActionMapping(openMapping);
// Register event listener for "ReportState" directive
device1->registerEvent("ReportState", [](const JsonDocument &directive, const AlexaInterfaceType &type)
{
Serial.println("Report state requested.");
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddToggleControllerProp(outputState,"ESP-01.Toggle")
.AddPowerControllerProp(outputState)
.send(); });
// Register event listener for "Event" directive to handle state changes
device1->registerEvent("Event", [](const JsonDocument &directive, const AlexaInterfaceType &type)
{
if (type == AlexaInterfaceType::TOGGLE_CONTROLLER||type == AlexaInterfaceType::POWER_CONTROLLER) {
if (directive["header"]["name"] == "TurnOn") {
outputState = PowerController::ON;
Serial.println("Turning ON");
} else if (directive["header"]["name"] == "TurnOff") {
outputState = PowerController::OFF;
Serial.println("Turning OFF");
}
// Send the updated state after change
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
.AddHealthProp(EndpointHealth::OK)
.AddToggleControllerProp(outputState,"ESP-01.Toggle")
.AddPowerControllerProp(outputState)
.send();
} });
}
void loop()
{
// Process Alexa client communication
alexClient.loop();
// Update the state of the LED based on the power controller state
digitalWrite(LED_BUILTIN, outputState != PowerController::ON);
}

View file

@ -1,110 +1,110 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Alex2ESP.h>
// Define constants for WiFi and Alexa Client configuration
const char *WIFI_SSID = "";
const char *ALEXA_USERNAME = "";
const char *ALEXA_PASSWORD = "";
const char *ALEXA_ROOT_TOPIC = "";
// Create the Alexa client object
Alex2ESP alexClient;
AlexaDevice* device1;
// Initial state for the light (PowerController)
PowerController outputState = PowerController::OFF;
int brightness=100;
void setup() {
// Initialize the LED as output
pinMode(LED_BUILTIN, OUTPUT);
// Initialize serial communication for debugging
Serial.begin(74880);
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID);
Serial.print("[WIFI] Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println();
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
// Initialize the Alexa client
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
// Register a new device with a unique ID and name
device1 = alexClient.getDevice("Test Lamp", "ESP-01");
// Set the device's display category to LIGHT
device1->setDisplayCategory(DisplayCategory::LIGHT);
// Add a power controller capability to the device
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
// Add a brightness controller to the device to allow dimming
device1->addCapability(AlexaInterfaceType::BRIGHTNESS_CONTROLLER);
// Register event listener for "ReportState" directive
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.AddBrightnessControllerProp(brightness)
.send();
});
// Register event listener for "Event" directive to handle state changes
device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
if (type == AlexaInterfaceType::POWER_CONTROLLER) {
if (directive["header"]["name"] == "TurnOn") {
outputState = PowerController::ON;
Serial.println("Turning ON");
} else if (directive["header"]["name"] == "TurnOff") {
outputState = PowerController::OFF;
Serial.println("Turning OFF");
}
}else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) {
if(directive["header"]["name"]=="SetBrightness"){
brightness=directive["payload"]["brightness"];
Serial.println("setting brightness to: "+brightness);
outputState = PowerController::ON;
}
}else{
return;
}
// Send the updated state after change
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.AddBrightnessControllerProp(brightness)
.send();
});
}
void loop() {
// Process Alexa client communication
alexClient.loop();
// Update the state of the LED based on the power controller state and brightness
if(outputState == PowerController::ON){
analogWrite(LED_BUILTIN,map(brightness,0,100,255,0));
}else{
digitalWrite(LED_BUILTIN, true);
}
}
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Alex2ESP.h>
// Define constants for WiFi and Alexa Client configuration
const char *WIFI_SSID = "";
const char *ALEXA_USERNAME = "";
const char *ALEXA_PASSWORD = "";
const char *ALEXA_ROOT_TOPIC = "";
// Create the Alexa client object
Alex2ESP alexClient;
AlexaDevice* device1;
// Initial state for the light (PowerController)
PowerController outputState = PowerController::OFF;
int brightness=100;
void setup() {
// Initialize the LED as output
pinMode(LED_BUILTIN, OUTPUT);
// Initialize serial communication for debugging
Serial.begin(74880);
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID);
Serial.print("[WIFI] Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println();
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
// Initialize the Alexa client
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
// Register a new device with a unique ID and name
device1 = alexClient.getDevice("Test Lamp", "ESP-01");
// Set the device's display category to LIGHT
device1->setDisplayCategory(DisplayCategory::LIGHT);
// Add a power controller capability to the device
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
// Add a brightness controller to the device to allow dimming
device1->addCapability(AlexaInterfaceType::BRIGHTNESS_CONTROLLER);
// Register event listener for "ReportState" directive
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.AddBrightnessControllerProp(brightness)
.send();
});
// Register event listener for "Event" directive to handle state changes
device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
if (type == AlexaInterfaceType::POWER_CONTROLLER) {
if (directive["header"]["name"] == "TurnOn") {
outputState = PowerController::ON;
Serial.println("Turning ON");
} else if (directive["header"]["name"] == "TurnOff") {
outputState = PowerController::OFF;
Serial.println("Turning OFF");
}
}else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) {
if(directive["header"]["name"]=="SetBrightness"){
brightness=directive["payload"]["brightness"];
Serial.println("setting brightness to: "+brightness);
outputState = PowerController::ON;
}
}else{
return;
}
// Send the updated state after change
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.AddBrightnessControllerProp(brightness)
.send();
});
}
void loop() {
// Process Alexa client communication
alexClient.loop();
// Update the state of the LED based on the power controller state and brightness
if(outputState == PowerController::ON){
analogWrite(LED_BUILTIN,map(brightness,0,100,255,0));
}else{
digitalWrite(LED_BUILTIN, true);
}
}

View file

@ -1,122 +1,122 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Alex2ESP.h>
// Define constants for WiFi and Alexa Client configuration
const char *WIFI_SSID = "";
const char *ALEXA_USERNAME = "";
const char *ALEXA_PASSWORD = "";
const char *ALEXA_ROOT_TOPIC = "";
// Create the Alexa client object
Alex2ESP alexClient;
AlexaDevice* device1;
// Initial state for the light (PowerController)
PowerController outputState = PowerController::OFF;
int brightness=100;
int colorTemp=0;
void setup() {
// Initialize the LED as output
pinMode(LED_BUILTIN, OUTPUT);
// Initialize serial communication for debugging
Serial.begin(74880);
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID);
Serial.print("[WIFI] Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println();
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
// Initialize the Alexa client
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
// Register a new device with a unique ID and name
device1 = alexClient.getDevice("Test Lamp ESP", "ESP-01");
// Set the device's display category to LIGHT
device1->setDisplayCategory(DisplayCategory::LIGHT);
// Add a power controller capability to the device
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
// Add a brightness controller to the device to allow dimming
device1->addCapability(AlexaInterfaceType::BRIGHTNESS_CONTROLLER);
// Add a color temperature controller
device1->addCapability(AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER);
// Register event listener for "ReportState" directive
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.AddBrightnessControllerProp(brightness)
.AddColorTemperatureControllerProp(colorTemp)
.send();
});
// Register event listener for "Event" directive to handle state changes
device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
if (type == AlexaInterfaceType::POWER_CONTROLLER) {
if (directive["header"]["name"] == "TurnOn") {
outputState = PowerController::ON;
Serial.println("Turning ON");
} else if (directive["header"]["name"] == "TurnOff") {
outputState = PowerController::OFF;
Serial.println("Turning OFF");
}
}else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) {
if(directive["header"]["name"]=="SetBrightness"){
brightness=directive["payload"]["brightness"];
Serial.println("setting brightness to: "+brightness);
outputState = PowerController::ON;
}
}else if (type == AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER) {
if(directive["header"]["name"]=="SetColorTemperature"){
colorTemp=directive["payload"]["colorTemperatureInKelvin"];
Serial.println("setting color temperature to: "+colorTemp);
outputState = PowerController::ON;
}
}else{
return;
}
// Send the updated state after change
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.AddBrightnessControllerProp(brightness)
.AddColorTemperatureControllerProp(colorTemp)
.send();
});
}
void loop() {
// Process Alexa client communication
alexClient.loop();
// Update the state of the LED based on the power controller state and brightness
if(outputState == PowerController::ON){
analogWrite(LED_BUILTIN,map(brightness,0,100,255,0));
}else{
digitalWrite(LED_BUILTIN, true);
}
}
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Alex2ESP.h>
// Define constants for WiFi and Alexa Client configuration
const char *WIFI_SSID = "";
const char *ALEXA_USERNAME = "";
const char *ALEXA_PASSWORD = "";
const char *ALEXA_ROOT_TOPIC = "";
// Create the Alexa client object
Alex2ESP alexClient;
AlexaDevice* device1;
// Initial state for the light (PowerController)
PowerController outputState = PowerController::OFF;
int brightness=100;
int colorTemp=0;
void setup() {
// Initialize the LED as output
pinMode(LED_BUILTIN, OUTPUT);
// Initialize serial communication for debugging
Serial.begin(74880);
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID);
Serial.print("[WIFI] Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println();
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
// Initialize the Alexa client
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
// Register a new device with a unique ID and name
device1 = alexClient.getDevice("Test Lamp ESP", "ESP-01");
// Set the device's display category to LIGHT
device1->setDisplayCategory(DisplayCategory::LIGHT);
// Add a power controller capability to the device
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
// Add a brightness controller to the device to allow dimming
device1->addCapability(AlexaInterfaceType::BRIGHTNESS_CONTROLLER);
// Add a color temperature controller
device1->addCapability(AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER);
// Register event listener for "ReportState" directive
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.AddBrightnessControllerProp(brightness)
.AddColorTemperatureControllerProp(colorTemp)
.send();
});
// Register event listener for "Event" directive to handle state changes
device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
if (type == AlexaInterfaceType::POWER_CONTROLLER) {
if (directive["header"]["name"] == "TurnOn") {
outputState = PowerController::ON;
Serial.println("Turning ON");
} else if (directive["header"]["name"] == "TurnOff") {
outputState = PowerController::OFF;
Serial.println("Turning OFF");
}
}else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) {
if(directive["header"]["name"]=="SetBrightness"){
brightness=directive["payload"]["brightness"];
Serial.println("setting brightness to: "+brightness);
outputState = PowerController::ON;
}
}else if (type == AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER) {
if(directive["header"]["name"]=="SetColorTemperature"){
colorTemp=directive["payload"]["colorTemperatureInKelvin"];
Serial.println("setting color temperature to: "+colorTemp);
outputState = PowerController::ON;
}
}else{
return;
}
// Send the updated state after change
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.AddBrightnessControllerProp(brightness)
.AddColorTemperatureControllerProp(colorTemp)
.send();
});
}
void loop() {
// Process Alexa client communication
alexClient.loop();
// Update the state of the LED based on the power controller state and brightness
if(outputState == PowerController::ON){
analogWrite(LED_BUILTIN,map(brightness,0,100,255,0));
}else{
digitalWrite(LED_BUILTIN, true);
}
}

View file

@ -1,67 +1,67 @@
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Alex2ESP.h>
// Define constants for WiFi and Alexa Client configuration
const char *WIFI_SSID = "";
const char *ALEXA_USERNAME = "";
const char *ALEXA_PASSWORD = "";
const char *ALEXA_ROOT_TOPIC = "";
// Create the Alexa client object
Alex2ESP alexClient;
AlexaDevice* device1;
void setup() {
// Initialize serial communication for debugging
Serial.begin(74880);
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID);
Serial.print("[WIFI] Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println();
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
// Initialize the Alexa client
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
// Register a new device with a unique ID and name
device1 = alexClient.getDevice("ESP Temp", "ESP-01");
// Set the device's display category to TEMPERATURE_SENSOR
device1->setDisplayCategory(DisplayCategory::TEMPERATURE_SENSOR);
// Add a temperature sensor capability to the device
device1->addCapability(AlexaInterfaceType::TEMPERATURE_SENSOR);
// Register event listener for "ReportState" directive
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddTemperatureSensorProp(TemperatureSensorScale::FAHRENHEIT,69)
.send();
});
}
void loop() {
// Process Alexa client communication
alexClient.loop();
}
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
#include <Alex2ESP.h>
// Define constants for WiFi and Alexa Client configuration
const char *WIFI_SSID = "";
const char *ALEXA_USERNAME = "";
const char *ALEXA_PASSWORD = "";
const char *ALEXA_ROOT_TOPIC = "";
// Create the Alexa client object
Alex2ESP alexClient;
AlexaDevice* device1;
void setup() {
// Initialize serial communication for debugging
Serial.begin(74880);
// Connect to Wi-Fi
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID);
Serial.print("[WIFI] Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(100);
}
Serial.println();
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
// Initialize the Alexa client
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
// Register a new device with a unique ID and name
device1 = alexClient.getDevice("ESP Temp", "ESP-01");
// Set the device's display category to TEMPERATURE_SENSOR
device1->setDisplayCategory(DisplayCategory::TEMPERATURE_SENSOR);
// Add a temperature sensor capability to the device
device1->addCapability(AlexaInterfaceType::TEMPERATURE_SENSOR);
// Register event listener for "ReportState" directive
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddTemperatureSensorProp(TemperatureSensorScale::FAHRENHEIT,69)
.send();
});
}
void loop() {
// Process Alexa client communication
alexClient.loop();
}

View file

@ -1,7 +1,7 @@
#######################################
# Syntax Coloring Map For Espalexa
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
#######################################
# Syntax Coloring Map For Espalexa
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################

View file

@ -1,59 +0,0 @@
{
"name": "Alex2ESP",
"version": "1.0.0",
"description": "A companion library for the Alex2MQTT Alexa Skill. Connects ESP-based devices to alex2mqtt.stormysdream.club for seamless integration with MQTT.",
"keywords": [
"alexa",
"mqtt",
"esp",
"voice assistant",
"home automation"
],
"repository": {
"type": "git",
"url": "https://github.com/chaos511/Alex2ESP"
},
"authors": [
{
"name": "David",
"email": "Alex2ESP@stormysdream.club",
"maintainer": true
}
],
"license": "MIT",
"frameworks": [
"arduino"
],
"platforms": [
"espressif8266",
"espressif32"
],
"examples": [
"examples/*"
],
"dependencies": [
{
"owner": "marvinroger",
"name": "AsyncMqttClient",
"version": "^0.9.0"
},
{
"owner":"me-no-dev",
"name": "ESPAsyncTCP",
"version": ">=1.2.2",
"platforms": "espressif8266"
},
{
"owner":"me-no-dev",
"name": "AsyncTCP",
"version": ">=1.1.1",
"platforms": "espressif32"
},
{
"owner": "bblanchon",
"name": "ArduinoJson",
"version": "^7.2.1"
}
],
"headers": "Alex2ESP"
}

View file

@ -1,10 +0,0 @@
name=Alex2ESP
version=1.0.0
author=David Alex2ESP@stormysdream.club
maintainer=David Alex2ESP@stormysdream.club
sentence=A companion library for the Alex2MQTT Alexa Skill.
paragraph=Connects ESP-based devices to alex2mqtt.stormysdream.club, enabling seamless integration with MQTT and Alexa Voice Service.
category=Communication
url=https://github.com/chaos511/Alex2ESP
architectures=esp8266,esp32
includes=Alex2ESP.h

398
readme.md
View file

@ -1,199 +1,199 @@
# Alex2ESP
Alex2ESP is a lightweight Arduino/PlatformIO library for integrating ESP8266 and ESP32 microcontrollers with Amazon Alexa smart home APIs. The library simplifies the process of creating Alexa-compatible devices using MQTT.
For more details on how to configure Alex2ESP, visit [Alex2MQTT Documentation](https://alex2mqtt.stormysdream.club/).
---
## Features
- Supports "All" Alexa smart home capabilities.
- Provides action mapping for advanced directive customization including Open,Close,Raise,and Lower commands.
- Allows custom JSON injection in both discovery and state reporting to allow for unsupported devices.
- Event-driven architecture for handling Alexa directives.
---
## Alternatives
one of the most popular library for controlling esp like devices using alexa is [FauxmoESP](https://github.com/vintlabs/fauxmoESP)
however FauxmoESP works by emulating a light bulb so it is limited in what commands it accepts.
---
## Quick Start
### Installation
Download the library as zip or via PlatformIO and include it in your project:
```cpp
#include <Alex2ESP.h>
```
---
## Creating a Basic Device
### Example: Light Control
Here’s how to create a simple device that controls a light:
First create an instance of the alexa client and initilize it with your MQTT Credentials
```cpp
Alex2ESP alexClient;
```
Credentials can be found at https://alex2mqtt.stormysdream.club/ after logging in with amazon.
```cpp
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
```
Once initilized you can begin to add virtual devices, in this example we add a power controller to toggle a light
```cpp
AlexaDevice* device1 = alexClient.getDevice("Test Lamp", "ESP-01");
device1->setDisplayCategory(DisplayCategory::LIGHT);
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.send();
});
// Register event listener for "Event" directive to handle state changes
device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
if (type == AlexaInterfaceType::POWER_CONTROLLER) {
if (directive["header"]["name"] == "TurnOn") {
outputState = PowerController::ON;
Serial.println("Turning ON");
} else if (directive["header"]["name"] == "TurnOff") {
outputState = PowerController::OFF;
Serial.println("Turning OFF");
}
// Send the updated state after change
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.send();
}
});
```
---
### Example: Toggle Controller for Blinds
Alex2ESP also supports action mapping so we can map keywords such as "open" and "close" to a toggle controller
For a comprehensive list of Alexa actions and mappings, visit the [Alexa Developer Documentation](https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery-objects.html#action-mapping)
```cpp
AlexaDevice* device = alexClient.getDevice("Bedroom Blinds", "ESP-01");
device->setDisplayCategory(DisplayCategory::LIGHT);
AlexaInterface* toggleController = device->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER);
toggleController->setInstance("ESP-01.Toggle");
toggleController->addFriendlyName("Bedroom Blinds", "en-US");
ActionMapping closeMapping({AlexaActions::Close}, "TurnOn");
ActionMapping openMapping({AlexaActions::Open}, "TurnOff");
toggleController->addActionMapping(closeMapping);
toggleController->addActionMapping(openMapping);
```
---
## Interface Types
| Alexa Interface Type | Status |
|-----------------------------------------------------|-------------|
| AlexaInterfaceType::ENDPOINT_HEALTH | Fully Supported |
| AlexaInterfaceType::POWER_CONTROLLER | Fully Supported |
| AlexaInterfaceType::BRIGHTNESS_CONTROLLER | Fully Supported |
| AlexaInterfaceType::TOGGLE_CONTROLLER | Fully Supported |
| AlexaInterfaceType::TEMPERATURE_SENSOR | Fully Supported |
| AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER | Fully Supported |
| AlexaInterfaceType::AUTOMATION_MANAGEMENT | Supported* |
| AlexaInterfaceType::CHANNEL_CONTROLLER | Supported* |
| AlexaInterfaceType::COLOR_CONTROLLER | Supported* |
| AlexaInterfaceType::CONTACT_SENSOR | Supported* |
| AlexaInterfaceType::APPLICATION_STATE_REPORTER | Supported* |
| AlexaInterfaceType::AUDIO_PLAY_QUEUE | Supported* |
| AlexaInterfaceType::AUTHORIZATION_CONTROLLER | Supported* |
| AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA | Supported* |
| AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER | Supported* |
| AlexaInterfaceType::CAMERA_STREAM_CONTROLLER | Supported* |
| AlexaInterfaceType::COMMISSIONABLE | Supported* |
| AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER | Supported* |
| AlexaInterfaceType::COOKING | Supported* |
| AlexaInterfaceType::DATA_CONTROLLER | Supported* |
| AlexaInterfaceType::DEVICE_USAGE_ESTIMATION | Supported* |
| AlexaInterfaceType::DEVICE_USAGE_METER | Supported* |
| AlexaInterfaceType::DOORBELL_EVENT_SOURCE | Supported* |
| AlexaInterfaceType::EQUALIZER_CONTROLLER | Supported* |
| AlexaInterfaceType::INPUT_CONTROLLER | Supported* |
| AlexaInterfaceType::INVENTORY_LEVEL_SENSOR | Supported* |
| AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR | Supported* |
| AlexaInterfaceType::INVENTORY_USAGE_SENSOR | Supported* |
| AlexaInterfaceType::KEYPAD_CONTROLLER | Supported* |
| AlexaInterfaceType::LAUNCHER | Supported* |
| AlexaInterfaceType::LOCK_CONTROLLER | Supported* |
| AlexaInterfaceType::MEDIA_PLAYBACK | Supported* |
| AlexaInterfaceType::MEDIA_SEARCH | Supported* |
| AlexaInterfaceType::MODE_CONTROLLER | Supported* |
| AlexaInterfaceType::MOTION_SENSOR | Supported* |
| AlexaInterfaceType::PERCENTAGE_CONTROLLER | Supported* |
| AlexaInterfaceType::PLAYBACK_CONTROLLER | Supported* |
| AlexaInterfaceType::PLAYBACK_STATE_REPORTER | Supported* |
| AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE | Supported* |
| AlexaInterfaceType::RANGE_CONTROLLER | Supported* |
| AlexaInterfaceType::RECORD_CONTROLLER | Supported* |
| AlexaInterfaceType::REMOTE_VIDEO_PLAYER | Supported* |
| AlexaInterfaceType::RTC_SESSION_CONTROLLER | Supported* |
| AlexaInterfaceType::SCENE_CONTROLLER | Supported* |
| AlexaInterfaceType::SECURITY_PANEL_CONTROLLER | Supported* |
| AlexaInterfaceType::SEEK_CONTROLLER | Supported* |
| AlexaInterfaceType::SIMPLE_EVENT_SOURCE | Supported* |
| AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR | Supported* |
| AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER | Supported* |
| AlexaInterfaceType::SPEAKER | Supported* |
| AlexaInterfaceType::STEP_SPEAKER | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE | Supported* |
| AlexaInterfaceType::TIME_HOLD_CONTROLLER | Supported* |
| AlexaInterfaceType::UI_CONTROLLER | Supported* |
| AlexaInterfaceType::USER_PREFERENCE | Supported* |
| AlexaInterfaceType::VIDEO_RECORDER | Supported* |
| AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER | Supported* |
(*Partial support or limited implementation advanced configuration is required)
---
## Advanced ussage
For devices with limited or partial support, Alex2ESP provides functions that allow you to attach custom JSON objects to the status report. For example, to report the status of a PowerController type, you can use the `AddPowerControllerProp` function. However, if a specific "add props" function does not exist for your use case, you can utilize the `AddContextProp` function to pass a custom JSON object.
For instance, to manually report the state of a PowerController, you can use the following code:
```cpp
JsonDocument doc;
doc["namespace"] = "Alexa.PowerController";
doc["name"] = "powerState";
doc["value"] = "ON";
doc["timeOfSample"] = "{REPLACE_WITH_DATETIME}"; // Alex2MQTT server wil do the replace
doc["uncertaintyInMilliseconds"] = 0;
AddContextProp(doc.as<JsonObject>())
```
---
## Contributing
Feel free to submit pull requests or issues for feature requests and bug fixes.
---
## License
This project is licensed under the MIT License. See the LICENSE file for details.
# Alex2ESP
Alex2ESP is a lightweight Arduino/PlatformIO library for integrating ESP8266 and ESP32 microcontrollers with Amazon Alexa smart home APIs. The library simplifies the process of creating Alexa-compatible devices using MQTT.
For more details on how to configure Alex2ESP, visit [Alex2MQTT Documentation](https://alex2mqtt.stormysdream.club/).
---
## Features
- Supports "All" Alexa smart home capabilities.
- Provides action mapping for advanced directive customization including Open,Close,Raise,and Lower commands.
- Allows custom JSON injection in both discovery and state reporting to allow for unsupported devices.
- Event-driven architecture for handling Alexa directives.
---
## Alternatives
one of the most popular library for controlling esp like devices using alexa is [FauxmoESP](https://github.com/vintlabs/fauxmoESP)
however FauxmoESP works by emulating a light bulb so it is limited in what commands it accepts.
---
## Quick Start
### Installation
Download the library as zip or via PlatformIO and include it in your project:
```cpp
#include <Alex2ESP.h>
```
---
## Creating a Basic Device
### Example: Light Control
Here’s how to create a simple device that controls a light:
First create an instance of the alexa client and initilize it with your MQTT Credentials
```cpp
Alex2ESP alexClient;
```
Credentials can be found at https://alex2mqtt.stormysdream.club/ after logging in with amazon.
```cpp
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
```
Once initilized you can begin to add virtual devices, in this example we add a power controller to toggle a light
```cpp
AlexaDevice* device1 = alexClient.getDevice("Test Lamp", "ESP-01");
device1->setDisplayCategory(DisplayCategory::LIGHT);
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
// Build and send status report
device1->buildStatusMessage(directive["header"]["correlationToken"])
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.send();
});
// Register event listener for "Event" directive to handle state changes
device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) {
if (type == AlexaInterfaceType::POWER_CONTROLLER) {
if (directive["header"]["name"] == "TurnOn") {
outputState = PowerController::ON;
Serial.println("Turning ON");
} else if (directive["header"]["name"] == "TurnOff") {
outputState = PowerController::OFF;
Serial.println("Turning OFF");
}
// Send the updated state after change
device1->buildStatusMessage(directive["header"]["correlationToken"], true)
.AddHealthProp(EndpointHealth::OK)
.AddPowerControllerProp(outputState)
.send();
}
});
```
---
### Example: Toggle Controller for Blinds
Alex2ESP also supports action mapping so we can map keywords such as "open" and "close" to a toggle controller
For a comprehensive list of Alexa actions and mappings, visit the [Alexa Developer Documentation](https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery-objects.html#action-mapping)
```cpp
AlexaDevice* device = alexClient.getDevice("Bedroom Blinds", "ESP-01");
device->setDisplayCategory(DisplayCategory::LIGHT);
AlexaInterface* toggleController = device->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER);
toggleController->setInstance("ESP-01.Toggle");
toggleController->addFriendlyName("Bedroom Blinds", "en-US");
ActionMapping closeMapping({AlexaActions::Close}, "TurnOn");
ActionMapping openMapping({AlexaActions::Open}, "TurnOff");
toggleController->addActionMapping(closeMapping);
toggleController->addActionMapping(openMapping);
```
---
## Interface Types
| Alexa Interface Type | Status |
|-----------------------------------------------------|-------------|
| AlexaInterfaceType::ENDPOINT_HEALTH | Fully Supported |
| AlexaInterfaceType::POWER_CONTROLLER | Fully Supported |
| AlexaInterfaceType::BRIGHTNESS_CONTROLLER | Fully Supported |
| AlexaInterfaceType::TOGGLE_CONTROLLER | Fully Supported |
| AlexaInterfaceType::TEMPERATURE_SENSOR | Fully Supported |
| AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER | Fully Supported |
| AlexaInterfaceType::AUTOMATION_MANAGEMENT | Supported* |
| AlexaInterfaceType::CHANNEL_CONTROLLER | Supported* |
| AlexaInterfaceType::COLOR_CONTROLLER | Supported* |
| AlexaInterfaceType::CONTACT_SENSOR | Supported* |
| AlexaInterfaceType::APPLICATION_STATE_REPORTER | Supported* |
| AlexaInterfaceType::AUDIO_PLAY_QUEUE | Supported* |
| AlexaInterfaceType::AUTHORIZATION_CONTROLLER | Supported* |
| AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA | Supported* |
| AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER | Supported* |
| AlexaInterfaceType::CAMERA_STREAM_CONTROLLER | Supported* |
| AlexaInterfaceType::COMMISSIONABLE | Supported* |
| AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER | Supported* |
| AlexaInterfaceType::COOKING | Supported* |
| AlexaInterfaceType::DATA_CONTROLLER | Supported* |
| AlexaInterfaceType::DEVICE_USAGE_ESTIMATION | Supported* |
| AlexaInterfaceType::DEVICE_USAGE_METER | Supported* |
| AlexaInterfaceType::DOORBELL_EVENT_SOURCE | Supported* |
| AlexaInterfaceType::EQUALIZER_CONTROLLER | Supported* |
| AlexaInterfaceType::INPUT_CONTROLLER | Supported* |
| AlexaInterfaceType::INVENTORY_LEVEL_SENSOR | Supported* |
| AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR | Supported* |
| AlexaInterfaceType::INVENTORY_USAGE_SENSOR | Supported* |
| AlexaInterfaceType::KEYPAD_CONTROLLER | Supported* |
| AlexaInterfaceType::LAUNCHER | Supported* |
| AlexaInterfaceType::LOCK_CONTROLLER | Supported* |
| AlexaInterfaceType::MEDIA_PLAYBACK | Supported* |
| AlexaInterfaceType::MEDIA_SEARCH | Supported* |
| AlexaInterfaceType::MODE_CONTROLLER | Supported* |
| AlexaInterfaceType::MOTION_SENSOR | Supported* |
| AlexaInterfaceType::PERCENTAGE_CONTROLLER | Supported* |
| AlexaInterfaceType::PLAYBACK_CONTROLLER | Supported* |
| AlexaInterfaceType::PLAYBACK_STATE_REPORTER | Supported* |
| AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE | Supported* |
| AlexaInterfaceType::RANGE_CONTROLLER | Supported* |
| AlexaInterfaceType::RECORD_CONTROLLER | Supported* |
| AlexaInterfaceType::REMOTE_VIDEO_PLAYER | Supported* |
| AlexaInterfaceType::RTC_SESSION_CONTROLLER | Supported* |
| AlexaInterfaceType::SCENE_CONTROLLER | Supported* |
| AlexaInterfaceType::SECURITY_PANEL_CONTROLLER | Supported* |
| AlexaInterfaceType::SEEK_CONTROLLER | Supported* |
| AlexaInterfaceType::SIMPLE_EVENT_SOURCE | Supported* |
| AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR | Supported* |
| AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER | Supported* |
| AlexaInterfaceType::SPEAKER | Supported* |
| AlexaInterfaceType::STEP_SPEAKER | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE | Supported* |
| AlexaInterfaceType::TIME_HOLD_CONTROLLER | Supported* |
| AlexaInterfaceType::UI_CONTROLLER | Supported* |
| AlexaInterfaceType::USER_PREFERENCE | Supported* |
| AlexaInterfaceType::VIDEO_RECORDER | Supported* |
| AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER | Supported* |
(*Partial support or limited implementation advanced configuration is required)
---
## Advanced ussage
For devices with limited or partial support, Alex2ESP provides functions that allow you to attach custom JSON objects to the status report. For example, to report the status of a PowerController type, you can use the `AddPowerControllerProp` function. However, if a specific "add props" function does not exist for your use case, you can utilize the `AddContextProp` function to pass a custom JSON object.
For instance, to manually report the state of a PowerController, you can use the following code:
```cpp
JsonDocument doc;
doc["namespace"] = "Alexa.PowerController";
doc["name"] = "powerState";
doc["value"] = "ON";
doc["timeOfSample"] = "{REPLACE_WITH_DATETIME}"; // Alex2MQTT server wil do the replace
doc["uncertaintyInMilliseconds"] = 0;
AddContextProp(doc.as<JsonObject>())
```
---
## Contributing
Feel free to submit pull requests or issues for feature requests and bug fixes.
---
## License
This project is licensed under the MIT License. See the LICENSE file for details.

View file

@ -1,389 +1,389 @@
/*
* @title Alex2ESP Library
* @version 1.0.0
* @author David
* @license MIT
* @contributors chaos511
*
* @description The Alex2ESP library is a companion to the Alex2MQTT Alexa Skill,
* providing seamless integration between ESP-based devices and the Alex2MQTT server.
* This library connects to alex2mqtt.stormysdream.club, where the skill is hosted,
* allowing your devices to communicate effortlessly with the Alexa Voice Service
* using MQTT as the backbone.
*/
#include "Alex2ESP.h"
Alex2ESP::Alex2ESP()
: rootTopic(), mqttUsername(nullptr), mqttPassword(nullptr), _state(Alex2ESPState::UNINITIALIZED), _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {}
void Alex2ESP::begin(const char *rootTopic, const char *username, const char *password)
{
_state = Alex2ESPState::INITIALIZED;
_disconnectReason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED;
this->rootTopic = rootTopic;
this->mqttUsername = username;
this->mqttPassword = password;
discoverTopic = (String(this->rootTopic) + String("/discover"));
discoverTopicSend = (String(this->rootTopic) + String("/discover_r"));
TopicESP = (String(this->rootTopic) + String("/+/alexaDirective_e"));
// Log the root topic and credentials
AlexaUtils::log("Setting root topic: ");
AlexaUtils::log(rootTopic);
AlexaUtils::log(" with creds ");
AlexaUtils::log(username);
AlexaUtils::log(":");
AlexaUtils::logln(password);
AlexaUtils::printMemoryInfo();
// Set up MQTT client callbacks
mqttClient.onConnect([this](bool sessionPresent)
{ this->onMqttConnect(sessionPresent); });
mqttClient.onDisconnect([this](AsyncMqttClientDisconnectReason reason)
{ this->onMqttDisconnect(reason); });
mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos)
{ this->onSubscribe(packetId, qos); });
mqttClient.onMessage([this](char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{ this->onMessage(topic, payload, properties, len, index, total); });
// Configure MQTT client
mqttClient.setServer(mqttServer, mqttPort);
mqttClient.setCredentials(username, password);
// TODO: Throw error if dns fails or has no internet access?
_state = Alex2ESPState::CONNECTING;
mqttClient.connect();
}
void Alex2ESP::onMqttConnect(bool sessionPresent)
{
_state = Alex2ESPState::SUBSCRIBING;
AlexaUtils::log("Connected to MQTT server, Now subscribing to: ");
AlexaUtils::log(discoverTopic.c_str());
AlexaUtils::log(" ");
AlexaUtils::logln(TopicESP.c_str());
mqttClient.subscribe(discoverTopic.c_str(), 1);
mqttClient.subscribe(TopicESP.c_str(), 1);
}
void Alex2ESP::onSubscribe(uint16_t packetId, uint8_t qos)
{
_state = Alex2ESPState::CONNECTED;
AlexaUtils::log("Subscribed with packetId: ");
AlexaUtils::log(packetId);
AlexaUtils::log(" and QoS: ");
AlexaUtils::logln(qos);
}
// Internal: Handle disconnection
// TODO: auto reconnect?
void Alex2ESP::onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
_state = Alex2ESPState::DISCONNECTED;
_disconnectReason = reason;
}
void Alex2ESP::onMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total)
{
AlexaUtils::logln("OnMessage Start");
AlexaUtils::printMemoryInfo();
clearToSend = false;
if (strcmp(topic, discoverTopic.c_str()) == 0)
{
// boolean successful = messagePackExtractor(topic, payload, length, index, total);
// if (successful && inputDoc["name"] == "Discover")
// {
for (const AlexaDevice &device : devices)
{
AlexaUtils::logln("Getting device json");
JsonDocument jsonData = device.getDeviceJSON();
String jsonString;
serializeJson(jsonData, jsonString);
jsonData.clear();
AlexaUtils::enqueue(jsonString.c_str(), discoverTopicSend.c_str());
}
// }
}
else
{
for (auto &device : devices)
{
String directiveTopic = String(rootTopic) + "/" + device.getEndpointId() + "/alexaDirective_e";
if (strcmp(topic, directiveTopic.c_str()) == 0)
{
AlexaUtils::logln(topic);
payload[length] = '\0';
AlexaUtils::logln(payload);
AlexaUtils::enqueueReceive(payload);
}
}
}
AlexaUtils::logln("OnMessage End");
AlexaUtils::printMemoryInfo();
clearToSend = true;
}
// boolean Alex2ESP::messagePackExtractor(char *topic, char *payload, size_t length, size_t index, size_t total)
// {
// payload[length] = '\0'; // Truncate the payload
// uint8_t messageId = payload[0]; // Message ID (byte 0)
// uint8_t fragmentId = payload[1]; // Fragment ID (byte 1)
// uint8_t totalFragments = payload[2]; // Total Fragments (byte 2)
// if (totalFragments > MAX_FRAGMENT_COUNT)
// {
// return false; //Message too long, skip.
// }
// if (fragmentId == 0)
// {
// clearCache();
// cache.totalParts = totalFragments;
// cache.messageId = String(messageId);
// }
// if (cache.messageId != String(messageId))
// {
// clearCache();
// cache.messageId = String(messageId);
// cache.totalParts = totalFragments;
// }
// if (fragmentId < cache.totalParts)
// {
// strncpy(cache.message + cache.fragmentOffset, payload + 3, length - 3); // Copy the fragment data
// cache.fragmentOffset=cache.fragmentOffset+(length-3); //
// cache.receivedParts++;
// Serial.printf("Received fragment %d of %d, length %d receivedParts: %d \n", fragmentId + 1, totalFragments, length, cache.receivedParts);
// if (isMessageComplete())
// {
// AlexaUtils::logln("Full message received.");
// AlexaUtils::logln(cache.message);
// DeserializationError error = deserializeJson(inputDoc, cache.message);
// if (error)
// {
// AlexaUtils::log("Failed to parse message: ");
// AlexaUtils::logln(String(error.f_str()));
// clearCache();
// return false;
// }
// else
// {
// AlexaUtils::logln("JSON message parsed successfully!");
// clearCache(true);
// return true;
// }
// }
// }
// return false;
// }
AlexaDevice *Alex2ESP::getDevice(const String &name, const String &endpointId)
{
// Check if a device with the given endpointId already exists
for (auto &device : devices)
{
if (device.getEndpointId() == endpointId)
{
return &device; // Return the existing device
}
}
// If the device doesn't exist, create a new one
devices.emplace_back(name, rootTopic, endpointId);
Serial.print("Created new device: ");
Serial.print(name);
Serial.print(" with endpointId: ");
Serial.println(endpointId);
// Return a pointer to the newly created device
return &devices.back();
}
// void Alex2ESP::clearCache(boolean skipDocClear)
// {
// if (!skipDocClear)
// {
// inputDoc.clear();
// }
// cache.receivedParts = 0; // Reset the count of received parts
// cache.messageId = ""; // Clear the message ID
// cache.totalParts = 0; // Reset the total parts count
// cache.fragmentOffset = 0;
// memset(cache.message, 0, sizeof(cache.message)); // Clear the message buffer
// }
// bool Alex2ESP::isMessageComplete()
// {
// return cache.receivedParts == cache.totalParts; // Check if we have received all parts
// }
Alex2ESPState Alex2ESP::getState() const
{
return _state;
}
AsyncMqttClientDisconnectReason Alex2ESP::getDisconnectReason() const
{
return _disconnectReason;
}
void Alex2ESP::handleMqttReconnection()
{
if (!mqttClient.connected() && _disconnectReason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED)
{
if (millis() - lastReconnectTime > 5000)
{
lastReconnectTime = millis();
reconnectAttempt++;
mqttClient.connect();
}
}
}
void Alex2ESP::processHttpPost()
{
if (!AlexaUtils::isQueueEmpty())
{
AlexaUtils::logln("processHttpPost");
const char *payload = AlexaUtils::dequeueVals(false);
httpPOST.begin(wifiPOST, "http://alex2mqtt.stormysdream.club/Alex2ESP");
httpPOST.setAuthorization(mqttUsername, mqttPassword);
httpPOST.addHeader("Content-Type", "text/plain");
AlexaUtils::log("Sending Data: ");
AlexaUtils::logln(payload);
int httpResponseCode = httpPOST.POST(payload);
if (httpResponseCode != 200)
{
if (retryCountPOST >= MAX_RETRY_COUNT)
{
AlexaUtils::dequeueVals(true);
}
AlexaUtils::log("POST Error code: ");
AlexaUtils::logln(httpResponseCode);
retryCountPOST++;
if (httpResponseCode > 0)
{
String response = httpPOST.getString();
Serial.println("Response:");
Serial.println(response);
}
}
else
{
retryCountPOST = 0;
AlexaUtils::dequeueVals(true);
}
httpPOST.end();
}
}
void Alex2ESP::processHttpGet()
{
if (!AlexaUtils::isReceiveQueueEmpty())
{
AlexaUtils::logln("processHttpGet");
String uuid;
AlexaUtils::dequeueReceive(uuid, false);
AlexaUtils::log("Sending get request for uuid: ");
// AlexaUtils::logln(uuid);
httpGET.begin(wifiGET, "http://alex2mqtt.stormysdream.club/Alex2ESP/" + uuid);
httpGET.setAuthorization(mqttUsername, mqttPassword);
int httpResponseCode = httpGET.GET();
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
if (httpResponseCode != 200)
{
Serial.print("Error code: ");
Serial.println(httpResponseCode);
if (retryCountGET >= MAX_RETRY_COUNT)
{
AlexaUtils::dequeueReceive(uuid, true);
}
retryCountGET++;
}
else
{
retryCountGET = 0;
AlexaUtils::dequeueReceive(uuid, true);
String payloadStr = httpGET.getString();
size_t length = payloadStr.length();
if (length < AlexaUtils::MAX_PAYLOAD_LENGTH - 1)
{
payloadStr.toCharArray(AlexaUtils::receivePayload, length + 1);
payloadStr = "";
AlexaUtils::receivePayload[length + 1] = '\0';
// AlexaUtils::logln(AlexaUtils::receivePayload);
DeserializationError error = deserializeJson(inputDoc, AlexaUtils::receivePayload);
if (error)
{
AlexaUtils::log("Failed to parse message: ");
AlexaUtils::logln(String(error.f_str()));
inputDoc.clear();
}
else
{
AlexaUtils::logln("JSON message parsed successfully!");
for (auto &device : devices)
{
if (strcmp(device.getEndpointId().c_str(), inputDoc["directive"]["endpoint"]["endpointId"]) == 0)
{
serializeJson(inputDoc, Serial);
Serial.println();
device.triggerEvent("DirectiveReceived", inputDoc["directive"], AlexaInterfaceType::UNKNOWN);
if (inputDoc["directive"]["header"]["name"] == "ReportState")
{
device.triggerEvent("ReportState", inputDoc["directive"], AlexaInterfaceType::UNKNOWN);
}
else
{
device.triggerEvent("Event", inputDoc["directive"], AlexaInterfaceUtils::fromString(inputDoc["directive"]["header"]["namespace"]));
}
}
}
}
}
}
httpGET.end();
}
}
void Alex2ESP::loop()
{
handleMqttReconnection();
if(clearToSend){
processHttpPost();
processHttpGet();
}
return;
/*
* @title Alex2ESP Library
* @version 1.0.0
* @author David
* @license MIT
* @contributors chaos511
*
* @description The Alex2ESP library is a companion to the Alex2MQTT Alexa Skill,
* providing seamless integration between ESP-based devices and the Alex2MQTT server.
* This library connects to alex2mqtt.stormysdream.club, where the skill is hosted,
* allowing your devices to communicate effortlessly with the Alexa Voice Service
* using MQTT as the backbone.
*/
#include "Alex2ESP.h"
Alex2ESP::Alex2ESP()
: rootTopic(), mqttUsername(nullptr), mqttPassword(nullptr), _state(Alex2ESPState::UNINITIALIZED), _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {}
void Alex2ESP::begin(const char *rootTopic, const char *username, const char *password)
{
_state = Alex2ESPState::INITIALIZED;
_disconnectReason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED;
this->rootTopic = rootTopic;
this->mqttUsername = username;
this->mqttPassword = password;
discoverTopic = (String(this->rootTopic) + String("/discover"));
discoverTopicSend = (String(this->rootTopic) + String("/discover_r"));
TopicESP = (String(this->rootTopic) + String("/+/alexaDirective_e"));
// Log the root topic and credentials
AlexaUtils::log("Setting root topic: ");
AlexaUtils::log(rootTopic);
AlexaUtils::log(" with creds ");
AlexaUtils::log(username);
AlexaUtils::log(":");
AlexaUtils::logln(password);
AlexaUtils::printMemoryInfo();
// Set up MQTT client callbacks
mqttClient.onConnect([this](bool sessionPresent)
{ this->onMqttConnect(sessionPresent); });
mqttClient.onDisconnect([this](AsyncMqttClientDisconnectReason reason)
{ this->onMqttDisconnect(reason); });
mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos)
{ this->onSubscribe(packetId, qos); });
mqttClient.onMessage([this](char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{ this->onMessage(topic, payload, properties, len, index, total); });
// Configure MQTT client
mqttClient.setServer(mqttServer, mqttPort);
mqttClient.setCredentials(username, password);
// TODO: Throw error if dns fails or has no internet access?
_state = Alex2ESPState::CONNECTING;
mqttClient.connect();
}
void Alex2ESP::onMqttConnect(bool sessionPresent)
{
_state = Alex2ESPState::SUBSCRIBING;
AlexaUtils::log("Connected to MQTT server, Now subscribing to: ");
AlexaUtils::log(discoverTopic.c_str());
AlexaUtils::log(" ");
AlexaUtils::logln(TopicESP.c_str());
mqttClient.subscribe(discoverTopic.c_str(), 1);
mqttClient.subscribe(TopicESP.c_str(), 1);
}
void Alex2ESP::onSubscribe(uint16_t packetId, uint8_t qos)
{
_state = Alex2ESPState::CONNECTED;
AlexaUtils::log("Subscribed with packetId: ");
AlexaUtils::log(packetId);
AlexaUtils::log(" and QoS: ");
AlexaUtils::logln(qos);
}
// Internal: Handle disconnection
// TODO: auto reconnect?
void Alex2ESP::onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
_state = Alex2ESPState::DISCONNECTED;
_disconnectReason = reason;
}
void Alex2ESP::onMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total)
{
AlexaUtils::logln("OnMessage Start");
AlexaUtils::printMemoryInfo();
clearToSend = false;
if (strcmp(topic, discoverTopic.c_str()) == 0)
{
// boolean successful = messagePackExtractor(topic, payload, length, index, total);
// if (successful && inputDoc["name"] == "Discover")
// {
for (const AlexaDevice &device : devices)
{
AlexaUtils::logln("Getting device json");
JsonDocument jsonData = device.getDeviceJSON();
String jsonString;
serializeJson(jsonData, jsonString);
jsonData.clear();
AlexaUtils::enqueue(jsonString.c_str(), discoverTopicSend.c_str());
}
// }
}
else
{
for (auto &device : devices)
{
String directiveTopic = String(rootTopic) + "/" + device.getEndpointId() + "/alexaDirective_e";
if (strcmp(topic, directiveTopic.c_str()) == 0)
{
AlexaUtils::logln(topic);
payload[length] = '\0';
AlexaUtils::logln(payload);
AlexaUtils::enqueueReceive(payload);
}
}
}
AlexaUtils::logln("OnMessage End");
AlexaUtils::printMemoryInfo();
clearToSend = true;
}
// boolean Alex2ESP::messagePackExtractor(char *topic, char *payload, size_t length, size_t index, size_t total)
// {
// payload[length] = '\0'; // Truncate the payload
// uint8_t messageId = payload[0]; // Message ID (byte 0)
// uint8_t fragmentId = payload[1]; // Fragment ID (byte 1)
// uint8_t totalFragments = payload[2]; // Total Fragments (byte 2)
// if (totalFragments > MAX_FRAGMENT_COUNT)
// {
// return false; //Message too long, skip.
// }
// if (fragmentId == 0)
// {
// clearCache();
// cache.totalParts = totalFragments;
// cache.messageId = String(messageId);
// }
// if (cache.messageId != String(messageId))
// {
// clearCache();
// cache.messageId = String(messageId);
// cache.totalParts = totalFragments;
// }
// if (fragmentId < cache.totalParts)
// {
// strncpy(cache.message + cache.fragmentOffset, payload + 3, length - 3); // Copy the fragment data
// cache.fragmentOffset=cache.fragmentOffset+(length-3); //
// cache.receivedParts++;
// Serial.printf("Received fragment %d of %d, length %d receivedParts: %d \n", fragmentId + 1, totalFragments, length, cache.receivedParts);
// if (isMessageComplete())
// {
// AlexaUtils::logln("Full message received.");
// AlexaUtils::logln(cache.message);
// DeserializationError error = deserializeJson(inputDoc, cache.message);
// if (error)
// {
// AlexaUtils::log("Failed to parse message: ");
// AlexaUtils::logln(String(error.f_str()));
// clearCache();
// return false;
// }
// else
// {
// AlexaUtils::logln("JSON message parsed successfully!");
// clearCache(true);
// return true;
// }
// }
// }
// return false;
// }
AlexaDevice *Alex2ESP::getDevice(const String &name, const String &endpointId)
{
// Check if a device with the given endpointId already exists
for (auto &device : devices)
{
if (device.getEndpointId() == endpointId)
{
return &device; // Return the existing device
}
}
// If the device doesn't exist, create a new one
devices.emplace_back(name, rootTopic, endpointId);
Serial.print("Created new device: ");
Serial.print(name);
Serial.print(" with endpointId: ");
Serial.println(endpointId);
// Return a pointer to the newly created device
return &devices.back();
}
// void Alex2ESP::clearCache(boolean skipDocClear)
// {
// if (!skipDocClear)
// {
// inputDoc.clear();
// }
// cache.receivedParts = 0; // Reset the count of received parts
// cache.messageId = ""; // Clear the message ID
// cache.totalParts = 0; // Reset the total parts count
// cache.fragmentOffset = 0;
// memset(cache.message, 0, sizeof(cache.message)); // Clear the message buffer
// }
// bool Alex2ESP::isMessageComplete()
// {
// return cache.receivedParts == cache.totalParts; // Check if we have received all parts
// }
Alex2ESPState Alex2ESP::getState() const
{
return _state;
}
AsyncMqttClientDisconnectReason Alex2ESP::getDisconnectReason() const
{
return _disconnectReason;
}
void Alex2ESP::handleMqttReconnection()
{
if (!mqttClient.connected() && _disconnectReason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED)
{
if (millis() - lastReconnectTime > 5000)
{
lastReconnectTime = millis();
reconnectAttempt++;
mqttClient.connect();
}
}
}
void Alex2ESP::processHttpPost()
{
if (!AlexaUtils::isQueueEmpty())
{
AlexaUtils::logln("processHttpPost");
const char *payload = AlexaUtils::dequeueVals(false);
httpPOST.begin(wifiPOST, "http://alex2mqtt.stormysdream.club/Alex2ESP");
httpPOST.setAuthorization(mqttUsername, mqttPassword);
httpPOST.addHeader("Content-Type", "text/plain");
AlexaUtils::log("Sending Data: ");
AlexaUtils::logln(payload);
int httpResponseCode = httpPOST.POST(payload);
if (httpResponseCode != 200)
{
if (retryCountPOST >= MAX_RETRY_COUNT)
{
AlexaUtils::dequeueVals(true);
}
AlexaUtils::log("POST Error code: ");
AlexaUtils::logln(httpResponseCode);
retryCountPOST++;
if (httpResponseCode > 0)
{
String response = httpPOST.getString();
Serial.println("Response:");
Serial.println(response);
}
}
else
{
retryCountPOST = 0;
AlexaUtils::dequeueVals(true);
}
httpPOST.end();
}
}
void Alex2ESP::processHttpGet()
{
if (!AlexaUtils::isReceiveQueueEmpty())
{
AlexaUtils::logln("processHttpGet");
String uuid;
AlexaUtils::dequeueReceive(uuid, false);
AlexaUtils::log("Sending get request for uuid: ");
// AlexaUtils::logln(uuid);
httpGET.begin(wifiGET, "http://alex2mqtt.stormysdream.club/Alex2ESP/" + uuid);
httpGET.setAuthorization(mqttUsername, mqttPassword);
int httpResponseCode = httpGET.GET();
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
if (httpResponseCode != 200)
{
Serial.print("Error code: ");
Serial.println(httpResponseCode);
if (retryCountGET >= MAX_RETRY_COUNT)
{
AlexaUtils::dequeueReceive(uuid, true);
}
retryCountGET++;
}
else
{
retryCountGET = 0;
AlexaUtils::dequeueReceive(uuid, true);
String payloadStr = httpGET.getString();
size_t length = payloadStr.length();
if (length < AlexaUtils::MAX_PAYLOAD_LENGTH - 1)
{
payloadStr.toCharArray(AlexaUtils::receivePayload, length + 1);
payloadStr = "";
AlexaUtils::receivePayload[length + 1] = '\0';
// AlexaUtils::logln(AlexaUtils::receivePayload);
DeserializationError error = deserializeJson(inputDoc, AlexaUtils::receivePayload);
if (error)
{
AlexaUtils::log("Failed to parse message: ");
AlexaUtils::logln(String(error.f_str()));
inputDoc.clear();
}
else
{
AlexaUtils::logln("JSON message parsed successfully!");
for (auto &device : devices)
{
if (strcmp(device.getEndpointId().c_str(), inputDoc["directive"]["endpoint"]["endpointId"]) == 0)
{
serializeJson(inputDoc, Serial);
Serial.println();
device.triggerEvent("DirectiveReceived", inputDoc["directive"], AlexaInterfaceType::UNKNOWN);
if (inputDoc["directive"]["header"]["name"] == "ReportState")
{
device.triggerEvent("ReportState", inputDoc["directive"], AlexaInterfaceType::UNKNOWN);
}
else
{
device.triggerEvent("Event", inputDoc["directive"], AlexaInterfaceUtils::fromString(inputDoc["directive"]["header"]["namespace"]));
}
}
}
}
}
}
httpGET.end();
}
}
void Alex2ESP::loop()
{
handleMqttReconnection();
if(clearToSend){
processHttpPost();
processHttpGet();
}
return;
}

View file

@ -1,104 +1,104 @@
/*
* @title Alex2ESP Library
* @version 1.0.0
* @author David
* @license MIT
* @contributors chaos511
*
* @description The Alex2ESP library is a companion to the Alex2MQTT Alexa Skill,
* providing seamless integration between ESP-based devices and the Alex2MQTT server.
* This library connects to alex2mqtt.stormysdream.club, where the skill is hosted,
* allowing your devices to communicate effortlessly with the Alexa Voice Service
* using MQTT as the backbone.
*/
#ifndef ALEX2ESP_H
#define ALEX2ESP_H
class AlexaDevice;
#include <Arduino.h>
#include <AsyncMqttClient.h>
#include <ArduinoJson.h>
#include "AlexaDevice.h"
#include <AlexaInterface.h>
#include <vector>
#include <AlexaUtils.h>
#include <ESP8266HTTPClient.h>
enum class Alex2ESPState
{
UNINITIALIZED,
INITIALIZED,
CONNECTING,
SUBSCRIBING,
CONNECTED,
DISCONNECTED
};
class Alex2ESP
{
public:
// Constructor
Alex2ESP();
// Begin function for initialization
void begin(const char *rootTopic, const char *username, const char *password);
Alex2ESPState getState() const;
void loop();
AsyncMqttClientDisconnectReason getDisconnectReason() const;
AlexaDevice *getDevice(const String &name, const String &endpointId);
void messageSplitAndSend(String messagepackString, String topic);
private:
static const int MAX_RETRY_COUNT = 2; // Define maximum retry count
int retryCountPOST=0;
int retryCountGET=0;
boolean clearToSend=false;
AsyncMqttClient mqttClient; // MQTT client instance
String rootTopic; // Root topic for communication
String discoverTopic; // The topic we listen on for discovery messages
String discoverTopicSend; // The topic we send discovery messages
String TopicESP; // The topic we listen on for esp messages
const char *mqttUsername; // Username for authentication
const char *mqttPassword; // Password for authentication
unsigned long lastReconnectTime;
int reconnectAttempt;
HTTPClient httpGET;
HTTPClient httpPOST;
WiFiClient wifiGET;
WiFiClient wifiPOST;
JsonDocument inputDoc;
std::vector<AlexaDevice> devices; // Collection of devices
Alex2ESPState _state;
AsyncMqttClientDisconnectReason _disconnectReason;
// MQTT connection details
const char *mqttServer = "alex2mqtt.stormysdream.club";
uint16_t mqttPort = 1883;
// Internal event handlers
void onMqttConnect(bool sessionPresent);
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason);
void onSubscribe(uint16_t packetId, uint8_t qos);
void onMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total);
//loop processing function
void handleMqttReconnection();
void processHttpPost();
void processHttpGet();
};
#endif // ALEX2ESP_H
/*
* @title Alex2ESP Library
* @version 1.0.0
* @author David
* @license MIT
* @contributors chaos511
*
* @description The Alex2ESP library is a companion to the Alex2MQTT Alexa Skill,
* providing seamless integration between ESP-based devices and the Alex2MQTT server.
* This library connects to alex2mqtt.stormysdream.club, where the skill is hosted,
* allowing your devices to communicate effortlessly with the Alexa Voice Service
* using MQTT as the backbone.
*/
#ifndef ALEX2ESP_H
#define ALEX2ESP_H
class AlexaDevice;
#include <Arduino.h>
#include <AsyncMqttClient.h>
#include <ArduinoJson.h>
#include "AlexaDevice.h"
#include <AlexaInterface.h>
#include <vector>
#include <AlexaUtils.h>
#include <ESP8266HTTPClient.h>
enum class Alex2ESPState
{
UNINITIALIZED,
INITIALIZED,
CONNECTING,
SUBSCRIBING,
CONNECTED,
DISCONNECTED
};
class Alex2ESP
{
public:
// Constructor
Alex2ESP();
// Begin function for initialization
void begin(const char *rootTopic, const char *username, const char *password);
Alex2ESPState getState() const;
void loop();
AsyncMqttClientDisconnectReason getDisconnectReason() const;
AlexaDevice *getDevice(const String &name, const String &endpointId);
void messageSplitAndSend(String messagepackString, String topic);
private:
static const int MAX_RETRY_COUNT = 2; // Define maximum retry count
int retryCountPOST=0;
int retryCountGET=0;
boolean clearToSend=false;
AsyncMqttClient mqttClient; // MQTT client instance
String rootTopic; // Root topic for communication
String discoverTopic; // The topic we listen on for discovery messages
String discoverTopicSend; // The topic we send discovery messages
String TopicESP; // The topic we listen on for esp messages
const char *mqttUsername; // Username for authentication
const char *mqttPassword; // Password for authentication
unsigned long lastReconnectTime;
int reconnectAttempt;
HTTPClient httpGET;
HTTPClient httpPOST;
WiFiClient wifiGET;
WiFiClient wifiPOST;
JsonDocument inputDoc;
std::vector<AlexaDevice> devices; // Collection of devices
Alex2ESPState _state;
AsyncMqttClientDisconnectReason _disconnectReason;
// MQTT connection details
const char *mqttServer = "alex2mqtt.stormysdream.club";
uint16_t mqttPort = 1883;
// Internal event handlers
void onMqttConnect(bool sessionPresent);
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason);
void onSubscribe(uint16_t packetId, uint8_t qos);
void onMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total);
//loop processing function
void handleMqttReconnection();
void processHttpPost();
void processHttpGet();
};
#endif // ALEX2ESP_H

View file

@ -1,164 +1,164 @@
#include "AlexaDevice.h"
AlexaDevice::AlexaDevice(const String& name, const String& rootTopic, const String& endpointId)
: name(name), endpointId(endpointId), rootTopic(rootTopic) {
// Initialize event arrays to nullptr
for (int i = 0; i < MAX_EVENTS; ++i) {
eventNames[i] = nullptr; // Set all event names to nullptr
eventCallbacks[i] = nullptr; // Set all callbacks to nullptr
}
}
// Setters and Getters
void AlexaDevice::setName(const String& name) {
this->name = name;
}
String AlexaDevice::getName() const {
return name;
}
String AlexaDevice::getEndpointId() const {
return endpointId;
}
DisplayCategory AlexaDevice::getDisplayCategory() const {
return displayCategory;
}
void AlexaDevice::setDisplayCategory(DisplayCategory category) {
displayCategory = category;
}
String AlexaDevice::getDescription() const {
return description;
}
void AlexaDevice::setDescription(const String& description) {
this->description = description;
}
String AlexaDevice::getManufacturerName() const {
return manufacturerName;
}
void AlexaDevice::setManufacturerName(const String& manufacturerName) {
this->manufacturerName = manufacturerName;
}
String AlexaDevice::getManufacturer() const {
return manufacturer;
}
void AlexaDevice::setManufacturer(const String& manufacturer) {
this->manufacturer = manufacturer;
}
String AlexaDevice::getModel() const {
return model;
}
void AlexaDevice::setModel(const String& model) {
this->model = model;
}
String AlexaDevice::getSoftwareVersion() const {
return softwareVersion;
}
AlexaInterface* AlexaDevice::addCapability(AlexaInterfaceType type) {
// Check if a interface with the given type already exists
for (auto& iface : capabilities) {
if (iface.getType() == type) {
return &iface; // Return the existing interface
}
}
// If the interface doesn't exist, create a new one
capabilities.emplace_back(type);
Serial.print("Created new interface with type: ");
Serial.println(capabilities.back().getTypeString());
// Return a pointer to the newly created device
return &capabilities.back();
}
JsonDocument AlexaDevice::getDeviceJSON() const {
JsonDocument json;
// Main device attributes
json["endpointId"] = endpointId;
json["friendlyName"] = name;
json["description"] = description;
json["manufacturerName"] = manufacturerName;
JsonArray displayCategories = json["displayCategories"].to<JsonArray>();
displayCategories.add(DisplayCategoryUtils::toString(displayCategory));
JsonObject additionalAttributes = json["additionalAttributes"].to<JsonObject>();
additionalAttributes["manufacturer"] = manufacturer;
additionalAttributes["model"] = model;
additionalAttributes["serialNumber"] = "ESP2Alex";
additionalAttributes["firmwareVersion"] = "1.0.0";
additionalAttributes["softwareVersion"] = softwareVersion;
additionalAttributes["customIdentifier"] = "ESP2Alex";
JsonArray capabilitiesArray = json["capabilities"].to<JsonArray>();
for (AlexaInterface capability : capabilities) {
capabilitiesArray.add(capability.getJSON().as<JsonObject>());
}
// capabilitiesArray.add(capability.getJSON().as<JsonObject>());
return json;
}
// void AlexaDevice::registerEvent(const String& eventName, std::function<void(const JsonDocument&,const AlexaInterfaceType&)> callback) {
// eventCallbacks[eventName] = callback;
// }
// void AlexaDevice::triggerEvent(const String& eventName, const JsonDocument& directive,const AlexaInterfaceType& type) const {
// auto it = eventCallbacks.find(eventName);
// if (it != eventCallbacks.end()) {
// // Call the registered callback function
// it->second(directive,type);
// } else {
// Serial.print("No event registered for: ");
// Serial.println(eventName);
// }
// }
// Register an event callback function
void AlexaDevice::registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&)) {
for (int i = 0; i < MAX_EVENTS; ++i) {
// Find an empty slot for the new event
if (eventNames[i] == nullptr) {
eventNames[i] = eventName; // Store the event name
eventCallbacks[i] = callback; // Store the callback function
break;
}
}
}
// Trigger the event and invoke the corresponding callback
void AlexaDevice::triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type) const {
for (int i = 0; i < MAX_EVENTS; ++i) {
if (eventNames[i] != nullptr && strcmp(eventNames[i], eventName) == 0) {
// Found the event name, call the corresponding callback function
eventCallbacks[i](directive, type);
return;
}
}
// If no event was found, print an error message
Serial.print("No event registered for: ");
Serial.println(eventName);
#include "AlexaDevice.h"
AlexaDevice::AlexaDevice(const String& name, const String& rootTopic, const String& endpointId)
: name(name), endpointId(endpointId), rootTopic(rootTopic) {
// Initialize event arrays to nullptr
for (int i = 0; i < MAX_EVENTS; ++i) {
eventNames[i] = nullptr; // Set all event names to nullptr
eventCallbacks[i] = nullptr; // Set all callbacks to nullptr
}
}
// Setters and Getters
void AlexaDevice::setName(const String& name) {
this->name = name;
}
String AlexaDevice::getName() const {
return name;
}
String AlexaDevice::getEndpointId() const {
return endpointId;
}
DisplayCategory AlexaDevice::getDisplayCategory() const {
return displayCategory;
}
void AlexaDevice::setDisplayCategory(DisplayCategory category) {
displayCategory = category;
}
String AlexaDevice::getDescription() const {
return description;
}
void AlexaDevice::setDescription(const String& description) {
this->description = description;
}
String AlexaDevice::getManufacturerName() const {
return manufacturerName;
}
void AlexaDevice::setManufacturerName(const String& manufacturerName) {
this->manufacturerName = manufacturerName;
}
String AlexaDevice::getManufacturer() const {
return manufacturer;
}
void AlexaDevice::setManufacturer(const String& manufacturer) {
this->manufacturer = manufacturer;
}
String AlexaDevice::getModel() const {
return model;
}
void AlexaDevice::setModel(const String& model) {
this->model = model;
}
String AlexaDevice::getSoftwareVersion() const {
return softwareVersion;
}
AlexaInterface* AlexaDevice::addCapability(AlexaInterfaceType type) {
// Check if a interface with the given type already exists
for (auto& iface : capabilities) {
if (iface.getType() == type) {
return &iface; // Return the existing interface
}
}
// If the interface doesn't exist, create a new one
capabilities.emplace_back(type);
Serial.print("Created new interface with type: ");
Serial.println(capabilities.back().getTypeString());
// Return a pointer to the newly created device
return &capabilities.back();
}
JsonDocument AlexaDevice::getDeviceJSON() const {
JsonDocument json;
// Main device attributes
json["endpointId"] = endpointId;
json["friendlyName"] = name;
json["description"] = description;
json["manufacturerName"] = manufacturerName;
JsonArray displayCategories = json["displayCategories"].to<JsonArray>();
displayCategories.add(DisplayCategoryUtils::toString(displayCategory));
JsonObject additionalAttributes = json["additionalAttributes"].to<JsonObject>();
additionalAttributes["manufacturer"] = manufacturer;
additionalAttributes["model"] = model;
additionalAttributes["serialNumber"] = "ESP2Alex";
additionalAttributes["firmwareVersion"] = "1.0.0";
additionalAttributes["softwareVersion"] = softwareVersion;
additionalAttributes["customIdentifier"] = "ESP2Alex";
JsonArray capabilitiesArray = json["capabilities"].to<JsonArray>();
for (AlexaInterface capability : capabilities) {
capabilitiesArray.add(capability.getJSON().as<JsonObject>());
}
// capabilitiesArray.add(capability.getJSON().as<JsonObject>());
return json;
}
// void AlexaDevice::registerEvent(const String& eventName, std::function<void(const JsonDocument&,const AlexaInterfaceType&)> callback) {
// eventCallbacks[eventName] = callback;
// }
// void AlexaDevice::triggerEvent(const String& eventName, const JsonDocument& directive,const AlexaInterfaceType& type) const {
// auto it = eventCallbacks.find(eventName);
// if (it != eventCallbacks.end()) {
// // Call the registered callback function
// it->second(directive,type);
// } else {
// Serial.print("No event registered for: ");
// Serial.println(eventName);
// }
// }
// Register an event callback function
void AlexaDevice::registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&)) {
for (int i = 0; i < MAX_EVENTS; ++i) {
// Find an empty slot for the new event
if (eventNames[i] == nullptr) {
eventNames[i] = eventName; // Store the event name
eventCallbacks[i] = callback; // Store the callback function
break;
}
}
}
// Trigger the event and invoke the corresponding callback
void AlexaDevice::triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type) const {
for (int i = 0; i < MAX_EVENTS; ++i) {
if (eventNames[i] != nullptr && strcmp(eventNames[i], eventName) == 0) {
// Found the event name, call the corresponding callback function
eventCallbacks[i](directive, type);
return;
}
}
// If no event was found, print an error message
Serial.print("No event registered for: ");
Serial.println(eventName);
}

View file

@ -1,204 +1,204 @@
#ifndef ALEXADEVICE_H
#define ALEXADEVICE_H
#include <Arduino.h>
#include <ArduinoJson.h>
#include "AlexaInterface.h"
#include <functional>
#include <map>
#include <unordered_map>
#include <string>
#include <AlexaStatusMessage.h>
#include <Alex2ESP.h>
#define MAX_EVENTS 10
enum class DisplayCategory {
ACTIVITY_TRIGGER,
AIR_CONDITIONER,
AIR_FRESHENER,
AIR_PURIFIER,
AIR_QUALITY_MONITOR,
ALEXA_VOICE_ENABLED,
AUTO_ACCESSORY,
BLUETOOTH_SPEAKER,
CAMERA,
CHRISTMAS_TREE,
COFFEE_MAKER,
COMPUTER,
CONTACT_SENSOR,
DISHWASHER,
DOOR,
DOORBELL,
DRYER,
EXTERIOR_BLIND,
FAN,
GAME_CONSOLE,
GARAGE_DOOR,
HEADPHONES,
HUB,
INTERIOR_BLIND,
LAPTOP,
LIGHT,
MICROWAVE,
MOBILE_PHONE,
MOTION_SENSOR,
MUSIC_SYSTEM,
NETWORK_HARDWARE,
OTHER,
OVEN,
PHONE,
PRINTER,
REMOTE,
ROUTER,
SCENE_TRIGGER,
SCREEN,
SECURITY_PANEL,
SECURITY_SYSTEM,
SLOW_COOKER,
SMARTLOCK,
SMARTPLUG,
SPEAKER,
STREAMING_DEVICE,
SWITCH,
TABLET,
TEMPERATURE_SENSOR,
THERMOSTAT,
TV,
VACUUM_CLEANER,
VEHICLE,
WASHER,
WATER_HEATER,
WEARABLE
};
class DisplayCategoryUtils {
public:
static String toString(DisplayCategory category) {
static const std::unordered_map<DisplayCategory, String> categoryMap = {
{DisplayCategory::ACTIVITY_TRIGGER, "ACTIVITY_TRIGGER"},
{DisplayCategory::AIR_CONDITIONER, "AIR_CONDITIONER"},
{DisplayCategory::AIR_FRESHENER, "AIR_FRESHENER"},
{DisplayCategory::AIR_PURIFIER, "AIR_PURIFIER"},
{DisplayCategory::AIR_QUALITY_MONITOR, "AIR_QUALITY_MONITOR"},
{DisplayCategory::ALEXA_VOICE_ENABLED, "ALEXA_VOICE_ENABLED"},
{DisplayCategory::AUTO_ACCESSORY, "AUTO_ACCESSORY"},
{DisplayCategory::BLUETOOTH_SPEAKER, "BLUETOOTH_SPEAKER"},
{DisplayCategory::CAMERA, "CAMERA"},
{DisplayCategory::CHRISTMAS_TREE, "CHRISTMAS_TREE"},
{DisplayCategory::COFFEE_MAKER, "COFFEE_MAKER"},
{DisplayCategory::COMPUTER, "COMPUTER"},
{DisplayCategory::CONTACT_SENSOR, "CONTACT_SENSOR"},
{DisplayCategory::DISHWASHER, "DISHWASHER"},
{DisplayCategory::DOOR, "DOOR"},
{DisplayCategory::DOORBELL, "DOORBELL"},
{DisplayCategory::DRYER, "DRYER"},
{DisplayCategory::EXTERIOR_BLIND, "EXTERIOR_BLIND"},
{DisplayCategory::FAN, "FAN"},
{DisplayCategory::GAME_CONSOLE, "GAME_CONSOLE"},
{DisplayCategory::GARAGE_DOOR, "GARAGE_DOOR"},
{DisplayCategory::HEADPHONES, "HEADPHONES"},
{DisplayCategory::HUB, "HUB"},
{DisplayCategory::INTERIOR_BLIND, "INTERIOR_BLIND"},
{DisplayCategory::LAPTOP, "LAPTOP"},
{DisplayCategory::LIGHT, "LIGHT"},
{DisplayCategory::MICROWAVE, "MICROWAVE"},
{DisplayCategory::MOBILE_PHONE, "MOBILE_PHONE"},
{DisplayCategory::MOTION_SENSOR, "MOTION_SENSOR"},
{DisplayCategory::MUSIC_SYSTEM, "MUSIC_SYSTEM"},
{DisplayCategory::NETWORK_HARDWARE, "NETWORK_HARDWARE"},
{DisplayCategory::OTHER, "OTHER"},
{DisplayCategory::OVEN, "OVEN"},
{DisplayCategory::PHONE, "PHONE"},
{DisplayCategory::PRINTER, "PRINTER"},
{DisplayCategory::REMOTE, "REMOTE"},
{DisplayCategory::ROUTER, "ROUTER"},
{DisplayCategory::SCENE_TRIGGER, "SCENE_TRIGGER"},
{DisplayCategory::SCREEN, "SCREEN"},
{DisplayCategory::SECURITY_PANEL, "SECURITY_PANEL"},
{DisplayCategory::SECURITY_SYSTEM, "SECURITY_SYSTEM"},
{DisplayCategory::SLOW_COOKER, "SLOW_COOKER"},
{DisplayCategory::SMARTLOCK, "SMARTLOCK"},
{DisplayCategory::SMARTPLUG, "SMARTPLUG"},
{DisplayCategory::SPEAKER, "SPEAKER"},
{DisplayCategory::STREAMING_DEVICE, "STREAMING_DEVICE"},
{DisplayCategory::SWITCH, "SWITCH"},
{DisplayCategory::TABLET, "TABLET"},
{DisplayCategory::TEMPERATURE_SENSOR, "TEMPERATURE_SENSOR"},
{DisplayCategory::THERMOSTAT, "THERMOSTAT"},
{DisplayCategory::TV, "TV"},
{DisplayCategory::VACUUM_CLEANER, "VACUUM_CLEANER"},
{DisplayCategory::VEHICLE, "VEHICLE"},
{DisplayCategory::WASHER, "WASHER"},
{DisplayCategory::WATER_HEATER, "WATER_HEATER"},
{DisplayCategory::WEARABLE, "WEARABLE"}
};
auto it = categoryMap.find(category);
if (it != categoryMap.end()) {
return it->second;
}
return "UNKNOWN";
}
};
class AlexaDevice {
public:
AlexaDevice(const String& name, const String& rootTopic, const String& endpointId);
void setName(const String& name);
String getName() const;
String getEndpointId() const;
DisplayCategory getDisplayCategory() const;
void setDisplayCategory(DisplayCategory category);
String getDescription() const;
void setDescription(const String& description);
String getManufacturerName() const;
void setManufacturerName(const String& manufacturerName);
String getManufacturer() const;
void setManufacturer(const String& manufacturer);
String getModel() const;
void setModel(const String& model);
String getSoftwareVersion() const;
JsonDocument getDeviceJSON() const;
AlexaInterface* addCapability(AlexaInterfaceType type);
void registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&));
void triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type) const;
AlexaStatusMessage buildStatusMessage(const String& correlationToken,const bool isResponse=false) {
return AlexaStatusMessage(correlationToken,rootTopic,endpointId,isResponse);
}
private:
String name;
String endpointId;
String rootTopic;
const char* eventNames[MAX_EVENTS];
void (*eventCallbacks[MAX_EVENTS])(const JsonDocument&, const AlexaInterfaceType&);
DisplayCategory displayCategory = DisplayCategory::OTHER;
String description = "Alexa2MQTT Default Device";
String manufacturerName = "Alexa2MQTT";
String manufacturer = "Alexa2MQTT";
String model = "Alexa2MQTT";
const String softwareVersion = "1.0.0";
std::vector<AlexaInterface> capabilities;
};
#endif
#ifndef ALEXADEVICE_H
#define ALEXADEVICE_H
#include <Arduino.h>
#include <ArduinoJson.h>
#include "AlexaInterface.h"
#include <functional>
#include <map>
#include <unordered_map>
#include <string>
#include <AlexaStatusMessage.h>
#include <Alex2ESP.h>
#define MAX_EVENTS 10
enum class DisplayCategory {
ACTIVITY_TRIGGER,
AIR_CONDITIONER,
AIR_FRESHENER,
AIR_PURIFIER,
AIR_QUALITY_MONITOR,
ALEXA_VOICE_ENABLED,
AUTO_ACCESSORY,
BLUETOOTH_SPEAKER,
CAMERA,
CHRISTMAS_TREE,
COFFEE_MAKER,
COMPUTER,
CONTACT_SENSOR,
DISHWASHER,
DOOR,
DOORBELL,
DRYER,
EXTERIOR_BLIND,
FAN,
GAME_CONSOLE,
GARAGE_DOOR,
HEADPHONES,
HUB,
INTERIOR_BLIND,
LAPTOP,
LIGHT,
MICROWAVE,
MOBILE_PHONE,
MOTION_SENSOR,
MUSIC_SYSTEM,
NETWORK_HARDWARE,
OTHER,
OVEN,
PHONE,
PRINTER,
REMOTE,
ROUTER,
SCENE_TRIGGER,
SCREEN,
SECURITY_PANEL,
SECURITY_SYSTEM,
SLOW_COOKER,
SMARTLOCK,
SMARTPLUG,
SPEAKER,
STREAMING_DEVICE,
SWITCH,
TABLET,
TEMPERATURE_SENSOR,
THERMOSTAT,
TV,
VACUUM_CLEANER,
VEHICLE,
WASHER,
WATER_HEATER,
WEARABLE
};
class DisplayCategoryUtils {
public:
static String toString(DisplayCategory category) {
static const std::unordered_map<DisplayCategory, String> categoryMap = {
{DisplayCategory::ACTIVITY_TRIGGER, "ACTIVITY_TRIGGER"},
{DisplayCategory::AIR_CONDITIONER, "AIR_CONDITIONER"},
{DisplayCategory::AIR_FRESHENER, "AIR_FRESHENER"},
{DisplayCategory::AIR_PURIFIER, "AIR_PURIFIER"},
{DisplayCategory::AIR_QUALITY_MONITOR, "AIR_QUALITY_MONITOR"},
{DisplayCategory::ALEXA_VOICE_ENABLED, "ALEXA_VOICE_ENABLED"},
{DisplayCategory::AUTO_ACCESSORY, "AUTO_ACCESSORY"},
{DisplayCategory::BLUETOOTH_SPEAKER, "BLUETOOTH_SPEAKER"},
{DisplayCategory::CAMERA, "CAMERA"},
{DisplayCategory::CHRISTMAS_TREE, "CHRISTMAS_TREE"},
{DisplayCategory::COFFEE_MAKER, "COFFEE_MAKER"},
{DisplayCategory::COMPUTER, "COMPUTER"},
{DisplayCategory::CONTACT_SENSOR, "CONTACT_SENSOR"},
{DisplayCategory::DISHWASHER, "DISHWASHER"},
{DisplayCategory::DOOR, "DOOR"},
{DisplayCategory::DOORBELL, "DOORBELL"},
{DisplayCategory::DRYER, "DRYER"},
{DisplayCategory::EXTERIOR_BLIND, "EXTERIOR_BLIND"},
{DisplayCategory::FAN, "FAN"},
{DisplayCategory::GAME_CONSOLE, "GAME_CONSOLE"},
{DisplayCategory::GARAGE_DOOR, "GARAGE_DOOR"},
{DisplayCategory::HEADPHONES, "HEADPHONES"},
{DisplayCategory::HUB, "HUB"},
{DisplayCategory::INTERIOR_BLIND, "INTERIOR_BLIND"},
{DisplayCategory::LAPTOP, "LAPTOP"},
{DisplayCategory::LIGHT, "LIGHT"},
{DisplayCategory::MICROWAVE, "MICROWAVE"},
{DisplayCategory::MOBILE_PHONE, "MOBILE_PHONE"},
{DisplayCategory::MOTION_SENSOR, "MOTION_SENSOR"},
{DisplayCategory::MUSIC_SYSTEM, "MUSIC_SYSTEM"},
{DisplayCategory::NETWORK_HARDWARE, "NETWORK_HARDWARE"},
{DisplayCategory::OTHER, "OTHER"},
{DisplayCategory::OVEN, "OVEN"},
{DisplayCategory::PHONE, "PHONE"},
{DisplayCategory::PRINTER, "PRINTER"},
{DisplayCategory::REMOTE, "REMOTE"},
{DisplayCategory::ROUTER, "ROUTER"},
{DisplayCategory::SCENE_TRIGGER, "SCENE_TRIGGER"},
{DisplayCategory::SCREEN, "SCREEN"},
{DisplayCategory::SECURITY_PANEL, "SECURITY_PANEL"},
{DisplayCategory::SECURITY_SYSTEM, "SECURITY_SYSTEM"},
{DisplayCategory::SLOW_COOKER, "SLOW_COOKER"},
{DisplayCategory::SMARTLOCK, "SMARTLOCK"},
{DisplayCategory::SMARTPLUG, "SMARTPLUG"},
{DisplayCategory::SPEAKER, "SPEAKER"},
{DisplayCategory::STREAMING_DEVICE, "STREAMING_DEVICE"},
{DisplayCategory::SWITCH, "SWITCH"},
{DisplayCategory::TABLET, "TABLET"},
{DisplayCategory::TEMPERATURE_SENSOR, "TEMPERATURE_SENSOR"},
{DisplayCategory::THERMOSTAT, "THERMOSTAT"},
{DisplayCategory::TV, "TV"},
{DisplayCategory::VACUUM_CLEANER, "VACUUM_CLEANER"},
{DisplayCategory::VEHICLE, "VEHICLE"},
{DisplayCategory::WASHER, "WASHER"},
{DisplayCategory::WATER_HEATER, "WATER_HEATER"},
{DisplayCategory::WEARABLE, "WEARABLE"}
};
auto it = categoryMap.find(category);
if (it != categoryMap.end()) {
return it->second;
}
return "UNKNOWN";
}
};
class AlexaDevice {
public:
AlexaDevice(const String& name, const String& rootTopic, const String& endpointId);
void setName(const String& name);
String getName() const;
String getEndpointId() const;
DisplayCategory getDisplayCategory() const;
void setDisplayCategory(DisplayCategory category);
String getDescription() const;
void setDescription(const String& description);
String getManufacturerName() const;
void setManufacturerName(const String& manufacturerName);
String getManufacturer() const;
void setManufacturer(const String& manufacturer);
String getModel() const;
void setModel(const String& model);
String getSoftwareVersion() const;
JsonDocument getDeviceJSON() const;
AlexaInterface* addCapability(AlexaInterfaceType type);
void registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&));
void triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type) const;
AlexaStatusMessage buildStatusMessage(const String& correlationToken,const bool isResponse=false) {
return AlexaStatusMessage(correlationToken,rootTopic,endpointId,isResponse);
}
private:
String name;
String endpointId;
String rootTopic;
const char* eventNames[MAX_EVENTS];
void (*eventCallbacks[MAX_EVENTS])(const JsonDocument&, const AlexaInterfaceType&);
DisplayCategory displayCategory = DisplayCategory::OTHER;
String description = "Alexa2MQTT Default Device";
String manufacturerName = "Alexa2MQTT";
String manufacturer = "Alexa2MQTT";
String model = "Alexa2MQTT";
const String softwareVersion = "1.0.0";
std::vector<AlexaInterface> capabilities;
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,3 @@
#include "AlexaStatusMessage.h"
char AlexaStatusMessage::outputString[MAX_STATUS_REPORT_SIZE];
#include "AlexaStatusMessage.h"
char AlexaStatusMessage::outputString[MAX_STATUS_REPORT_SIZE];

View file

@ -1,167 +1,167 @@
#include <ArduinoJson.h>
#include <Alex2ESP.h>
#include <AlexaUtils.h>
#ifndef ALEXA_STATUS_MESSAGE_H
#define ALEXA_STATUS_MESSAGE_H
#define MAX_STATUS_REPORT_SIZE 2048
enum class EndpointHealth
{
OK,
UNREACHABLE
};
enum class PowerController
{
ON,
OFF
};
enum class TemperatureSensorScale
{
CELSIUS,
FAHRENHEIT
};
class AlexaStatusMessage
{
public:
AlexaStatusMessage(const String &correlationToken, const String &rootTopic, const String &endpointId, const bool isResponse)
{
this->endpointId = endpointId;
this->rootTopic = rootTopic;
JsonObject event = doc["event"].to<JsonObject>();
JsonObject event_header = event["header"].to<JsonObject>();
event_header["namespace"] = "Alexa";
if (isResponse)
{
event_header["name"] = "Response";
}
else
{
event_header["name"] = "StateReport";
}
event_header["payloadVersion"] = "3";
event_header["messageId"] = generateMessageId();
event_header["correlationToken"] = correlationToken;
JsonObject event_endpoint = event["endpoint"].to<JsonObject>();
event_endpoint["endpointId"] = endpointId;
contextProperties = doc["context"]["properties"].to<JsonArray>();
}
void setEndpointId(const String &endpointId)
{
// event["endpoint"]["endpointId"] = endpointId;
}
AlexaStatusMessage &AddHealthProp(EndpointHealth endpointHealth, unsigned int uncertaintyInMs = 0)
{
JsonDocument healthValue;
healthValue["value"] = (endpointHealth == EndpointHealth::OK) ? "OK" : "UNREACHABLE";
return AddProperty(AlexaInterfaceType::ENDPOINT_HEALTH, "connectivity", healthValue.as<JsonObject>(), uncertaintyInMs);
}
AlexaStatusMessage &AddPowerControllerProp(PowerController powerController, unsigned int uncertaintyInMs = 0)
{
String value = (powerController == PowerController::ON) ? "ON" : "OFF";
return AddProperty(AlexaInterfaceType::POWER_CONTROLLER, "powerState", value, uncertaintyInMs);
}
AlexaStatusMessage &AddTemperatureSensorProp(TemperatureSensorScale tempSensor,float value, unsigned int uncertaintyInMs = 0)
{
JsonDocument tempValue;
tempValue["scale"] = "CELSIUS";
tempValue["value"] = value;
if((tempSensor == TemperatureSensorScale::FAHRENHEIT)){
tempValue["value"] = (value - 32) * 5.0 / 9.0;
}
return AddProperty(AlexaInterfaceType::TEMPERATURE_SENSOR, "temperature", tempValue.as<JsonObject>(), uncertaintyInMs);
}
AlexaStatusMessage &AddBrightnessControllerProp(unsigned int brightness, unsigned int uncertaintyInMs = 0)
{
return AddProperty(AlexaInterfaceType::BRIGHTNESS_CONTROLLER, "brightness", brightness, uncertaintyInMs);
}
AlexaStatusMessage &AddColorTemperatureControllerProp(unsigned int colorTemperature, unsigned int uncertaintyInMs = 0)
{
return AddProperty(AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER, "colorTemperatureInKelvin", colorTemperature, uncertaintyInMs);
}
AlexaStatusMessage &AddToggleControllerProp(PowerController powerController,String instanceName, unsigned int uncertaintyInMs = 0)
{
String value = (powerController == PowerController::ON) ? "ON" : "OFF";
return AddProperty(AlexaInterfaceType::TOGGLE_CONTROLLER, "toggleState", value, uncertaintyInMs,instanceName);
}
AlexaStatusMessage &AddContextProp(const JsonObject &property)
{
contextProperties.add(property);
return *this; // Return a reference to the current object
}
void send()
{
doc.shrinkToFit();
serializeJson(doc, outputString);
doc.clear();
String topic = rootTopic + "/" + endpointId + "/alexaResponce";
AlexaUtils::enqueue(outputString, topic.c_str());
}
private:
String rootTopic;
String endpointId;
JsonDocument doc;
JsonArray contextProperties;
static char outputString[MAX_STATUS_REPORT_SIZE];
// TODO: make real uuid4 gen function
String generateMessageId()
{
char buffer[38];
const char charset[] = "0123456789abcdefABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // Allowed characters
// Seed the random number generator (optional)
srand(static_cast<unsigned int>(time(nullptr)));
// Generate 37 random characters
for (int i = 0; i < 37; ++i)
{
buffer[i] = charset[rand() % (sizeof(charset) - 1)]; // Pick a random character
}
buffer[37] = '\0'; // Null-terminate the string
return String(buffer);
}
template <typename T>
AlexaStatusMessage &AddProperty(AlexaInterfaceType type, const String &propertyName, const T &value, unsigned int uncertaintyInMs = 0,const String &instanceName="")
{
JsonDocument prop;
prop["namespace"] = AlexaInterfaceUtils::toString(type);
prop["name"] = propertyName;
if (!instanceName.isEmpty()) {
prop["instance"] = instanceName;
}
if constexpr (std::is_same_v<T, JsonObject>)
{
prop["value"] = value;
}
else
{
prop["value"] = value;
}
prop["timeOfSample"] = "{REPLACE_WITH_DATETIME}";
prop["uncertaintyInMilliseconds"] = uncertaintyInMs;
contextProperties.add(prop.as<JsonObject>());
return *this;
}
};
#endif
#include <ArduinoJson.h>
#include <Alex2ESP.h>
#include <AlexaUtils.h>
#ifndef ALEXA_STATUS_MESSAGE_H
#define ALEXA_STATUS_MESSAGE_H
#define MAX_STATUS_REPORT_SIZE 2048
enum class EndpointHealth
{
OK,
UNREACHABLE
};
enum class PowerController
{
ON,
OFF
};
enum class TemperatureSensorScale
{
CELSIUS,
FAHRENHEIT
};
class AlexaStatusMessage
{
public:
AlexaStatusMessage(const String &correlationToken, const String &rootTopic, const String &endpointId, const bool isResponse)
{
this->endpointId = endpointId;
this->rootTopic = rootTopic;
JsonObject event = doc["event"].to<JsonObject>();
JsonObject event_header = event["header"].to<JsonObject>();
event_header["namespace"] = "Alexa";
if (isResponse)
{
event_header["name"] = "Response";
}
else
{
event_header["name"] = "StateReport";
}
event_header["payloadVersion"] = "3";
event_header["messageId"] = generateMessageId();
event_header["correlationToken"] = correlationToken;
JsonObject event_endpoint = event["endpoint"].to<JsonObject>();
event_endpoint["endpointId"] = endpointId;
contextProperties = doc["context"]["properties"].to<JsonArray>();
}
void setEndpointId(const String &endpointId)
{
// event["endpoint"]["endpointId"] = endpointId;
}
AlexaStatusMessage &AddHealthProp(EndpointHealth endpointHealth, unsigned int uncertaintyInMs = 0)
{
JsonDocument healthValue;
healthValue["value"] = (endpointHealth == EndpointHealth::OK) ? "OK" : "UNREACHABLE";
return AddProperty(AlexaInterfaceType::ENDPOINT_HEALTH, "connectivity", healthValue.as<JsonObject>(), uncertaintyInMs);
}
AlexaStatusMessage &AddPowerControllerProp(PowerController powerController, unsigned int uncertaintyInMs = 0)
{
String value = (powerController == PowerController::ON) ? "ON" : "OFF";
return AddProperty(AlexaInterfaceType::POWER_CONTROLLER, "powerState", value, uncertaintyInMs);
}
AlexaStatusMessage &AddTemperatureSensorProp(TemperatureSensorScale tempSensor,float value, unsigned int uncertaintyInMs = 0)
{
JsonDocument tempValue;
tempValue["scale"] = "CELSIUS";
tempValue["value"] = value;
if((tempSensor == TemperatureSensorScale::FAHRENHEIT)){
tempValue["value"] = (value - 32) * 5.0 / 9.0;
}
return AddProperty(AlexaInterfaceType::TEMPERATURE_SENSOR, "temperature", tempValue.as<JsonObject>(), uncertaintyInMs);
}
AlexaStatusMessage &AddBrightnessControllerProp(unsigned int brightness, unsigned int uncertaintyInMs = 0)
{
return AddProperty(AlexaInterfaceType::BRIGHTNESS_CONTROLLER, "brightness", brightness, uncertaintyInMs);
}
AlexaStatusMessage &AddColorTemperatureControllerProp(unsigned int colorTemperature, unsigned int uncertaintyInMs = 0)
{
return AddProperty(AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER, "colorTemperatureInKelvin", colorTemperature, uncertaintyInMs);
}
AlexaStatusMessage &AddToggleControllerProp(PowerController powerController,String instanceName, unsigned int uncertaintyInMs = 0)
{
String value = (powerController == PowerController::ON) ? "ON" : "OFF";
return AddProperty(AlexaInterfaceType::TOGGLE_CONTROLLER, "toggleState", value, uncertaintyInMs,instanceName);
}
AlexaStatusMessage &AddContextProp(const JsonObject &property)
{
contextProperties.add(property);
return *this; // Return a reference to the current object
}
void send()
{
doc.shrinkToFit();
serializeJson(doc, outputString);
doc.clear();
String topic = rootTopic + "/" + endpointId + "/alexaResponce";
AlexaUtils::enqueue(outputString, topic.c_str());
}
private:
String rootTopic;
String endpointId;
JsonDocument doc;
JsonArray contextProperties;
static char outputString[MAX_STATUS_REPORT_SIZE];
// TODO: make real uuid4 gen function
String generateMessageId()
{
char buffer[38];
const char charset[] = "0123456789abcdefABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // Allowed characters
// Seed the random number generator (optional)
srand(static_cast<unsigned int>(time(nullptr)));
// Generate 37 random characters
for (int i = 0; i < 37; ++i)
{
buffer[i] = charset[rand() % (sizeof(charset) - 1)]; // Pick a random character
}
buffer[37] = '\0'; // Null-terminate the string
return String(buffer);
}
template <typename T>
AlexaStatusMessage &AddProperty(AlexaInterfaceType type, const String &propertyName, const T &value, unsigned int uncertaintyInMs = 0,const String &instanceName="")
{
JsonDocument prop;
prop["namespace"] = AlexaInterfaceUtils::toString(type);
prop["name"] = propertyName;
if (!instanceName.isEmpty()) {
prop["instance"] = instanceName;
}
if constexpr (std::is_same_v<T, JsonObject>)
{
prop["value"] = value;
}
else
{
prop["value"] = value;
}
prop["timeOfSample"] = "{REPLACE_WITH_DATETIME}";
prop["uncertaintyInMilliseconds"] = uncertaintyInMs;
contextProperties.add(prop.as<JsonObject>());
return *this;
}
};
#endif

View file

@ -1,220 +1,220 @@
#include "AlexaUtils.h"
// #define Alex2ESP_DEBUG
uint8_t AlexaUtils::nextMessageId = 0;
char AlexaUtils::receiveQueue[MAX_QUEUE_LENGTH][16]={};
char AlexaUtils::receivePayload[MAX_PAYLOAD_LENGTH];
char AlexaUtils::topicQueue[MAX_QUEUE_LENGTH][MAX_TOPIC_LENGTH] = {};
char AlexaUtils::packetQueue[MAX_QUEUE_LENGTH][MAX_PACKET_LENGTH] = {};
char AlexaUtils::combinedData[MAX_TOPIC_LENGTH + MAX_PACKET_LENGTH + 32];
int AlexaUtils::queueStart = 0;
int AlexaUtils::queueEnd = 0;
int AlexaUtils::queueCount = 0;
int AlexaUtils::queueStartReceive = 0;
int AlexaUtils::queueEndReceive = 0;
int AlexaUtils::queueCountReceive = 0;
// Enqueue a packet into the receive queue
bool AlexaUtils::enqueueReceive(const char* packet) {
if (isReceiveQueueFull()) {
return false; // Queue is full
}
AlexaUtils::log("Enqueing: ");
AlexaUtils::logln(packet);
// Copy the packet into the receive queue
strncpy(receiveQueue[queueEndReceive], packet, sizeof(receiveQueue[0]) - 1);
receiveQueue[queueEndReceive][sizeof(receiveQueue[0]) - 1] = '\0'; // Ensure null-termination
// Update the queueEnd and queueCount
queueEndReceive = (queueEndReceive + 1) % MAX_QUEUE_LENGTH;
queueCountReceive++;
return true;
}
// Dequeue a packet from the receive queue
bool AlexaUtils::dequeueReceive(String& packet,bool remove) {
if (isReceiveQueueEmpty()) {
return false; // Queue is empty
}
// Read the front of the receive queue
packet = String(receiveQueue[queueStartReceive]);
if(remove){
// Clear the dequeued slot (optional but good for debugging)
memset(receiveQueue[queueStartReceive], 0, sizeof(receiveQueue[0]));
// Update the queueStart and queueCount
queueStartReceive = (queueStartReceive + 1) % MAX_QUEUE_LENGTH;
queueCountReceive--;
}
return true;
}
// Check if the receive queue is empty
bool AlexaUtils::isReceiveQueueEmpty() {
return queueCountReceive == 0;
}
// Check if the receive queue is full
bool AlexaUtils::isReceiveQueueFull() {
return queueCountReceive == MAX_QUEUE_LENGTH;
}
// Enqueue a topic and packet into the queue
bool AlexaUtils::enqueue(const char* packet,const char* topic) {
if (isQueueFull()) {
return false; // Queue is full
}
// Copy the topic and packet into the respective arrays
strncpy(topicQueue[queueEnd], topic, MAX_TOPIC_LENGTH - 1);
topicQueue[queueEnd][MAX_TOPIC_LENGTH - 1] = '\0'; // Ensure null-termination
strncpy(packetQueue[queueEnd], packet, MAX_PACKET_LENGTH - 1);
packetQueue[queueEnd][MAX_PACKET_LENGTH - 1] = '\0'; // Ensure null-termination
// Update the queueEnd and queueCount
queueEnd = (queueEnd + 1) % MAX_QUEUE_LENGTH;
queueCount++;
return true;
}
// Dequeue a topic and packet from the queue
bool AlexaUtils::dequeue(String& topic, String& packet) {
if (isQueueEmpty()) {
return false; // Queue is empty
}
// Read the front of the queue
topic = String(topicQueue[queueStart]);
packet = String(packetQueue[queueStart]);
// Clear the dequeued slot (optional but good for debugging)
memset(topicQueue[queueStart], 0, MAX_TOPIC_LENGTH);
memset(packetQueue[queueStart], 0, MAX_PACKET_LENGTH);
// Update the queueStart and queueCount
queueStart = (queueStart + 1) % MAX_QUEUE_LENGTH;
queueCount--;
return true;
}
const char* AlexaUtils::dequeueVals(bool remove) {
if (isQueueEmpty()) {
return nullptr; // Queue is empty
}
// Reset combinedData before use to ensure no leftover data from previous calls
memset(combinedData, 0, sizeof(combinedData));
// Get lengths of the topic and packet
int topicLen = strlen(topicQueue[queueStart]);
int packetLen = strlen(packetQueue[queueStart]);
// Calculate available space in the combinedData buffer
size_t availableSpace = sizeof(combinedData) - 1; // Reserve space for the null terminator
// Check if the combined data can fit in the buffer
if ((topicLen + packetLen + strlen(MESSAGE_PAYLOAD_SPLIT)) > availableSpace) {
// Return null if the combined data is too large for the buffer
AlexaUtils::logln("Error: Combined data exceeds buffer size.");
return nullptr;
}
// Concatenate the topic, separator, and packet into combinedData
snprintf(combinedData, sizeof(combinedData), "%s%s%s", topicQueue[queueStart], MESSAGE_PAYLOAD_SPLIT, packetQueue[queueStart]);
if (remove) {
// Clear the dequeued slot
memset(topicQueue[queueStart], 0, MAX_TOPIC_LENGTH);
memset(packetQueue[queueStart], 0, MAX_PACKET_LENGTH);
// Update the queueStart and queueCount
queueStart = (queueStart + 1) % MAX_QUEUE_LENGTH;
queueCount--;
}
// Return the pointer to the combined data
return combinedData;
}
// Check if the queue is empty
bool AlexaUtils::isQueueEmpty() {
return queueCount == 0;
}
// Check if the queue is full
bool AlexaUtils::isQueueFull() {
return queueCount == MAX_QUEUE_LENGTH;
}
void AlexaUtils::log(const char *string) {
#ifdef Alex2ESP_DEBUG
Serial.print(string);
#endif
}
void AlexaUtils::logln(const char *string) {
#ifdef Alex2ESP_DEBUG
Serial.println(string);
#endif
}
void AlexaUtils::log(int num) {
#ifdef Alex2ESP_DEBUG
Serial.print(num);
#endif
}
void AlexaUtils::logln(int num) {
#ifdef Alex2ESP_DEBUG
Serial.println(num);
#endif
}
void AlexaUtils::log(uint16_t num) {
#ifdef Alex2ESP_DEBUG
Serial.print(num);
#endif
}
void AlexaUtils::logln(uint16_t num) {
#ifdef Alex2ESP_DEBUG
Serial.println(num);
#endif
}
void AlexaUtils::log(uint8_t num) {
#ifdef Alex2ESP_DEBUG
Serial.print(num);
#endif
}
void AlexaUtils::logln(uint8_t num) {
#ifdef Alex2ESP_DEBUG
Serial.println(num);
#endif
}
void AlexaUtils::logln(String string) {
#ifdef Alex2ESP_DEBUG
Serial.println(string);
#endif
}
#include "AlexaUtils.h"
// #define Alex2ESP_DEBUG
uint8_t AlexaUtils::nextMessageId = 0;
char AlexaUtils::receiveQueue[MAX_QUEUE_LENGTH][16]={};
char AlexaUtils::receivePayload[MAX_PAYLOAD_LENGTH];
char AlexaUtils::topicQueue[MAX_QUEUE_LENGTH][MAX_TOPIC_LENGTH] = {};
char AlexaUtils::packetQueue[MAX_QUEUE_LENGTH][MAX_PACKET_LENGTH] = {};
char AlexaUtils::combinedData[MAX_TOPIC_LENGTH + MAX_PACKET_LENGTH + 32];
int AlexaUtils::queueStart = 0;
int AlexaUtils::queueEnd = 0;
int AlexaUtils::queueCount = 0;
int AlexaUtils::queueStartReceive = 0;
int AlexaUtils::queueEndReceive = 0;
int AlexaUtils::queueCountReceive = 0;
// Enqueue a packet into the receive queue
bool AlexaUtils::enqueueReceive(const char* packet) {
if (isReceiveQueueFull()) {
return false; // Queue is full
}
AlexaUtils::log("Enqueing: ");
AlexaUtils::logln(packet);
// Copy the packet into the receive queue
strncpy(receiveQueue[queueEndReceive], packet, sizeof(receiveQueue[0]) - 1);
receiveQueue[queueEndReceive][sizeof(receiveQueue[0]) - 1] = '\0'; // Ensure null-termination
// Update the queueEnd and queueCount
queueEndReceive = (queueEndReceive + 1) % MAX_QUEUE_LENGTH;
queueCountReceive++;
return true;
}
// Dequeue a packet from the receive queue
bool AlexaUtils::dequeueReceive(String& packet,bool remove) {
if (isReceiveQueueEmpty()) {
return false; // Queue is empty
}
// Read the front of the receive queue
packet = String(receiveQueue[queueStartReceive]);
if(remove){
// Clear the dequeued slot (optional but good for debugging)
memset(receiveQueue[queueStartReceive], 0, sizeof(receiveQueue[0]));
// Update the queueStart and queueCount
queueStartReceive = (queueStartReceive + 1) % MAX_QUEUE_LENGTH;
queueCountReceive--;
}
return true;
}
// Check if the receive queue is empty
bool AlexaUtils::isReceiveQueueEmpty() {
return queueCountReceive == 0;
}
// Check if the receive queue is full
bool AlexaUtils::isReceiveQueueFull() {
return queueCountReceive == MAX_QUEUE_LENGTH;
}
// Enqueue a topic and packet into the queue
bool AlexaUtils::enqueue(const char* packet,const char* topic) {
if (isQueueFull()) {
return false; // Queue is full
}
// Copy the topic and packet into the respective arrays
strncpy(topicQueue[queueEnd], topic, MAX_TOPIC_LENGTH - 1);
topicQueue[queueEnd][MAX_TOPIC_LENGTH - 1] = '\0'; // Ensure null-termination
strncpy(packetQueue[queueEnd], packet, MAX_PACKET_LENGTH - 1);
packetQueue[queueEnd][MAX_PACKET_LENGTH - 1] = '\0'; // Ensure null-termination
// Update the queueEnd and queueCount
queueEnd = (queueEnd + 1) % MAX_QUEUE_LENGTH;
queueCount++;
return true;
}
// Dequeue a topic and packet from the queue
bool AlexaUtils::dequeue(String& topic, String& packet) {
if (isQueueEmpty()) {
return false; // Queue is empty
}
// Read the front of the queue
topic = String(topicQueue[queueStart]);
packet = String(packetQueue[queueStart]);
// Clear the dequeued slot (optional but good for debugging)
memset(topicQueue[queueStart], 0, MAX_TOPIC_LENGTH);
memset(packetQueue[queueStart], 0, MAX_PACKET_LENGTH);
// Update the queueStart and queueCount
queueStart = (queueStart + 1) % MAX_QUEUE_LENGTH;
queueCount--;
return true;
}
const char* AlexaUtils::dequeueVals(bool remove) {
if (isQueueEmpty()) {
return nullptr; // Queue is empty
}
// Reset combinedData before use to ensure no leftover data from previous calls
memset(combinedData, 0, sizeof(combinedData));
// Get lengths of the topic and packet
int topicLen = strlen(topicQueue[queueStart]);
int packetLen = strlen(packetQueue[queueStart]);
// Calculate available space in the combinedData buffer
size_t availableSpace = sizeof(combinedData) - 1; // Reserve space for the null terminator
// Check if the combined data can fit in the buffer
if ((topicLen + packetLen + strlen(MESSAGE_PAYLOAD_SPLIT)) > availableSpace) {
// Return null if the combined data is too large for the buffer
AlexaUtils::logln("Error: Combined data exceeds buffer size.");
return nullptr;
}
// Concatenate the topic, separator, and packet into combinedData
snprintf(combinedData, sizeof(combinedData), "%s%s%s", topicQueue[queueStart], MESSAGE_PAYLOAD_SPLIT, packetQueue[queueStart]);
if (remove) {
// Clear the dequeued slot
memset(topicQueue[queueStart], 0, MAX_TOPIC_LENGTH);
memset(packetQueue[queueStart], 0, MAX_PACKET_LENGTH);
// Update the queueStart and queueCount
queueStart = (queueStart + 1) % MAX_QUEUE_LENGTH;
queueCount--;
}
// Return the pointer to the combined data
return combinedData;
}
// Check if the queue is empty
bool AlexaUtils::isQueueEmpty() {
return queueCount == 0;
}
// Check if the queue is full
bool AlexaUtils::isQueueFull() {
return queueCount == MAX_QUEUE_LENGTH;
}
void AlexaUtils::log(const char *string) {
#ifdef Alex2ESP_DEBUG
Serial.print(string);
#endif
}
void AlexaUtils::logln(const char *string) {
#ifdef Alex2ESP_DEBUG
Serial.println(string);
#endif
}
void AlexaUtils::log(int num) {
#ifdef Alex2ESP_DEBUG
Serial.print(num);
#endif
}
void AlexaUtils::logln(int num) {
#ifdef Alex2ESP_DEBUG
Serial.println(num);
#endif
}
void AlexaUtils::log(uint16_t num) {
#ifdef Alex2ESP_DEBUG
Serial.print(num);
#endif
}
void AlexaUtils::logln(uint16_t num) {
#ifdef Alex2ESP_DEBUG
Serial.println(num);
#endif
}
void AlexaUtils::log(uint8_t num) {
#ifdef Alex2ESP_DEBUG
Serial.print(num);
#endif
}
void AlexaUtils::logln(uint8_t num) {
#ifdef Alex2ESP_DEBUG
Serial.println(num);
#endif
}
void AlexaUtils::logln(String string) {
#ifdef Alex2ESP_DEBUG
Serial.println(string);
#endif
}

View file

@ -1,83 +1,83 @@
#ifndef ALEXA_UTILS_H
#define ALEXA_UTILS_H
#include <vector>
#include <string>
#include <Arduino.h>
#include <AsyncMqttClient.h>
#include <cstring> // Include for strcpy and memset
class AlexaUtils
{
public:
static const int MAX_PAYLOAD_LENGTH = 2048; // Maximum length for receive packets
static char receivePayload[MAX_PAYLOAD_LENGTH];
static bool enqueueReceive(const char* packet);
static bool dequeueReceive(String& packet,bool remove);
static bool isReceiveQueueEmpty();
static bool isReceiveQueueFull();
static bool enqueue(const char* topic, const char* packet);
static bool dequeue(String& topic, String& packet);
static bool isQueueEmpty();
static bool isQueueFull();
static const char* dequeueVals(bool remove);
static void log(const char *string);
static void logln(const char *string);
static void log(int num);
static void logln(int num);
static void log(uint16_t num);
static void logln(uint16_t num);
static void log(uint8_t num);
static void logln(uint8_t num);
static void logln(String string);
// Incrementing message ID
static uint8_t nextMessageId;
// Static function to set the MQTT client reference
static void printMemoryInfo()
{
size_t freeHeap = ESP.getFreeHeap();
size_t freeStack = ESP.getFreeContStack(); // Requires ESP8266 core 3.0.0+
size_t sketchSize = ESP.getSketchSize();
size_t freeSketchSpace = ESP.getFreeSketchSpace();
// Print in custom format
Serial.printf("freeHeap: %u, freeStack: %u, freeROM: %u, usedROM: %u\n",
freeHeap,
freeStack,
freeSketchSpace,
sketchSize);
}
private:
static const int MAX_QUEUE_LENGTH = 5; // Define maximum queue length
static const int MAX_TOPIC_LENGTH = 128; // Maximum length for topics
static const int MAX_PACKET_LENGTH = 2048; // Maximum length for sending packets
static constexpr char MESSAGE_PAYLOAD_SPLIT[32] = "{MESSAGE_PAYLOAD_SPLIT}";
// Static arrays for topics and packets
static char topicQueue[MAX_QUEUE_LENGTH][MAX_TOPIC_LENGTH];
static char packetQueue[MAX_QUEUE_LENGTH][MAX_PACKET_LENGTH];
static char combinedData[MAX_TOPIC_LENGTH + MAX_PACKET_LENGTH + 32];
static char receiveQueue[MAX_QUEUE_LENGTH][16];
static int queueStart; // Index of the front of the queue
static int queueEnd; // Index of the back of the queue
static int queueCount; // Number of items in the queue
static int queueStartReceive;
static int queueEndReceive;
static int queueCountReceive;
};
#endif // ALEXA_UTILS_H
#ifndef ALEXA_UTILS_H
#define ALEXA_UTILS_H
#include <vector>
#include <string>
#include <Arduino.h>
#include <AsyncMqttClient.h>
#include <cstring> // Include for strcpy and memset
class AlexaUtils
{
public:
static const int MAX_PAYLOAD_LENGTH = 2048; // Maximum length for receive packets
static char receivePayload[MAX_PAYLOAD_LENGTH];
static bool enqueueReceive(const char* packet);
static bool dequeueReceive(String& packet,bool remove);
static bool isReceiveQueueEmpty();
static bool isReceiveQueueFull();
static bool enqueue(const char* topic, const char* packet);
static bool dequeue(String& topic, String& packet);
static bool isQueueEmpty();
static bool isQueueFull();
static const char* dequeueVals(bool remove);
static void log(const char *string);
static void logln(const char *string);
static void log(int num);
static void logln(int num);
static void log(uint16_t num);
static void logln(uint16_t num);
static void log(uint8_t num);
static void logln(uint8_t num);
static void logln(String string);
// Incrementing message ID
static uint8_t nextMessageId;
// Static function to set the MQTT client reference
static void printMemoryInfo()
{
size_t freeHeap = ESP.getFreeHeap();
size_t freeStack = ESP.getFreeContStack(); // Requires ESP8266 core 3.0.0+
size_t sketchSize = ESP.getSketchSize();
size_t freeSketchSpace = ESP.getFreeSketchSpace();
// Print in custom format
Serial.printf("freeHeap: %u, freeStack: %u, freeROM: %u, usedROM: %u\n",
freeHeap,
freeStack,
freeSketchSpace,
sketchSize);
}
private:
static const int MAX_QUEUE_LENGTH = 5; // Define maximum queue length
static const int MAX_TOPIC_LENGTH = 128; // Maximum length for topics
static const int MAX_PACKET_LENGTH = 2048; // Maximum length for sending packets
static constexpr char MESSAGE_PAYLOAD_SPLIT[32] = "{MESSAGE_PAYLOAD_SPLIT}";
// Static arrays for topics and packets
static char topicQueue[MAX_QUEUE_LENGTH][MAX_TOPIC_LENGTH];
static char packetQueue[MAX_QUEUE_LENGTH][MAX_PACKET_LENGTH];
static char combinedData[MAX_TOPIC_LENGTH + MAX_PACKET_LENGTH + 32];
static char receiveQueue[MAX_QUEUE_LENGTH][16];
static int queueStart; // Index of the front of the queue
static int queueEnd; // Index of the back of the queue
static int queueCount; // Number of items in the queue
static int queueStartReceive;
static int queueEndReceive;
static int queueCountReceive;
};
#endif // ALEXA_UTILS_H