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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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