From 1313f47856d376aaa38ce11981dfb330f710c0ee Mon Sep 17 00:00:00 2001 From: David <43281665+chaos511@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:00:00 +0000 Subject: [PATCH] Add examples (brightness, color temp, temp sensor, blind control) + AlexaStatusMessage; update src/keywords/readme; drop library.json/properties --- examples/basicLight.cpp | 186 ++-- examples/blindControl.cpp | 254 +++--- examples/lightWithBrightness.cpp | 220 ++--- examples/lightWithColor.cpp | 0 examples/lightWithColorTemp.cpp | 244 ++--- examples/tempSensor.cpp | 134 +-- keywords.txt | 14 +- library.json | 59 -- library.properties | 10 - readme.md | 398 ++++---- src/Alex2ESP.cpp | 776 ++++++++-------- src/Alex2ESP.h | 208 ++--- src/AlexaDevice.cpp | 326 +++---- src/AlexaDevice.h | 408 ++++----- src/AlexaInterface.h | 1446 +++++++++++++++--------------- src/AlexaStatusMessage.cpp | 6 +- src/AlexaStatusMessage.h | 334 +++---- src/AlexaUtils.cpp | 440 ++++----- src/AlexaUtils.h | 166 ++-- 19 files changed, 2780 insertions(+), 2849 deletions(-) delete mode 100644 examples/lightWithColor.cpp delete mode 100644 library.json delete mode 100644 library.properties diff --git a/examples/basicLight.cpp b/examples/basicLight.cpp index a13d6c1..e3b7d76 100644 --- a/examples/basicLight.cpp +++ b/examples/basicLight.cpp @@ -1,93 +1,93 @@ -#include - -#ifdef ESP32 - #include -#else - #include -#endif - -#include - -// Define constants for WiFi and Alexa Client configuration -const char* WIFI_SSID = ""; - -const char* ALEXA_USERNAME = ""; -const char* ALEXA_PASSWORD = ""; -const char* ALEXA_ROOT_TOPIC = ""; - -// Create the Alexa client object -Alex2ESP alexClient; -AlexaDevice* device1; - -// Initial state for the light (PowerController) -PowerController outputState = PowerController::OFF; - -void setup() { - // Initialize the LED as output - pinMode(LED_BUILTIN, OUTPUT); - - // Initialize serial communication for debugging - Serial.begin(74880); - - // Connect to Wi-Fi - WiFi.mode(WIFI_STA); - WiFi.begin(WIFI_SSID); - - Serial.print("[WIFI] Connecting to WiFi"); - - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - delay(100); - } - - Serial.println(); - Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); - - // Initialize the Alexa client - alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); - - // Register a new device with a unique ID and name - device1 = alexClient.getDevice("Test Lamp", "ESP-01"); - - // Set the device's display category to LIGHT - device1->setDisplayCategory(DisplayCategory::LIGHT); - - // Add a power controller capability to the device - device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER); - - // Register event listener for "ReportState" directive - device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) { - // Build and send status report - device1->buildStatusMessage(directive["header"]["correlationToken"]) - .AddHealthProp(EndpointHealth::OK) - .AddPowerControllerProp(outputState) - .send(); - }); - - // Register event listener for "Event" directive to handle state changes - device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) { - if (type == AlexaInterfaceType::POWER_CONTROLLER) { - if (directive["header"]["name"] == "TurnOn") { - outputState = PowerController::ON; - Serial.println("Turning ON"); - } else if (directive["header"]["name"] == "TurnOff") { - outputState = PowerController::OFF; - Serial.println("Turning OFF"); - } - - // Send the updated state after change - device1->buildStatusMessage(directive["header"]["correlationToken"], true) - .AddHealthProp(EndpointHealth::OK) - .AddPowerControllerProp(outputState) - .send(); - } - }); -} - -void loop() { - // Process Alexa client communication - alexClient.loop(); - - // Update the state of the LED based on the power controller state - digitalWrite(LED_BUILTIN, outputState != PowerController::ON); -} +#include + +#ifdef ESP32 + #include +#else + #include +#endif + +#include + +// Define constants for WiFi and Alexa Client configuration +const char* WIFI_SSID = ""; + +const char* ALEXA_USERNAME = ""; +const char* ALEXA_PASSWORD = ""; +const char* ALEXA_ROOT_TOPIC = ""; + +// Create the Alexa client object +Alex2ESP alexClient; +AlexaDevice* device1; + +// Initial state for the light (PowerController) +PowerController outputState = PowerController::OFF; + +void setup() { + // Initialize the LED as output + pinMode(LED_BUILTIN, OUTPUT); + + // Initialize serial communication for debugging + Serial.begin(74880); + + // Connect to Wi-Fi + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID); + + Serial.print("[WIFI] Connecting to WiFi"); + + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(100); + } + + Serial.println(); + Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); + + // Initialize the Alexa client + alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); + + // Register a new device with a unique ID and name + device1 = alexClient.getDevice("Test Lamp", "ESP-01"); + + // Set the device's display category to LIGHT + device1->setDisplayCategory(DisplayCategory::LIGHT); + + // Add a power controller capability to the device + device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER); + + // Register event listener for "ReportState" directive + device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) { + // Build and send status report + device1->buildStatusMessage(directive["header"]["correlationToken"]) + .AddHealthProp(EndpointHealth::OK) + .AddPowerControllerProp(outputState) + .send(); + }); + + // Register event listener for "Event" directive to handle state changes + device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) { + if (type == AlexaInterfaceType::POWER_CONTROLLER) { + if (directive["header"]["name"] == "TurnOn") { + outputState = PowerController::ON; + Serial.println("Turning ON"); + } else if (directive["header"]["name"] == "TurnOff") { + outputState = PowerController::OFF; + Serial.println("Turning OFF"); + } + + // Send the updated state after change + device1->buildStatusMessage(directive["header"]["correlationToken"], true) + .AddHealthProp(EndpointHealth::OK) + .AddPowerControllerProp(outputState) + .send(); + } + }); +} + +void loop() { + // Process Alexa client communication + alexClient.loop(); + + // Update the state of the LED based on the power controller state + digitalWrite(LED_BUILTIN, outputState != PowerController::ON); +} diff --git a/examples/blindControl.cpp b/examples/blindControl.cpp index 9bb2bfc..50394d6 100644 --- a/examples/blindControl.cpp +++ b/examples/blindControl.cpp @@ -1,127 +1,127 @@ -#include - -#ifdef ESP32 -#include -#else -#include -#endif - -#include - -// Define constants for WiFi and Alexa Client configuration -const char *WIFI_SSID = ""; - -const char *ALEXA_USERNAME = ""; -const char *ALEXA_PASSWORD = ""; -const char *ALEXA_ROOT_TOPIC = ""; - -// Create the Alexa client object -Alex2ESP alexClient; -AlexaDevice *device1; - -// Initial state for the blinds (PowerController) -PowerController outputState = PowerController::OFF; - -void setup() -{ - // Initialize the LED as output - pinMode(LED_BUILTIN, OUTPUT); - - // Initialize serial communication for debugging - Serial.begin(74880); - - // Connect to Wi-Fi - WiFi.mode(WIFI_STA); - WiFi.begin(WIFI_SSID); - - Serial.print("[WIFI] Connecting to WiFi"); - - while (WiFi.status() != WL_CONNECTED) - { - Serial.print("."); - delay(100); - } - - Serial.println(); - Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); - - // Initialize the Alexa client - alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); - - String deviceName="Bedroom Blinds"; - - // Register a new device with a unique ID and name - device1 = alexClient.getDevice(deviceName, "ESP-01"); - - // Set the device's display category to LIGHT - device1->setDisplayCategory(DisplayCategory::LIGHT); - - //a power controller will allow us to "turn on/off" the blinds (not really usefull but is possable) - device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER); - - // Add a toggle controller capability to the device - AlexaInterface* toggleController=device1->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER); - - //toggle controllers are unique in that they require some additinal attrabutes by default we first need to set an instance name - toggleController->setInstance("ESP-01.Toggle"); - - //we also need to set a name for the interface, this is a sub device listed under the main device name - //to allow open/close commands as well as on/off commands we can use the same name as device name - toggleController->addFriendlyName(deviceName.c_str(),"en-US"); - - // Example of adding action mappings to map open/close to on/off - ActionMapping closeMapping({AlexaActions::Close}, "TurnOn"); - - ActionMapping openMapping({AlexaActions::Open}, "TurnOff"); - // the full supported list of alexa action mappings can be found on - // https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery-objects.html#action-mapping - //note, only 'generic controller interffaces support action mappings for a full list visit - //https://developer.amazon.com/en-US/docs/alexa/device-apis/generic-controllers.html - - - toggleController->addActionMapping(closeMapping); - toggleController->addActionMapping(openMapping); - - // Register event listener for "ReportState" directive - device1->registerEvent("ReportState", [](const JsonDocument &directive, const AlexaInterfaceType &type) - { - - Serial.println("Report state requested."); - - // Build and send status report - device1->buildStatusMessage(directive["header"]["correlationToken"]) - .AddHealthProp(EndpointHealth::OK) - .AddToggleControllerProp(outputState,"ESP-01.Toggle") - .AddPowerControllerProp(outputState) - .send(); }); - - // Register event listener for "Event" directive to handle state changes - device1->registerEvent("Event", [](const JsonDocument &directive, const AlexaInterfaceType &type) - { - if (type == AlexaInterfaceType::TOGGLE_CONTROLLER||type == AlexaInterfaceType::POWER_CONTROLLER) { - if (directive["header"]["name"] == "TurnOn") { - outputState = PowerController::ON; - Serial.println("Turning ON"); - } else if (directive["header"]["name"] == "TurnOff") { - outputState = PowerController::OFF; - Serial.println("Turning OFF"); - } - - // Send the updated state after change - device1->buildStatusMessage(directive["header"]["correlationToken"], true) - .AddHealthProp(EndpointHealth::OK) - .AddToggleControllerProp(outputState,"ESP-01.Toggle") - .AddPowerControllerProp(outputState) - .send(); - } }); -} - - -void loop() -{ - // Process Alexa client communication - alexClient.loop(); - - // Update the state of the LED based on the power controller state - digitalWrite(LED_BUILTIN, outputState != PowerController::ON); -} +#include + +#ifdef ESP32 +#include +#else +#include +#endif + +#include + +// Define constants for WiFi and Alexa Client configuration +const char *WIFI_SSID = ""; + +const char *ALEXA_USERNAME = ""; +const char *ALEXA_PASSWORD = ""; +const char *ALEXA_ROOT_TOPIC = ""; + +// Create the Alexa client object +Alex2ESP alexClient; +AlexaDevice *device1; + +// Initial state for the blinds (PowerController) +PowerController outputState = PowerController::OFF; + +void setup() +{ + // Initialize the LED as output + pinMode(LED_BUILTIN, OUTPUT); + + // Initialize serial communication for debugging + Serial.begin(74880); + + // Connect to Wi-Fi + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID); + + Serial.print("[WIFI] Connecting to WiFi"); + + while (WiFi.status() != WL_CONNECTED) + { + Serial.print("."); + delay(100); + } + + Serial.println(); + Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); + + // Initialize the Alexa client + alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); + + String deviceName="Bedroom Blinds"; + + // Register a new device with a unique ID and name + device1 = alexClient.getDevice(deviceName, "ESP-01"); + + // Set the device's display category to LIGHT + device1->setDisplayCategory(DisplayCategory::LIGHT); + + //a power controller will allow us to "turn on/off" the blinds (not really usefull but is possable) + device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER); + + // Add a toggle controller capability to the device + AlexaInterface* toggleController=device1->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER); + + //toggle controllers are unique in that they require some additinal attrabutes by default we first need to set an instance name + toggleController->setInstance("ESP-01.Toggle"); + + //we also need to set a name for the interface, this is a sub device listed under the main device name + //to allow open/close commands as well as on/off commands we can use the same name as device name + toggleController->addFriendlyName(deviceName.c_str(),"en-US"); + + // Example of adding action mappings to map open/close to on/off + ActionMapping closeMapping({AlexaActions::Close}, "TurnOn"); + + ActionMapping openMapping({AlexaActions::Open}, "TurnOff"); + // the full supported list of alexa action mappings can be found on + // https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery-objects.html#action-mapping + //note, only 'generic controller interffaces support action mappings for a full list visit + //https://developer.amazon.com/en-US/docs/alexa/device-apis/generic-controllers.html + + + toggleController->addActionMapping(closeMapping); + toggleController->addActionMapping(openMapping); + + // Register event listener for "ReportState" directive + device1->registerEvent("ReportState", [](const JsonDocument &directive, const AlexaInterfaceType &type) + { + + Serial.println("Report state requested."); + + // Build and send status report + device1->buildStatusMessage(directive["header"]["correlationToken"]) + .AddHealthProp(EndpointHealth::OK) + .AddToggleControllerProp(outputState,"ESP-01.Toggle") + .AddPowerControllerProp(outputState) + .send(); }); + + // Register event listener for "Event" directive to handle state changes + device1->registerEvent("Event", [](const JsonDocument &directive, const AlexaInterfaceType &type) + { + if (type == AlexaInterfaceType::TOGGLE_CONTROLLER||type == AlexaInterfaceType::POWER_CONTROLLER) { + if (directive["header"]["name"] == "TurnOn") { + outputState = PowerController::ON; + Serial.println("Turning ON"); + } else if (directive["header"]["name"] == "TurnOff") { + outputState = PowerController::OFF; + Serial.println("Turning OFF"); + } + + // Send the updated state after change + device1->buildStatusMessage(directive["header"]["correlationToken"], true) + .AddHealthProp(EndpointHealth::OK) + .AddToggleControllerProp(outputState,"ESP-01.Toggle") + .AddPowerControllerProp(outputState) + .send(); + } }); +} + + +void loop() +{ + // Process Alexa client communication + alexClient.loop(); + + // Update the state of the LED based on the power controller state + digitalWrite(LED_BUILTIN, outputState != PowerController::ON); +} diff --git a/examples/lightWithBrightness.cpp b/examples/lightWithBrightness.cpp index f554e07..e45fa04 100644 --- a/examples/lightWithBrightness.cpp +++ b/examples/lightWithBrightness.cpp @@ -1,110 +1,110 @@ -#include - -#ifdef ESP32 -#include -#else -#include -#endif - -#include - -// Define constants for WiFi and Alexa Client configuration -const char *WIFI_SSID = ""; - -const char *ALEXA_USERNAME = ""; -const char *ALEXA_PASSWORD = ""; -const char *ALEXA_ROOT_TOPIC = ""; - -// Create the Alexa client object -Alex2ESP alexClient; -AlexaDevice* device1; - -// Initial state for the light (PowerController) -PowerController outputState = PowerController::OFF; -int brightness=100; -void setup() { - // Initialize the LED as output - pinMode(LED_BUILTIN, OUTPUT); - - // Initialize serial communication for debugging - Serial.begin(74880); - - // Connect to Wi-Fi - WiFi.mode(WIFI_STA); - WiFi.begin(WIFI_SSID); - - Serial.print("[WIFI] Connecting to WiFi"); - - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - delay(100); - } - - Serial.println(); - Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); - - // Initialize the Alexa client - alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); - - // Register a new device with a unique ID and name - device1 = alexClient.getDevice("Test Lamp", "ESP-01"); - - // Set the device's display category to LIGHT - device1->setDisplayCategory(DisplayCategory::LIGHT); - - // Add a power controller capability to the device - device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER); - - // Add a brightness controller to the device to allow dimming - device1->addCapability(AlexaInterfaceType::BRIGHTNESS_CONTROLLER); - - // Register event listener for "ReportState" directive - device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) { - // Build and send status report - device1->buildStatusMessage(directive["header"]["correlationToken"]) - .AddHealthProp(EndpointHealth::OK) - .AddPowerControllerProp(outputState) - .AddBrightnessControllerProp(brightness) - .send(); - }); - - // Register event listener for "Event" directive to handle state changes - device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) { - if (type == AlexaInterfaceType::POWER_CONTROLLER) { - if (directive["header"]["name"] == "TurnOn") { - outputState = PowerController::ON; - Serial.println("Turning ON"); - } else if (directive["header"]["name"] == "TurnOff") { - outputState = PowerController::OFF; - Serial.println("Turning OFF"); - } - }else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) { - if(directive["header"]["name"]=="SetBrightness"){ - brightness=directive["payload"]["brightness"]; - Serial.println("setting brightness to: "+brightness); - outputState = PowerController::ON; - } - }else{ - return; - } - - // Send the updated state after change - device1->buildStatusMessage(directive["header"]["correlationToken"], true) - .AddHealthProp(EndpointHealth::OK) - .AddPowerControllerProp(outputState) - .AddBrightnessControllerProp(brightness) - .send(); - }); -} - -void loop() { - // Process Alexa client communication - alexClient.loop(); - - // Update the state of the LED based on the power controller state and brightness - if(outputState == PowerController::ON){ - analogWrite(LED_BUILTIN,map(brightness,0,100,255,0)); - }else{ - digitalWrite(LED_BUILTIN, true); - } -} +#include + +#ifdef ESP32 +#include +#else +#include +#endif + +#include + +// Define constants for WiFi and Alexa Client configuration +const char *WIFI_SSID = ""; + +const char *ALEXA_USERNAME = ""; +const char *ALEXA_PASSWORD = ""; +const char *ALEXA_ROOT_TOPIC = ""; + +// Create the Alexa client object +Alex2ESP alexClient; +AlexaDevice* device1; + +// Initial state for the light (PowerController) +PowerController outputState = PowerController::OFF; +int brightness=100; +void setup() { + // Initialize the LED as output + pinMode(LED_BUILTIN, OUTPUT); + + // Initialize serial communication for debugging + Serial.begin(74880); + + // Connect to Wi-Fi + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID); + + Serial.print("[WIFI] Connecting to WiFi"); + + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(100); + } + + Serial.println(); + Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); + + // Initialize the Alexa client + alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); + + // Register a new device with a unique ID and name + device1 = alexClient.getDevice("Test Lamp", "ESP-01"); + + // Set the device's display category to LIGHT + device1->setDisplayCategory(DisplayCategory::LIGHT); + + // Add a power controller capability to the device + device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER); + + // Add a brightness controller to the device to allow dimming + device1->addCapability(AlexaInterfaceType::BRIGHTNESS_CONTROLLER); + + // Register event listener for "ReportState" directive + device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) { + // Build and send status report + device1->buildStatusMessage(directive["header"]["correlationToken"]) + .AddHealthProp(EndpointHealth::OK) + .AddPowerControllerProp(outputState) + .AddBrightnessControllerProp(brightness) + .send(); + }); + + // Register event listener for "Event" directive to handle state changes + device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) { + if (type == AlexaInterfaceType::POWER_CONTROLLER) { + if (directive["header"]["name"] == "TurnOn") { + outputState = PowerController::ON; + Serial.println("Turning ON"); + } else if (directive["header"]["name"] == "TurnOff") { + outputState = PowerController::OFF; + Serial.println("Turning OFF"); + } + }else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) { + if(directive["header"]["name"]=="SetBrightness"){ + brightness=directive["payload"]["brightness"]; + Serial.println("setting brightness to: "+brightness); + outputState = PowerController::ON; + } + }else{ + return; + } + + // Send the updated state after change + device1->buildStatusMessage(directive["header"]["correlationToken"], true) + .AddHealthProp(EndpointHealth::OK) + .AddPowerControllerProp(outputState) + .AddBrightnessControllerProp(brightness) + .send(); + }); +} + +void loop() { + // Process Alexa client communication + alexClient.loop(); + + // Update the state of the LED based on the power controller state and brightness + if(outputState == PowerController::ON){ + analogWrite(LED_BUILTIN,map(brightness,0,100,255,0)); + }else{ + digitalWrite(LED_BUILTIN, true); + } +} diff --git a/examples/lightWithColor.cpp b/examples/lightWithColor.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/examples/lightWithColorTemp.cpp b/examples/lightWithColorTemp.cpp index 24374e0..bac6fc6 100644 --- a/examples/lightWithColorTemp.cpp +++ b/examples/lightWithColorTemp.cpp @@ -1,122 +1,122 @@ -#include - -#ifdef ESP32 -#include -#else -#include -#endif - -#include - -// Define constants for WiFi and Alexa Client configuration -const char *WIFI_SSID = ""; - -const char *ALEXA_USERNAME = ""; -const char *ALEXA_PASSWORD = ""; -const char *ALEXA_ROOT_TOPIC = ""; - -// Create the Alexa client object -Alex2ESP alexClient; -AlexaDevice* device1; - -// Initial state for the light (PowerController) -PowerController outputState = PowerController::OFF; -int brightness=100; -int colorTemp=0; -void setup() { - // Initialize the LED as output - pinMode(LED_BUILTIN, OUTPUT); - - // Initialize serial communication for debugging - Serial.begin(74880); - - // Connect to Wi-Fi - WiFi.mode(WIFI_STA); - WiFi.begin(WIFI_SSID); - - Serial.print("[WIFI] Connecting to WiFi"); - - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - delay(100); - } - - Serial.println(); - Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); - - // Initialize the Alexa client - alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); - - // Register a new device with a unique ID and name - device1 = alexClient.getDevice("Test Lamp ESP", "ESP-01"); - - // Set the device's display category to LIGHT - device1->setDisplayCategory(DisplayCategory::LIGHT); - - // Add a power controller capability to the device - device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER); - - // Add a brightness controller to the device to allow dimming - device1->addCapability(AlexaInterfaceType::BRIGHTNESS_CONTROLLER); - - // Add a color temperature controller - device1->addCapability(AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER); - - // Register event listener for "ReportState" directive - device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) { - // Build and send status report - device1->buildStatusMessage(directive["header"]["correlationToken"]) - .AddHealthProp(EndpointHealth::OK) - .AddPowerControllerProp(outputState) - .AddBrightnessControllerProp(brightness) - .AddColorTemperatureControllerProp(colorTemp) - .send(); - }); - - // Register event listener for "Event" directive to handle state changes - device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) { - if (type == AlexaInterfaceType::POWER_CONTROLLER) { - if (directive["header"]["name"] == "TurnOn") { - outputState = PowerController::ON; - Serial.println("Turning ON"); - } else if (directive["header"]["name"] == "TurnOff") { - outputState = PowerController::OFF; - Serial.println("Turning OFF"); - } - }else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) { - if(directive["header"]["name"]=="SetBrightness"){ - brightness=directive["payload"]["brightness"]; - Serial.println("setting brightness to: "+brightness); - outputState = PowerController::ON; - } - }else if (type == AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER) { - if(directive["header"]["name"]=="SetColorTemperature"){ - colorTemp=directive["payload"]["colorTemperatureInKelvin"]; - Serial.println("setting color temperature to: "+colorTemp); - outputState = PowerController::ON; - } - }else{ - return; - } - - // Send the updated state after change - device1->buildStatusMessage(directive["header"]["correlationToken"], true) - .AddHealthProp(EndpointHealth::OK) - .AddPowerControllerProp(outputState) - .AddBrightnessControllerProp(brightness) - .AddColorTemperatureControllerProp(colorTemp) - .send(); - }); -} - -void loop() { - // Process Alexa client communication - alexClient.loop(); - - // Update the state of the LED based on the power controller state and brightness - if(outputState == PowerController::ON){ - analogWrite(LED_BUILTIN,map(brightness,0,100,255,0)); - }else{ - digitalWrite(LED_BUILTIN, true); - } -} +#include + +#ifdef ESP32 +#include +#else +#include +#endif + +#include + +// Define constants for WiFi and Alexa Client configuration +const char *WIFI_SSID = ""; + +const char *ALEXA_USERNAME = ""; +const char *ALEXA_PASSWORD = ""; +const char *ALEXA_ROOT_TOPIC = ""; + +// Create the Alexa client object +Alex2ESP alexClient; +AlexaDevice* device1; + +// Initial state for the light (PowerController) +PowerController outputState = PowerController::OFF; +int brightness=100; +int colorTemp=0; +void setup() { + // Initialize the LED as output + pinMode(LED_BUILTIN, OUTPUT); + + // Initialize serial communication for debugging + Serial.begin(74880); + + // Connect to Wi-Fi + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID); + + Serial.print("[WIFI] Connecting to WiFi"); + + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(100); + } + + Serial.println(); + Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); + + // Initialize the Alexa client + alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); + + // Register a new device with a unique ID and name + device1 = alexClient.getDevice("Test Lamp ESP", "ESP-01"); + + // Set the device's display category to LIGHT + device1->setDisplayCategory(DisplayCategory::LIGHT); + + // Add a power controller capability to the device + device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER); + + // Add a brightness controller to the device to allow dimming + device1->addCapability(AlexaInterfaceType::BRIGHTNESS_CONTROLLER); + + // Add a color temperature controller + device1->addCapability(AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER); + + // Register event listener for "ReportState" directive + device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) { + // Build and send status report + device1->buildStatusMessage(directive["header"]["correlationToken"]) + .AddHealthProp(EndpointHealth::OK) + .AddPowerControllerProp(outputState) + .AddBrightnessControllerProp(brightness) + .AddColorTemperatureControllerProp(colorTemp) + .send(); + }); + + // Register event listener for "Event" directive to handle state changes + device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) { + if (type == AlexaInterfaceType::POWER_CONTROLLER) { + if (directive["header"]["name"] == "TurnOn") { + outputState = PowerController::ON; + Serial.println("Turning ON"); + } else if (directive["header"]["name"] == "TurnOff") { + outputState = PowerController::OFF; + Serial.println("Turning OFF"); + } + }else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) { + if(directive["header"]["name"]=="SetBrightness"){ + brightness=directive["payload"]["brightness"]; + Serial.println("setting brightness to: "+brightness); + outputState = PowerController::ON; + } + }else if (type == AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER) { + if(directive["header"]["name"]=="SetColorTemperature"){ + colorTemp=directive["payload"]["colorTemperatureInKelvin"]; + Serial.println("setting color temperature to: "+colorTemp); + outputState = PowerController::ON; + } + }else{ + return; + } + + // Send the updated state after change + device1->buildStatusMessage(directive["header"]["correlationToken"], true) + .AddHealthProp(EndpointHealth::OK) + .AddPowerControllerProp(outputState) + .AddBrightnessControllerProp(brightness) + .AddColorTemperatureControllerProp(colorTemp) + .send(); + }); +} + +void loop() { + // Process Alexa client communication + alexClient.loop(); + + // Update the state of the LED based on the power controller state and brightness + if(outputState == PowerController::ON){ + analogWrite(LED_BUILTIN,map(brightness,0,100,255,0)); + }else{ + digitalWrite(LED_BUILTIN, true); + } +} diff --git a/examples/tempSensor.cpp b/examples/tempSensor.cpp index 067cc8f..5850adb 100644 --- a/examples/tempSensor.cpp +++ b/examples/tempSensor.cpp @@ -1,67 +1,67 @@ -#include - -#ifdef ESP32 -#include -#else -#include -#endif - -#include - -// Define constants for WiFi and Alexa Client configuration -const char *WIFI_SSID = ""; - -const char *ALEXA_USERNAME = ""; -const char *ALEXA_PASSWORD = ""; -const char *ALEXA_ROOT_TOPIC = ""; - -// Create the Alexa client object -Alex2ESP alexClient; -AlexaDevice* device1; - -void setup() { - - // Initialize serial communication for debugging - Serial.begin(74880); - - // Connect to Wi-Fi - WiFi.mode(WIFI_STA); - WiFi.begin(WIFI_SSID); - - Serial.print("[WIFI] Connecting to WiFi"); - - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - delay(100); - } - - Serial.println(); - Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); - - // Initialize the Alexa client - alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); - - // Register a new device with a unique ID and name - device1 = alexClient.getDevice("ESP Temp", "ESP-01"); - - // Set the device's display category to TEMPERATURE_SENSOR - device1->setDisplayCategory(DisplayCategory::TEMPERATURE_SENSOR); - - // Add a temperature sensor capability to the device - device1->addCapability(AlexaInterfaceType::TEMPERATURE_SENSOR); - - // Register event listener for "ReportState" directive - device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) { - // Build and send status report - device1->buildStatusMessage(directive["header"]["correlationToken"]) - .AddHealthProp(EndpointHealth::OK) - .AddTemperatureSensorProp(TemperatureSensorScale::FAHRENHEIT,69) - .send(); - }); - -} - -void loop() { - // Process Alexa client communication - alexClient.loop(); -} +#include + +#ifdef ESP32 +#include +#else +#include +#endif + +#include + +// Define constants for WiFi and Alexa Client configuration +const char *WIFI_SSID = ""; + +const char *ALEXA_USERNAME = ""; +const char *ALEXA_PASSWORD = ""; +const char *ALEXA_ROOT_TOPIC = ""; + +// Create the Alexa client object +Alex2ESP alexClient; +AlexaDevice* device1; + +void setup() { + + // Initialize serial communication for debugging + Serial.begin(74880); + + // Connect to Wi-Fi + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID); + + Serial.print("[WIFI] Connecting to WiFi"); + + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(100); + } + + Serial.println(); + Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); + + // Initialize the Alexa client + alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); + + // Register a new device with a unique ID and name + device1 = alexClient.getDevice("ESP Temp", "ESP-01"); + + // Set the device's display category to TEMPERATURE_SENSOR + device1->setDisplayCategory(DisplayCategory::TEMPERATURE_SENSOR); + + // Add a temperature sensor capability to the device + device1->addCapability(AlexaInterfaceType::TEMPERATURE_SENSOR); + + // Register event listener for "ReportState" directive + device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) { + // Build and send status report + device1->buildStatusMessage(directive["header"]["correlationToken"]) + .AddHealthProp(EndpointHealth::OK) + .AddTemperatureSensorProp(TemperatureSensorScale::FAHRENHEIT,69) + .send(); + }); + +} + +void loop() { + // Process Alexa client communication + alexClient.loop(); +} diff --git a/keywords.txt b/keywords.txt index 82fdc99..e9e849d 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,7 +1,7 @@ -####################################### -# Syntax Coloring Map For Espalexa -####################################### - -####################################### -# Datatypes (KEYWORD1) -####################################### +####################################### +# Syntax Coloring Map For Espalexa +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### diff --git a/library.json b/library.json deleted file mode 100644 index d76f05a..0000000 --- a/library.json +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/library.properties b/library.properties deleted file mode 100644 index ec57917..0000000 --- a/library.properties +++ /dev/null @@ -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 \ No newline at end of file diff --git a/readme.md b/readme.md index 0d29a48..7ee3651 100644 --- a/readme.md +++ b/readme.md @@ -1,199 +1,199 @@ -# Alex2ESP - -Alex2ESP is a lightweight Arduino/PlatformIO library for integrating ESP8266 and ESP32 microcontrollers with Amazon Alexa smart home APIs. The library simplifies the process of creating Alexa-compatible devices using MQTT. - -For more details on how to configure Alex2ESP, visit [Alex2MQTT Documentation](https://alex2mqtt.stormysdream.club/). - ---- - - - -## Features -- Supports "All" Alexa smart home capabilities. -- Provides action mapping for advanced directive customization including Open,Close,Raise,and Lower commands. -- Allows custom JSON injection in both discovery and state reporting to allow for unsupported devices. -- Event-driven architecture for handling Alexa directives. ---- - -## Alternatives - - one of the most popular library for controlling esp like devices using alexa is [FauxmoESP](https://github.com/vintlabs/fauxmoESP) - however FauxmoESP works by emulating a light bulb so it is limited in what commands it accepts. - ---- - -## Quick Start -### Installation -Download the library as zip or via PlatformIO and include it in your project: -```cpp -#include -``` - ---- - -## Creating a Basic Device -### Example: Light Control - -Here’s how to create a simple device that controls a light: - -First create an instance of the alexa client and initilize it with your MQTT Credentials -```cpp -Alex2ESP alexClient; -``` - -Credentials can be found at https://alex2mqtt.stormysdream.club/ after logging in with amazon. -```cpp - alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); -``` - -Once initilized you can begin to add virtual devices, in this example we add a power controller to toggle a light -```cpp - - AlexaDevice* device1 = alexClient.getDevice("Test Lamp", "ESP-01"); - device1->setDisplayCategory(DisplayCategory::LIGHT); - device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER); - - device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) { - // Build and send status report - device1->buildStatusMessage(directive["header"]["correlationToken"]) - .AddHealthProp(EndpointHealth::OK) - .AddPowerControllerProp(outputState) - .send(); - }); - - // Register event listener for "Event" directive to handle state changes - device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) { - if (type == AlexaInterfaceType::POWER_CONTROLLER) { - if (directive["header"]["name"] == "TurnOn") { - outputState = PowerController::ON; - Serial.println("Turning ON"); - } else if (directive["header"]["name"] == "TurnOff") { - outputState = PowerController::OFF; - Serial.println("Turning OFF"); - } - - // Send the updated state after change - device1->buildStatusMessage(directive["header"]["correlationToken"], true) - .AddHealthProp(EndpointHealth::OK) - .AddPowerControllerProp(outputState) - .send(); - } - }); -``` ---- -### Example: Toggle Controller for Blinds -Alex2ESP also supports action mapping so we can map keywords such as "open" and "close" to a toggle controller -For a comprehensive list of Alexa actions and mappings, visit the [Alexa Developer Documentation](https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery-objects.html#action-mapping) - -```cpp -AlexaDevice* device = alexClient.getDevice("Bedroom Blinds", "ESP-01"); -device->setDisplayCategory(DisplayCategory::LIGHT); - -AlexaInterface* toggleController = device->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER); -toggleController->setInstance("ESP-01.Toggle"); -toggleController->addFriendlyName("Bedroom Blinds", "en-US"); - -ActionMapping closeMapping({AlexaActions::Close}, "TurnOn"); -ActionMapping openMapping({AlexaActions::Open}, "TurnOff"); -toggleController->addActionMapping(closeMapping); -toggleController->addActionMapping(openMapping); -``` - ---- - -## Interface Types -| Alexa Interface Type | Status | -|-----------------------------------------------------|-------------| -| AlexaInterfaceType::ENDPOINT_HEALTH | Fully Supported | -| AlexaInterfaceType::POWER_CONTROLLER | Fully Supported | -| AlexaInterfaceType::BRIGHTNESS_CONTROLLER | Fully Supported | -| AlexaInterfaceType::TOGGLE_CONTROLLER | Fully Supported | -| AlexaInterfaceType::TEMPERATURE_SENSOR | Fully Supported | -| AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER | Fully Supported | -| AlexaInterfaceType::AUTOMATION_MANAGEMENT | Supported* | -| AlexaInterfaceType::CHANNEL_CONTROLLER | Supported* | -| AlexaInterfaceType::COLOR_CONTROLLER | Supported* | -| AlexaInterfaceType::CONTACT_SENSOR | Supported* | -| AlexaInterfaceType::APPLICATION_STATE_REPORTER | Supported* | -| AlexaInterfaceType::AUDIO_PLAY_QUEUE | Supported* | -| AlexaInterfaceType::AUTHORIZATION_CONTROLLER | Supported* | -| AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA | Supported* | -| AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER | Supported* | -| AlexaInterfaceType::CAMERA_STREAM_CONTROLLER | Supported* | -| AlexaInterfaceType::COMMISSIONABLE | Supported* | -| AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER | Supported* | -| AlexaInterfaceType::COOKING | Supported* | -| AlexaInterfaceType::DATA_CONTROLLER | Supported* | -| AlexaInterfaceType::DEVICE_USAGE_ESTIMATION | Supported* | -| AlexaInterfaceType::DEVICE_USAGE_METER | Supported* | -| AlexaInterfaceType::DOORBELL_EVENT_SOURCE | Supported* | -| AlexaInterfaceType::EQUALIZER_CONTROLLER | Supported* | -| AlexaInterfaceType::INPUT_CONTROLLER | Supported* | -| AlexaInterfaceType::INVENTORY_LEVEL_SENSOR | Supported* | -| AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR | Supported* | -| AlexaInterfaceType::INVENTORY_USAGE_SENSOR | Supported* | -| AlexaInterfaceType::KEYPAD_CONTROLLER | Supported* | -| AlexaInterfaceType::LAUNCHER | Supported* | -| AlexaInterfaceType::LOCK_CONTROLLER | Supported* | -| AlexaInterfaceType::MEDIA_PLAYBACK | Supported* | -| AlexaInterfaceType::MEDIA_SEARCH | Supported* | -| AlexaInterfaceType::MODE_CONTROLLER | Supported* | -| AlexaInterfaceType::MOTION_SENSOR | Supported* | -| AlexaInterfaceType::PERCENTAGE_CONTROLLER | Supported* | -| AlexaInterfaceType::PLAYBACK_CONTROLLER | Supported* | -| AlexaInterfaceType::PLAYBACK_STATE_REPORTER | Supported* | -| AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE | Supported* | -| AlexaInterfaceType::RANGE_CONTROLLER | Supported* | -| AlexaInterfaceType::RECORD_CONTROLLER | Supported* | -| AlexaInterfaceType::REMOTE_VIDEO_PLAYER | Supported* | -| AlexaInterfaceType::RTC_SESSION_CONTROLLER | Supported* | -| AlexaInterfaceType::SCENE_CONTROLLER | Supported* | -| AlexaInterfaceType::SECURITY_PANEL_CONTROLLER | Supported* | -| AlexaInterfaceType::SEEK_CONTROLLER | Supported* | -| AlexaInterfaceType::SIMPLE_EVENT_SOURCE | Supported* | -| AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR | Supported* | -| AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER | Supported* | -| AlexaInterfaceType::SPEAKER | Supported* | -| AlexaInterfaceType::STEP_SPEAKER | Supported* | -| AlexaInterfaceType::THERMOSTAT_CONTROLLER | Supported* | -| AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION | Supported* | -| AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS | Supported* | -| AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE | Supported* | -| AlexaInterfaceType::TIME_HOLD_CONTROLLER | Supported* | -| AlexaInterfaceType::UI_CONTROLLER | Supported* | -| AlexaInterfaceType::USER_PREFERENCE | Supported* | -| AlexaInterfaceType::VIDEO_RECORDER | Supported* | -| AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER | Supported* | - - -(*Partial support or limited implementation advanced configuration is required) - ---- - - -## Advanced ussage -For devices with limited or partial support, Alex2ESP provides functions that allow you to attach custom JSON objects to the status report. For example, to report the status of a PowerController type, you can use the `AddPowerControllerProp` function. However, if a specific "add props" function does not exist for your use case, you can utilize the `AddContextProp` function to pass a custom JSON object. - -For instance, to manually report the state of a PowerController, you can use the following code: - -```cpp - JsonDocument doc; - - doc["namespace"] = "Alexa.PowerController"; - doc["name"] = "powerState"; - doc["value"] = "ON"; - doc["timeOfSample"] = "{REPLACE_WITH_DATETIME}"; // Alex2MQTT server wil do the replace - doc["uncertaintyInMilliseconds"] = 0; - AddContextProp(doc.as()) -``` - ---- - -## Contributing -Feel free to submit pull requests or issues for feature requests and bug fixes. - ---- - -## License -This project is licensed under the MIT License. See the LICENSE file for details. - +# Alex2ESP + +Alex2ESP is a lightweight Arduino/PlatformIO library for integrating ESP8266 and ESP32 microcontrollers with Amazon Alexa smart home APIs. The library simplifies the process of creating Alexa-compatible devices using MQTT. + +For more details on how to configure Alex2ESP, visit [Alex2MQTT Documentation](https://alex2mqtt.stormysdream.club/). + +--- + + + +## Features +- Supports "All" Alexa smart home capabilities. +- Provides action mapping for advanced directive customization including Open,Close,Raise,and Lower commands. +- Allows custom JSON injection in both discovery and state reporting to allow for unsupported devices. +- Event-driven architecture for handling Alexa directives. +--- + +## Alternatives + + one of the most popular library for controlling esp like devices using alexa is [FauxmoESP](https://github.com/vintlabs/fauxmoESP) + however FauxmoESP works by emulating a light bulb so it is limited in what commands it accepts. + +--- + +## Quick Start +### Installation +Download the library as zip or via PlatformIO and include it in your project: +```cpp +#include +``` + +--- + +## Creating a Basic Device +### Example: Light Control + +Here’s how to create a simple device that controls a light: + +First create an instance of the alexa client and initilize it with your MQTT Credentials +```cpp +Alex2ESP alexClient; +``` + +Credentials can be found at https://alex2mqtt.stormysdream.club/ after logging in with amazon. +```cpp + alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC); +``` + +Once initilized you can begin to add virtual devices, in this example we add a power controller to toggle a light +```cpp + + AlexaDevice* device1 = alexClient.getDevice("Test Lamp", "ESP-01"); + device1->setDisplayCategory(DisplayCategory::LIGHT); + device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER); + + device1->registerEvent("ReportState", [](const JsonDocument& directive, const AlexaInterfaceType& type) { + // Build and send status report + device1->buildStatusMessage(directive["header"]["correlationToken"]) + .AddHealthProp(EndpointHealth::OK) + .AddPowerControllerProp(outputState) + .send(); + }); + + // Register event listener for "Event" directive to handle state changes + device1->registerEvent("Event", [](const JsonDocument& directive, const AlexaInterfaceType& type) { + if (type == AlexaInterfaceType::POWER_CONTROLLER) { + if (directive["header"]["name"] == "TurnOn") { + outputState = PowerController::ON; + Serial.println("Turning ON"); + } else if (directive["header"]["name"] == "TurnOff") { + outputState = PowerController::OFF; + Serial.println("Turning OFF"); + } + + // Send the updated state after change + device1->buildStatusMessage(directive["header"]["correlationToken"], true) + .AddHealthProp(EndpointHealth::OK) + .AddPowerControllerProp(outputState) + .send(); + } + }); +``` +--- +### Example: Toggle Controller for Blinds +Alex2ESP also supports action mapping so we can map keywords such as "open" and "close" to a toggle controller +For a comprehensive list of Alexa actions and mappings, visit the [Alexa Developer Documentation](https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-discovery-objects.html#action-mapping) + +```cpp +AlexaDevice* device = alexClient.getDevice("Bedroom Blinds", "ESP-01"); +device->setDisplayCategory(DisplayCategory::LIGHT); + +AlexaInterface* toggleController = device->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER); +toggleController->setInstance("ESP-01.Toggle"); +toggleController->addFriendlyName("Bedroom Blinds", "en-US"); + +ActionMapping closeMapping({AlexaActions::Close}, "TurnOn"); +ActionMapping openMapping({AlexaActions::Open}, "TurnOff"); +toggleController->addActionMapping(closeMapping); +toggleController->addActionMapping(openMapping); +``` + +--- + +## Interface Types +| Alexa Interface Type | Status | +|-----------------------------------------------------|-------------| +| AlexaInterfaceType::ENDPOINT_HEALTH | Fully Supported | +| AlexaInterfaceType::POWER_CONTROLLER | Fully Supported | +| AlexaInterfaceType::BRIGHTNESS_CONTROLLER | Fully Supported | +| AlexaInterfaceType::TOGGLE_CONTROLLER | Fully Supported | +| AlexaInterfaceType::TEMPERATURE_SENSOR | Fully Supported | +| AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER | Fully Supported | +| AlexaInterfaceType::AUTOMATION_MANAGEMENT | Supported* | +| AlexaInterfaceType::CHANNEL_CONTROLLER | Supported* | +| AlexaInterfaceType::COLOR_CONTROLLER | Supported* | +| AlexaInterfaceType::CONTACT_SENSOR | Supported* | +| AlexaInterfaceType::APPLICATION_STATE_REPORTER | Supported* | +| AlexaInterfaceType::AUDIO_PLAY_QUEUE | Supported* | +| AlexaInterfaceType::AUTHORIZATION_CONTROLLER | Supported* | +| AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA | Supported* | +| AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER | Supported* | +| AlexaInterfaceType::CAMERA_STREAM_CONTROLLER | Supported* | +| AlexaInterfaceType::COMMISSIONABLE | Supported* | +| AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER | Supported* | +| AlexaInterfaceType::COOKING | Supported* | +| AlexaInterfaceType::DATA_CONTROLLER | Supported* | +| AlexaInterfaceType::DEVICE_USAGE_ESTIMATION | Supported* | +| AlexaInterfaceType::DEVICE_USAGE_METER | Supported* | +| AlexaInterfaceType::DOORBELL_EVENT_SOURCE | Supported* | +| AlexaInterfaceType::EQUALIZER_CONTROLLER | Supported* | +| AlexaInterfaceType::INPUT_CONTROLLER | Supported* | +| AlexaInterfaceType::INVENTORY_LEVEL_SENSOR | Supported* | +| AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR | Supported* | +| AlexaInterfaceType::INVENTORY_USAGE_SENSOR | Supported* | +| AlexaInterfaceType::KEYPAD_CONTROLLER | Supported* | +| AlexaInterfaceType::LAUNCHER | Supported* | +| AlexaInterfaceType::LOCK_CONTROLLER | Supported* | +| AlexaInterfaceType::MEDIA_PLAYBACK | Supported* | +| AlexaInterfaceType::MEDIA_SEARCH | Supported* | +| AlexaInterfaceType::MODE_CONTROLLER | Supported* | +| AlexaInterfaceType::MOTION_SENSOR | Supported* | +| AlexaInterfaceType::PERCENTAGE_CONTROLLER | Supported* | +| AlexaInterfaceType::PLAYBACK_CONTROLLER | Supported* | +| AlexaInterfaceType::PLAYBACK_STATE_REPORTER | Supported* | +| AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE | Supported* | +| AlexaInterfaceType::RANGE_CONTROLLER | Supported* | +| AlexaInterfaceType::RECORD_CONTROLLER | Supported* | +| AlexaInterfaceType::REMOTE_VIDEO_PLAYER | Supported* | +| AlexaInterfaceType::RTC_SESSION_CONTROLLER | Supported* | +| AlexaInterfaceType::SCENE_CONTROLLER | Supported* | +| AlexaInterfaceType::SECURITY_PANEL_CONTROLLER | Supported* | +| AlexaInterfaceType::SEEK_CONTROLLER | Supported* | +| AlexaInterfaceType::SIMPLE_EVENT_SOURCE | Supported* | +| AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR | Supported* | +| AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER | Supported* | +| AlexaInterfaceType::SPEAKER | Supported* | +| AlexaInterfaceType::STEP_SPEAKER | Supported* | +| AlexaInterfaceType::THERMOSTAT_CONTROLLER | Supported* | +| AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION | Supported* | +| AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS | Supported* | +| AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE | Supported* | +| AlexaInterfaceType::TIME_HOLD_CONTROLLER | Supported* | +| AlexaInterfaceType::UI_CONTROLLER | Supported* | +| AlexaInterfaceType::USER_PREFERENCE | Supported* | +| AlexaInterfaceType::VIDEO_RECORDER | Supported* | +| AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER | Supported* | + + +(*Partial support or limited implementation advanced configuration is required) + +--- + + +## Advanced ussage +For devices with limited or partial support, Alex2ESP provides functions that allow you to attach custom JSON objects to the status report. For example, to report the status of a PowerController type, you can use the `AddPowerControllerProp` function. However, if a specific "add props" function does not exist for your use case, you can utilize the `AddContextProp` function to pass a custom JSON object. + +For instance, to manually report the state of a PowerController, you can use the following code: + +```cpp + JsonDocument doc; + + doc["namespace"] = "Alexa.PowerController"; + doc["name"] = "powerState"; + doc["value"] = "ON"; + doc["timeOfSample"] = "{REPLACE_WITH_DATETIME}"; // Alex2MQTT server wil do the replace + doc["uncertaintyInMilliseconds"] = 0; + AddContextProp(doc.as()) +``` + +--- + +## Contributing +Feel free to submit pull requests or issues for feature requests and bug fixes. + +--- + +## License +This project is licensed under the MIT License. See the LICENSE file for details. + diff --git a/src/Alex2ESP.cpp b/src/Alex2ESP.cpp index 7a585ca..e12610c 100644 --- a/src/Alex2ESP.cpp +++ b/src/Alex2ESP.cpp @@ -1,389 +1,389 @@ -/* - * @title Alex2ESP Library - * @version 1.0.0 - * @author David - * @license MIT - * @contributors chaos511 - * - * @description The Alex2ESP library is a companion to the Alex2MQTT Alexa Skill, - * providing seamless integration between ESP-based devices and the Alex2MQTT server. - * This library connects to alex2mqtt.stormysdream.club, where the skill is hosted, - * allowing your devices to communicate effortlessly with the Alexa Voice Service - * using MQTT as the backbone. - */ -#include "Alex2ESP.h" - -Alex2ESP::Alex2ESP() - : rootTopic(), mqttUsername(nullptr), mqttPassword(nullptr), _state(Alex2ESPState::UNINITIALIZED), _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {} - -void Alex2ESP::begin(const char *rootTopic, const char *username, const char *password) -{ - - _state = Alex2ESPState::INITIALIZED; - _disconnectReason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED; - this->rootTopic = rootTopic; - this->mqttUsername = username; - this->mqttPassword = password; - - discoverTopic = (String(this->rootTopic) + String("/discover")); - discoverTopicSend = (String(this->rootTopic) + String("/discover_r")); - - TopicESP = (String(this->rootTopic) + String("/+/alexaDirective_e")); - - // Log the root topic and credentials - AlexaUtils::log("Setting root topic: "); - AlexaUtils::log(rootTopic); - AlexaUtils::log(" with creds "); - AlexaUtils::log(username); - AlexaUtils::log(":"); - AlexaUtils::logln(password); - AlexaUtils::printMemoryInfo(); - - // Set up MQTT client callbacks - mqttClient.onConnect([this](bool sessionPresent) - { this->onMqttConnect(sessionPresent); }); - mqttClient.onDisconnect([this](AsyncMqttClientDisconnectReason reason) - { this->onMqttDisconnect(reason); }); - mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) - { this->onSubscribe(packetId, qos); }); - mqttClient.onMessage([this](char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) - { this->onMessage(topic, payload, properties, len, index, total); }); - - // Configure MQTT client - mqttClient.setServer(mqttServer, mqttPort); - mqttClient.setCredentials(username, password); - - // TODO: Throw error if dns fails or has no internet access? - _state = Alex2ESPState::CONNECTING; - mqttClient.connect(); -} - -void Alex2ESP::onMqttConnect(bool sessionPresent) -{ - _state = Alex2ESPState::SUBSCRIBING; - - AlexaUtils::log("Connected to MQTT server, Now subscribing to: "); - AlexaUtils::log(discoverTopic.c_str()); - AlexaUtils::log(" "); - AlexaUtils::logln(TopicESP.c_str()); - - mqttClient.subscribe(discoverTopic.c_str(), 1); - mqttClient.subscribe(TopicESP.c_str(), 1); -} - -void Alex2ESP::onSubscribe(uint16_t packetId, uint8_t qos) -{ - _state = Alex2ESPState::CONNECTED; - - AlexaUtils::log("Subscribed with packetId: "); - AlexaUtils::log(packetId); - AlexaUtils::log(" and QoS: "); - AlexaUtils::logln(qos); -} - -// Internal: Handle disconnection -// TODO: auto reconnect? -void Alex2ESP::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) -{ - _state = Alex2ESPState::DISCONNECTED; - _disconnectReason = reason; -} - -void Alex2ESP::onMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total) -{ - AlexaUtils::logln("OnMessage Start"); - AlexaUtils::printMemoryInfo(); - - clearToSend = false; - - if (strcmp(topic, discoverTopic.c_str()) == 0) - { - - // boolean successful = messagePackExtractor(topic, payload, length, index, total); - - // if (successful && inputDoc["name"] == "Discover") - // { - for (const AlexaDevice &device : devices) - { - AlexaUtils::logln("Getting device json"); - JsonDocument jsonData = device.getDeviceJSON(); - String jsonString; - serializeJson(jsonData, jsonString); - jsonData.clear(); - AlexaUtils::enqueue(jsonString.c_str(), discoverTopicSend.c_str()); - } - // } - } - else - { - for (auto &device : devices) - { - String directiveTopic = String(rootTopic) + "/" + device.getEndpointId() + "/alexaDirective_e"; - - if (strcmp(topic, directiveTopic.c_str()) == 0) - { - AlexaUtils::logln(topic); - payload[length] = '\0'; - AlexaUtils::logln(payload); - AlexaUtils::enqueueReceive(payload); - } - } - } - AlexaUtils::logln("OnMessage End"); - AlexaUtils::printMemoryInfo(); - clearToSend = true; -} - -// boolean Alex2ESP::messagePackExtractor(char *topic, char *payload, size_t length, size_t index, size_t total) -// { -// payload[length] = '\0'; // Truncate the payload - -// uint8_t messageId = payload[0]; // Message ID (byte 0) -// uint8_t fragmentId = payload[1]; // Fragment ID (byte 1) -// uint8_t totalFragments = payload[2]; // Total Fragments (byte 2) - -// if (totalFragments > MAX_FRAGMENT_COUNT) -// { -// return false; //Message too long, skip. -// } - -// if (fragmentId == 0) -// { -// clearCache(); -// cache.totalParts = totalFragments; -// cache.messageId = String(messageId); -// } - -// if (cache.messageId != String(messageId)) -// { -// clearCache(); -// cache.messageId = String(messageId); -// cache.totalParts = totalFragments; -// } - -// if (fragmentId < cache.totalParts) -// { -// strncpy(cache.message + cache.fragmentOffset, payload + 3, length - 3); // Copy the fragment data -// cache.fragmentOffset=cache.fragmentOffset+(length-3); // -// cache.receivedParts++; -// Serial.printf("Received fragment %d of %d, length %d receivedParts: %d \n", fragmentId + 1, totalFragments, length, cache.receivedParts); - -// if (isMessageComplete()) -// { -// AlexaUtils::logln("Full message received."); -// AlexaUtils::logln(cache.message); - -// DeserializationError error = deserializeJson(inputDoc, cache.message); -// if (error) -// { -// AlexaUtils::log("Failed to parse message: "); -// AlexaUtils::logln(String(error.f_str())); -// clearCache(); -// return false; -// } -// else -// { -// AlexaUtils::logln("JSON message parsed successfully!"); -// clearCache(true); -// return true; -// } -// } -// } -// return false; -// } - -AlexaDevice *Alex2ESP::getDevice(const String &name, const String &endpointId) -{ - // Check if a device with the given endpointId already exists - for (auto &device : devices) - { - if (device.getEndpointId() == endpointId) - { - return &device; // Return the existing device - } - } - - // If the device doesn't exist, create a new one - devices.emplace_back(name, rootTopic, endpointId); - - Serial.print("Created new device: "); - Serial.print(name); - Serial.print(" with endpointId: "); - Serial.println(endpointId); - - // Return a pointer to the newly created device - return &devices.back(); -} - -// void Alex2ESP::clearCache(boolean skipDocClear) -// { -// if (!skipDocClear) -// { -// inputDoc.clear(); -// } -// cache.receivedParts = 0; // Reset the count of received parts -// cache.messageId = ""; // Clear the message ID -// cache.totalParts = 0; // Reset the total parts count -// cache.fragmentOffset = 0; -// memset(cache.message, 0, sizeof(cache.message)); // Clear the message buffer -// } - -// bool Alex2ESP::isMessageComplete() -// { -// return cache.receivedParts == cache.totalParts; // Check if we have received all parts -// } - -Alex2ESPState Alex2ESP::getState() const -{ - return _state; -} - -AsyncMqttClientDisconnectReason Alex2ESP::getDisconnectReason() const -{ - return _disconnectReason; -} - -void Alex2ESP::handleMqttReconnection() -{ - if (!mqttClient.connected() && _disconnectReason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) - { - if (millis() - lastReconnectTime > 5000) - { - lastReconnectTime = millis(); - reconnectAttempt++; - mqttClient.connect(); - } - } -} -void Alex2ESP::processHttpPost() -{ - if (!AlexaUtils::isQueueEmpty()) - { - AlexaUtils::logln("processHttpPost"); - - const char *payload = AlexaUtils::dequeueVals(false); - - httpPOST.begin(wifiPOST, "http://alex2mqtt.stormysdream.club/Alex2ESP"); - httpPOST.setAuthorization(mqttUsername, mqttPassword); - httpPOST.addHeader("Content-Type", "text/plain"); - - AlexaUtils::log("Sending Data: "); - AlexaUtils::logln(payload); - - int httpResponseCode = httpPOST.POST(payload); - - if (httpResponseCode != 200) - { - - if (retryCountPOST >= MAX_RETRY_COUNT) - { - AlexaUtils::dequeueVals(true); - } - - AlexaUtils::log("POST Error code: "); - AlexaUtils::logln(httpResponseCode); - retryCountPOST++; - if (httpResponseCode > 0) - { - String response = httpPOST.getString(); - Serial.println("Response:"); - Serial.println(response); - } - } - else - { - retryCountPOST = 0; - AlexaUtils::dequeueVals(true); - } - httpPOST.end(); - } -} -void Alex2ESP::processHttpGet() -{ - if (!AlexaUtils::isReceiveQueueEmpty()) - { - AlexaUtils::logln("processHttpGet"); - - String uuid; - AlexaUtils::dequeueReceive(uuid, false); - AlexaUtils::log("Sending get request for uuid: "); - // AlexaUtils::logln(uuid); - - httpGET.begin(wifiGET, "http://alex2mqtt.stormysdream.club/Alex2ESP/" + uuid); - httpGET.setAuthorization(mqttUsername, mqttPassword); - int httpResponseCode = httpGET.GET(); - - Serial.print("HTTP Response code: "); - Serial.println(httpResponseCode); - if (httpResponseCode != 200) - { - Serial.print("Error code: "); - Serial.println(httpResponseCode); - if (retryCountGET >= MAX_RETRY_COUNT) - { - AlexaUtils::dequeueReceive(uuid, true); - } - - retryCountGET++; - } - else - { - retryCountGET = 0; - AlexaUtils::dequeueReceive(uuid, true); - String payloadStr = httpGET.getString(); - size_t length = payloadStr.length(); - - if (length < AlexaUtils::MAX_PAYLOAD_LENGTH - 1) - { - payloadStr.toCharArray(AlexaUtils::receivePayload, length + 1); - payloadStr = ""; - AlexaUtils::receivePayload[length + 1] = '\0'; - // AlexaUtils::logln(AlexaUtils::receivePayload); - - DeserializationError error = deserializeJson(inputDoc, AlexaUtils::receivePayload); - if (error) - { - AlexaUtils::log("Failed to parse message: "); - AlexaUtils::logln(String(error.f_str())); - inputDoc.clear(); - } - else - { - AlexaUtils::logln("JSON message parsed successfully!"); - - for (auto &device : devices) - { - if (strcmp(device.getEndpointId().c_str(), inputDoc["directive"]["endpoint"]["endpointId"]) == 0) - { - serializeJson(inputDoc, Serial); - Serial.println(); - device.triggerEvent("DirectiveReceived", inputDoc["directive"], AlexaInterfaceType::UNKNOWN); - - if (inputDoc["directive"]["header"]["name"] == "ReportState") - { - device.triggerEvent("ReportState", inputDoc["directive"], AlexaInterfaceType::UNKNOWN); - } - else - { - device.triggerEvent("Event", inputDoc["directive"], AlexaInterfaceUtils::fromString(inputDoc["directive"]["header"]["namespace"])); - } - } - } - } - } - } - httpGET.end(); - } -} - -void Alex2ESP::loop() -{ - - handleMqttReconnection(); - if(clearToSend){ - processHttpPost(); - processHttpGet(); - } - - return; +/* + * @title Alex2ESP Library + * @version 1.0.0 + * @author David + * @license MIT + * @contributors chaos511 + * + * @description The Alex2ESP library is a companion to the Alex2MQTT Alexa Skill, + * providing seamless integration between ESP-based devices and the Alex2MQTT server. + * This library connects to alex2mqtt.stormysdream.club, where the skill is hosted, + * allowing your devices to communicate effortlessly with the Alexa Voice Service + * using MQTT as the backbone. + */ +#include "Alex2ESP.h" + +Alex2ESP::Alex2ESP() + : rootTopic(), mqttUsername(nullptr), mqttPassword(nullptr), _state(Alex2ESPState::UNINITIALIZED), _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {} + +void Alex2ESP::begin(const char *rootTopic, const char *username, const char *password) +{ + + _state = Alex2ESPState::INITIALIZED; + _disconnectReason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED; + this->rootTopic = rootTopic; + this->mqttUsername = username; + this->mqttPassword = password; + + discoverTopic = (String(this->rootTopic) + String("/discover")); + discoverTopicSend = (String(this->rootTopic) + String("/discover_r")); + + TopicESP = (String(this->rootTopic) + String("/+/alexaDirective_e")); + + // Log the root topic and credentials + AlexaUtils::log("Setting root topic: "); + AlexaUtils::log(rootTopic); + AlexaUtils::log(" with creds "); + AlexaUtils::log(username); + AlexaUtils::log(":"); + AlexaUtils::logln(password); + AlexaUtils::printMemoryInfo(); + + // Set up MQTT client callbacks + mqttClient.onConnect([this](bool sessionPresent) + { this->onMqttConnect(sessionPresent); }); + mqttClient.onDisconnect([this](AsyncMqttClientDisconnectReason reason) + { this->onMqttDisconnect(reason); }); + mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) + { this->onSubscribe(packetId, qos); }); + mqttClient.onMessage([this](char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) + { this->onMessage(topic, payload, properties, len, index, total); }); + + // Configure MQTT client + mqttClient.setServer(mqttServer, mqttPort); + mqttClient.setCredentials(username, password); + + // TODO: Throw error if dns fails or has no internet access? + _state = Alex2ESPState::CONNECTING; + mqttClient.connect(); +} + +void Alex2ESP::onMqttConnect(bool sessionPresent) +{ + _state = Alex2ESPState::SUBSCRIBING; + + AlexaUtils::log("Connected to MQTT server, Now subscribing to: "); + AlexaUtils::log(discoverTopic.c_str()); + AlexaUtils::log(" "); + AlexaUtils::logln(TopicESP.c_str()); + + mqttClient.subscribe(discoverTopic.c_str(), 1); + mqttClient.subscribe(TopicESP.c_str(), 1); +} + +void Alex2ESP::onSubscribe(uint16_t packetId, uint8_t qos) +{ + _state = Alex2ESPState::CONNECTED; + + AlexaUtils::log("Subscribed with packetId: "); + AlexaUtils::log(packetId); + AlexaUtils::log(" and QoS: "); + AlexaUtils::logln(qos); +} + +// Internal: Handle disconnection +// TODO: auto reconnect? +void Alex2ESP::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) +{ + _state = Alex2ESPState::DISCONNECTED; + _disconnectReason = reason; +} + +void Alex2ESP::onMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total) +{ + AlexaUtils::logln("OnMessage Start"); + AlexaUtils::printMemoryInfo(); + + clearToSend = false; + + if (strcmp(topic, discoverTopic.c_str()) == 0) + { + + // boolean successful = messagePackExtractor(topic, payload, length, index, total); + + // if (successful && inputDoc["name"] == "Discover") + // { + for (const AlexaDevice &device : devices) + { + AlexaUtils::logln("Getting device json"); + JsonDocument jsonData = device.getDeviceJSON(); + String jsonString; + serializeJson(jsonData, jsonString); + jsonData.clear(); + AlexaUtils::enqueue(jsonString.c_str(), discoverTopicSend.c_str()); + } + // } + } + else + { + for (auto &device : devices) + { + String directiveTopic = String(rootTopic) + "/" + device.getEndpointId() + "/alexaDirective_e"; + + if (strcmp(topic, directiveTopic.c_str()) == 0) + { + AlexaUtils::logln(topic); + payload[length] = '\0'; + AlexaUtils::logln(payload); + AlexaUtils::enqueueReceive(payload); + } + } + } + AlexaUtils::logln("OnMessage End"); + AlexaUtils::printMemoryInfo(); + clearToSend = true; +} + +// boolean Alex2ESP::messagePackExtractor(char *topic, char *payload, size_t length, size_t index, size_t total) +// { +// payload[length] = '\0'; // Truncate the payload + +// uint8_t messageId = payload[0]; // Message ID (byte 0) +// uint8_t fragmentId = payload[1]; // Fragment ID (byte 1) +// uint8_t totalFragments = payload[2]; // Total Fragments (byte 2) + +// if (totalFragments > MAX_FRAGMENT_COUNT) +// { +// return false; //Message too long, skip. +// } + +// if (fragmentId == 0) +// { +// clearCache(); +// cache.totalParts = totalFragments; +// cache.messageId = String(messageId); +// } + +// if (cache.messageId != String(messageId)) +// { +// clearCache(); +// cache.messageId = String(messageId); +// cache.totalParts = totalFragments; +// } + +// if (fragmentId < cache.totalParts) +// { +// strncpy(cache.message + cache.fragmentOffset, payload + 3, length - 3); // Copy the fragment data +// cache.fragmentOffset=cache.fragmentOffset+(length-3); // +// cache.receivedParts++; +// Serial.printf("Received fragment %d of %d, length %d receivedParts: %d \n", fragmentId + 1, totalFragments, length, cache.receivedParts); + +// if (isMessageComplete()) +// { +// AlexaUtils::logln("Full message received."); +// AlexaUtils::logln(cache.message); + +// DeserializationError error = deserializeJson(inputDoc, cache.message); +// if (error) +// { +// AlexaUtils::log("Failed to parse message: "); +// AlexaUtils::logln(String(error.f_str())); +// clearCache(); +// return false; +// } +// else +// { +// AlexaUtils::logln("JSON message parsed successfully!"); +// clearCache(true); +// return true; +// } +// } +// } +// return false; +// } + +AlexaDevice *Alex2ESP::getDevice(const String &name, const String &endpointId) +{ + // Check if a device with the given endpointId already exists + for (auto &device : devices) + { + if (device.getEndpointId() == endpointId) + { + return &device; // Return the existing device + } + } + + // If the device doesn't exist, create a new one + devices.emplace_back(name, rootTopic, endpointId); + + Serial.print("Created new device: "); + Serial.print(name); + Serial.print(" with endpointId: "); + Serial.println(endpointId); + + // Return a pointer to the newly created device + return &devices.back(); +} + +// void Alex2ESP::clearCache(boolean skipDocClear) +// { +// if (!skipDocClear) +// { +// inputDoc.clear(); +// } +// cache.receivedParts = 0; // Reset the count of received parts +// cache.messageId = ""; // Clear the message ID +// cache.totalParts = 0; // Reset the total parts count +// cache.fragmentOffset = 0; +// memset(cache.message, 0, sizeof(cache.message)); // Clear the message buffer +// } + +// bool Alex2ESP::isMessageComplete() +// { +// return cache.receivedParts == cache.totalParts; // Check if we have received all parts +// } + +Alex2ESPState Alex2ESP::getState() const +{ + return _state; +} + +AsyncMqttClientDisconnectReason Alex2ESP::getDisconnectReason() const +{ + return _disconnectReason; +} + +void Alex2ESP::handleMqttReconnection() +{ + if (!mqttClient.connected() && _disconnectReason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) + { + if (millis() - lastReconnectTime > 5000) + { + lastReconnectTime = millis(); + reconnectAttempt++; + mqttClient.connect(); + } + } +} +void Alex2ESP::processHttpPost() +{ + if (!AlexaUtils::isQueueEmpty()) + { + AlexaUtils::logln("processHttpPost"); + + const char *payload = AlexaUtils::dequeueVals(false); + + httpPOST.begin(wifiPOST, "http://alex2mqtt.stormysdream.club/Alex2ESP"); + httpPOST.setAuthorization(mqttUsername, mqttPassword); + httpPOST.addHeader("Content-Type", "text/plain"); + + AlexaUtils::log("Sending Data: "); + AlexaUtils::logln(payload); + + int httpResponseCode = httpPOST.POST(payload); + + if (httpResponseCode != 200) + { + + if (retryCountPOST >= MAX_RETRY_COUNT) + { + AlexaUtils::dequeueVals(true); + } + + AlexaUtils::log("POST Error code: "); + AlexaUtils::logln(httpResponseCode); + retryCountPOST++; + if (httpResponseCode > 0) + { + String response = httpPOST.getString(); + Serial.println("Response:"); + Serial.println(response); + } + } + else + { + retryCountPOST = 0; + AlexaUtils::dequeueVals(true); + } + httpPOST.end(); + } +} +void Alex2ESP::processHttpGet() +{ + if (!AlexaUtils::isReceiveQueueEmpty()) + { + AlexaUtils::logln("processHttpGet"); + + String uuid; + AlexaUtils::dequeueReceive(uuid, false); + AlexaUtils::log("Sending get request for uuid: "); + // AlexaUtils::logln(uuid); + + httpGET.begin(wifiGET, "http://alex2mqtt.stormysdream.club/Alex2ESP/" + uuid); + httpGET.setAuthorization(mqttUsername, mqttPassword); + int httpResponseCode = httpGET.GET(); + + Serial.print("HTTP Response code: "); + Serial.println(httpResponseCode); + if (httpResponseCode != 200) + { + Serial.print("Error code: "); + Serial.println(httpResponseCode); + if (retryCountGET >= MAX_RETRY_COUNT) + { + AlexaUtils::dequeueReceive(uuid, true); + } + + retryCountGET++; + } + else + { + retryCountGET = 0; + AlexaUtils::dequeueReceive(uuid, true); + String payloadStr = httpGET.getString(); + size_t length = payloadStr.length(); + + if (length < AlexaUtils::MAX_PAYLOAD_LENGTH - 1) + { + payloadStr.toCharArray(AlexaUtils::receivePayload, length + 1); + payloadStr = ""; + AlexaUtils::receivePayload[length + 1] = '\0'; + // AlexaUtils::logln(AlexaUtils::receivePayload); + + DeserializationError error = deserializeJson(inputDoc, AlexaUtils::receivePayload); + if (error) + { + AlexaUtils::log("Failed to parse message: "); + AlexaUtils::logln(String(error.f_str())); + inputDoc.clear(); + } + else + { + AlexaUtils::logln("JSON message parsed successfully!"); + + for (auto &device : devices) + { + if (strcmp(device.getEndpointId().c_str(), inputDoc["directive"]["endpoint"]["endpointId"]) == 0) + { + serializeJson(inputDoc, Serial); + Serial.println(); + device.triggerEvent("DirectiveReceived", inputDoc["directive"], AlexaInterfaceType::UNKNOWN); + + if (inputDoc["directive"]["header"]["name"] == "ReportState") + { + device.triggerEvent("ReportState", inputDoc["directive"], AlexaInterfaceType::UNKNOWN); + } + else + { + device.triggerEvent("Event", inputDoc["directive"], AlexaInterfaceUtils::fromString(inputDoc["directive"]["header"]["namespace"])); + } + } + } + } + } + } + httpGET.end(); + } +} + +void Alex2ESP::loop() +{ + + handleMqttReconnection(); + if(clearToSend){ + processHttpPost(); + processHttpGet(); + } + + return; } \ No newline at end of file diff --git a/src/Alex2ESP.h b/src/Alex2ESP.h index fd96667..0ef0a50 100644 --- a/src/Alex2ESP.h +++ b/src/Alex2ESP.h @@ -1,104 +1,104 @@ -/* - * @title Alex2ESP Library - * @version 1.0.0 - * @author David - * @license MIT - * @contributors chaos511 - * - * @description The Alex2ESP library is a companion to the Alex2MQTT Alexa Skill, - * providing seamless integration between ESP-based devices and the Alex2MQTT server. - * This library connects to alex2mqtt.stormysdream.club, where the skill is hosted, - * allowing your devices to communicate effortlessly with the Alexa Voice Service - * using MQTT as the backbone. - */ - -#ifndef ALEX2ESP_H -#define ALEX2ESP_H -class AlexaDevice; - -#include -#include -#include -#include "AlexaDevice.h" -#include -#include -#include - -#include - - -enum class Alex2ESPState -{ - UNINITIALIZED, - INITIALIZED, - CONNECTING, - SUBSCRIBING, - CONNECTED, - DISCONNECTED -}; - -class Alex2ESP -{ -public: - // Constructor - Alex2ESP(); - - // Begin function for initialization - void begin(const char *rootTopic, const char *username, const char *password); - Alex2ESPState getState() const; - void loop(); - - AsyncMqttClientDisconnectReason getDisconnectReason() const; - - AlexaDevice *getDevice(const String &name, const String &endpointId); - void messageSplitAndSend(String messagepackString, String topic); - -private: - static const int MAX_RETRY_COUNT = 2; // Define maximum retry count - int retryCountPOST=0; - int retryCountGET=0; - - boolean clearToSend=false; - AsyncMqttClient mqttClient; // MQTT client instance - String rootTopic; // Root topic for communication - String discoverTopic; // The topic we listen on for discovery messages - String discoverTopicSend; // The topic we send discovery messages - - String TopicESP; // The topic we listen on for esp messages - - const char *mqttUsername; // Username for authentication - const char *mqttPassword; // Password for authentication - - unsigned long lastReconnectTime; - int reconnectAttempt; - - HTTPClient httpGET; - HTTPClient httpPOST; - - WiFiClient wifiGET; - WiFiClient wifiPOST; - JsonDocument inputDoc; - - std::vector devices; // Collection of devices - - Alex2ESPState _state; - AsyncMqttClientDisconnectReason _disconnectReason; - - // MQTT connection details - const char *mqttServer = "alex2mqtt.stormysdream.club"; - uint16_t mqttPort = 1883; - - // Internal event handlers - void onMqttConnect(bool sessionPresent); - void onMqttDisconnect(AsyncMqttClientDisconnectReason reason); - void onSubscribe(uint16_t packetId, uint8_t qos); - void onMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total); - - //loop processing function - void handleMqttReconnection(); - void processHttpPost(); - void processHttpGet(); - -}; - -#endif // ALEX2ESP_H +/* + * @title Alex2ESP Library + * @version 1.0.0 + * @author David + * @license MIT + * @contributors chaos511 + * + * @description The Alex2ESP library is a companion to the Alex2MQTT Alexa Skill, + * providing seamless integration between ESP-based devices and the Alex2MQTT server. + * This library connects to alex2mqtt.stormysdream.club, where the skill is hosted, + * allowing your devices to communicate effortlessly with the Alexa Voice Service + * using MQTT as the backbone. + */ + +#ifndef ALEX2ESP_H +#define ALEX2ESP_H +class AlexaDevice; + +#include +#include +#include +#include "AlexaDevice.h" +#include +#include +#include + +#include + + +enum class Alex2ESPState +{ + UNINITIALIZED, + INITIALIZED, + CONNECTING, + SUBSCRIBING, + CONNECTED, + DISCONNECTED +}; + +class Alex2ESP +{ +public: + // Constructor + Alex2ESP(); + + // Begin function for initialization + void begin(const char *rootTopic, const char *username, const char *password); + Alex2ESPState getState() const; + void loop(); + + AsyncMqttClientDisconnectReason getDisconnectReason() const; + + AlexaDevice *getDevice(const String &name, const String &endpointId); + void messageSplitAndSend(String messagepackString, String topic); + +private: + static const int MAX_RETRY_COUNT = 2; // Define maximum retry count + int retryCountPOST=0; + int retryCountGET=0; + + boolean clearToSend=false; + AsyncMqttClient mqttClient; // MQTT client instance + String rootTopic; // Root topic for communication + String discoverTopic; // The topic we listen on for discovery messages + String discoverTopicSend; // The topic we send discovery messages + + String TopicESP; // The topic we listen on for esp messages + + const char *mqttUsername; // Username for authentication + const char *mqttPassword; // Password for authentication + + unsigned long lastReconnectTime; + int reconnectAttempt; + + HTTPClient httpGET; + HTTPClient httpPOST; + + WiFiClient wifiGET; + WiFiClient wifiPOST; + JsonDocument inputDoc; + + std::vector devices; // Collection of devices + + Alex2ESPState _state; + AsyncMqttClientDisconnectReason _disconnectReason; + + // MQTT connection details + const char *mqttServer = "alex2mqtt.stormysdream.club"; + uint16_t mqttPort = 1883; + + // Internal event handlers + void onMqttConnect(bool sessionPresent); + void onMqttDisconnect(AsyncMqttClientDisconnectReason reason); + void onSubscribe(uint16_t packetId, uint8_t qos); + void onMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t length, size_t index, size_t total); + + //loop processing function + void handleMqttReconnection(); + void processHttpPost(); + void processHttpGet(); + +}; + +#endif // ALEX2ESP_H diff --git a/src/AlexaDevice.cpp b/src/AlexaDevice.cpp index 8267b3a..e9a748a 100644 --- a/src/AlexaDevice.cpp +++ b/src/AlexaDevice.cpp @@ -1,164 +1,164 @@ -#include "AlexaDevice.h" - -AlexaDevice::AlexaDevice(const String& name, const String& rootTopic, const String& endpointId) - : name(name), endpointId(endpointId), rootTopic(rootTopic) { - // Initialize event arrays to nullptr - for (int i = 0; i < MAX_EVENTS; ++i) { - eventNames[i] = nullptr; // Set all event names to nullptr - eventCallbacks[i] = nullptr; // Set all callbacks to nullptr - } -} - -// Setters and Getters -void AlexaDevice::setName(const String& name) { - this->name = name; -} - -String AlexaDevice::getName() const { - return name; -} - -String AlexaDevice::getEndpointId() const { - return endpointId; -} - -DisplayCategory AlexaDevice::getDisplayCategory() const { - return displayCategory; -} - -void AlexaDevice::setDisplayCategory(DisplayCategory category) { - displayCategory = category; -} - -String AlexaDevice::getDescription() const { - return description; -} - -void AlexaDevice::setDescription(const String& description) { - this->description = description; -} - -String AlexaDevice::getManufacturerName() const { - return manufacturerName; -} - -void AlexaDevice::setManufacturerName(const String& manufacturerName) { - this->manufacturerName = manufacturerName; -} - -String AlexaDevice::getManufacturer() const { - return manufacturer; -} - -void AlexaDevice::setManufacturer(const String& manufacturer) { - this->manufacturer = manufacturer; -} - -String AlexaDevice::getModel() const { - return model; -} - -void AlexaDevice::setModel(const String& model) { - this->model = model; -} - -String AlexaDevice::getSoftwareVersion() const { - return softwareVersion; -} - - -AlexaInterface* AlexaDevice::addCapability(AlexaInterfaceType type) { - // Check if a interface with the given type already exists - for (auto& iface : capabilities) { - if (iface.getType() == type) { - return &iface; // Return the existing interface - } - } - - // If the interface doesn't exist, create a new one - capabilities.emplace_back(type); - - Serial.print("Created new interface with type: "); - Serial.println(capabilities.back().getTypeString()); - - // Return a pointer to the newly created device - return &capabilities.back(); -} - - - -JsonDocument AlexaDevice::getDeviceJSON() const { - - JsonDocument json; - - // Main device attributes - json["endpointId"] = endpointId; - json["friendlyName"] = name; - json["description"] = description; - json["manufacturerName"] = manufacturerName; - - - - JsonArray displayCategories = json["displayCategories"].to(); - displayCategories.add(DisplayCategoryUtils::toString(displayCategory)); - - JsonObject additionalAttributes = json["additionalAttributes"].to(); - additionalAttributes["manufacturer"] = manufacturer; - additionalAttributes["model"] = model; - additionalAttributes["serialNumber"] = "ESP2Alex"; - additionalAttributes["firmwareVersion"] = "1.0.0"; - additionalAttributes["softwareVersion"] = softwareVersion; - additionalAttributes["customIdentifier"] = "ESP2Alex"; - - JsonArray capabilitiesArray = json["capabilities"].to(); - - for (AlexaInterface capability : capabilities) { - capabilitiesArray.add(capability.getJSON().as()); - } - - // capabilitiesArray.add(capability.getJSON().as()); - - return json; -} - -// void AlexaDevice::registerEvent(const String& eventName, std::function callback) { -// eventCallbacks[eventName] = callback; -// } - -// void AlexaDevice::triggerEvent(const String& eventName, const JsonDocument& directive,const AlexaInterfaceType& type) const { -// auto it = eventCallbacks.find(eventName); -// if (it != eventCallbacks.end()) { -// // Call the registered callback function -// it->second(directive,type); -// } else { -// Serial.print("No event registered for: "); -// Serial.println(eventName); -// } -// } - - -// Register an event callback function -void AlexaDevice::registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&)) { - for (int i = 0; i < MAX_EVENTS; ++i) { - // Find an empty slot for the new event - if (eventNames[i] == nullptr) { - eventNames[i] = eventName; // Store the event name - eventCallbacks[i] = callback; // Store the callback function - break; - } - } -} - -// Trigger the event and invoke the corresponding callback -void AlexaDevice::triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type) const { - for (int i = 0; i < MAX_EVENTS; ++i) { - if (eventNames[i] != nullptr && strcmp(eventNames[i], eventName) == 0) { - // Found the event name, call the corresponding callback function - eventCallbacks[i](directive, type); - return; - } - } - // If no event was found, print an error message - Serial.print("No event registered for: "); - Serial.println(eventName); +#include "AlexaDevice.h" + +AlexaDevice::AlexaDevice(const String& name, const String& rootTopic, const String& endpointId) + : name(name), endpointId(endpointId), rootTopic(rootTopic) { + // Initialize event arrays to nullptr + for (int i = 0; i < MAX_EVENTS; ++i) { + eventNames[i] = nullptr; // Set all event names to nullptr + eventCallbacks[i] = nullptr; // Set all callbacks to nullptr + } +} + +// Setters and Getters +void AlexaDevice::setName(const String& name) { + this->name = name; +} + +String AlexaDevice::getName() const { + return name; +} + +String AlexaDevice::getEndpointId() const { + return endpointId; +} + +DisplayCategory AlexaDevice::getDisplayCategory() const { + return displayCategory; +} + +void AlexaDevice::setDisplayCategory(DisplayCategory category) { + displayCategory = category; +} + +String AlexaDevice::getDescription() const { + return description; +} + +void AlexaDevice::setDescription(const String& description) { + this->description = description; +} + +String AlexaDevice::getManufacturerName() const { + return manufacturerName; +} + +void AlexaDevice::setManufacturerName(const String& manufacturerName) { + this->manufacturerName = manufacturerName; +} + +String AlexaDevice::getManufacturer() const { + return manufacturer; +} + +void AlexaDevice::setManufacturer(const String& manufacturer) { + this->manufacturer = manufacturer; +} + +String AlexaDevice::getModel() const { + return model; +} + +void AlexaDevice::setModel(const String& model) { + this->model = model; +} + +String AlexaDevice::getSoftwareVersion() const { + return softwareVersion; +} + + +AlexaInterface* AlexaDevice::addCapability(AlexaInterfaceType type) { + // Check if a interface with the given type already exists + for (auto& iface : capabilities) { + if (iface.getType() == type) { + return &iface; // Return the existing interface + } + } + + // If the interface doesn't exist, create a new one + capabilities.emplace_back(type); + + Serial.print("Created new interface with type: "); + Serial.println(capabilities.back().getTypeString()); + + // Return a pointer to the newly created device + return &capabilities.back(); +} + + + +JsonDocument AlexaDevice::getDeviceJSON() const { + + JsonDocument json; + + // Main device attributes + json["endpointId"] = endpointId; + json["friendlyName"] = name; + json["description"] = description; + json["manufacturerName"] = manufacturerName; + + + + JsonArray displayCategories = json["displayCategories"].to(); + displayCategories.add(DisplayCategoryUtils::toString(displayCategory)); + + JsonObject additionalAttributes = json["additionalAttributes"].to(); + additionalAttributes["manufacturer"] = manufacturer; + additionalAttributes["model"] = model; + additionalAttributes["serialNumber"] = "ESP2Alex"; + additionalAttributes["firmwareVersion"] = "1.0.0"; + additionalAttributes["softwareVersion"] = softwareVersion; + additionalAttributes["customIdentifier"] = "ESP2Alex"; + + JsonArray capabilitiesArray = json["capabilities"].to(); + + for (AlexaInterface capability : capabilities) { + capabilitiesArray.add(capability.getJSON().as()); + } + + // capabilitiesArray.add(capability.getJSON().as()); + + return json; +} + +// void AlexaDevice::registerEvent(const String& eventName, std::function callback) { +// eventCallbacks[eventName] = callback; +// } + +// void AlexaDevice::triggerEvent(const String& eventName, const JsonDocument& directive,const AlexaInterfaceType& type) const { +// auto it = eventCallbacks.find(eventName); +// if (it != eventCallbacks.end()) { +// // Call the registered callback function +// it->second(directive,type); +// } else { +// Serial.print("No event registered for: "); +// Serial.println(eventName); +// } +// } + + +// Register an event callback function +void AlexaDevice::registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&)) { + for (int i = 0; i < MAX_EVENTS; ++i) { + // Find an empty slot for the new event + if (eventNames[i] == nullptr) { + eventNames[i] = eventName; // Store the event name + eventCallbacks[i] = callback; // Store the callback function + break; + } + } +} + +// Trigger the event and invoke the corresponding callback +void AlexaDevice::triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type) const { + for (int i = 0; i < MAX_EVENTS; ++i) { + if (eventNames[i] != nullptr && strcmp(eventNames[i], eventName) == 0) { + // Found the event name, call the corresponding callback function + eventCallbacks[i](directive, type); + return; + } + } + // If no event was found, print an error message + Serial.print("No event registered for: "); + Serial.println(eventName); } \ No newline at end of file diff --git a/src/AlexaDevice.h b/src/AlexaDevice.h index 6eb8c4f..c38f677 100644 --- a/src/AlexaDevice.h +++ b/src/AlexaDevice.h @@ -1,204 +1,204 @@ -#ifndef ALEXADEVICE_H -#define ALEXADEVICE_H - -#include -#include -#include "AlexaInterface.h" -#include -#include -#include -#include -#include -#include - - -#define MAX_EVENTS 10 - -enum class DisplayCategory { - ACTIVITY_TRIGGER, - AIR_CONDITIONER, - AIR_FRESHENER, - AIR_PURIFIER, - AIR_QUALITY_MONITOR, - ALEXA_VOICE_ENABLED, - AUTO_ACCESSORY, - BLUETOOTH_SPEAKER, - CAMERA, - CHRISTMAS_TREE, - COFFEE_MAKER, - COMPUTER, - CONTACT_SENSOR, - DISHWASHER, - DOOR, - DOORBELL, - DRYER, - EXTERIOR_BLIND, - FAN, - GAME_CONSOLE, - GARAGE_DOOR, - HEADPHONES, - HUB, - INTERIOR_BLIND, - LAPTOP, - LIGHT, - MICROWAVE, - MOBILE_PHONE, - MOTION_SENSOR, - MUSIC_SYSTEM, - NETWORK_HARDWARE, - OTHER, - OVEN, - PHONE, - PRINTER, - REMOTE, - ROUTER, - SCENE_TRIGGER, - SCREEN, - SECURITY_PANEL, - SECURITY_SYSTEM, - SLOW_COOKER, - SMARTLOCK, - SMARTPLUG, - SPEAKER, - STREAMING_DEVICE, - SWITCH, - TABLET, - TEMPERATURE_SENSOR, - THERMOSTAT, - TV, - VACUUM_CLEANER, - VEHICLE, - WASHER, - WATER_HEATER, - WEARABLE -}; - -class DisplayCategoryUtils { -public: - static String toString(DisplayCategory category) { - static const std::unordered_map categoryMap = { - {DisplayCategory::ACTIVITY_TRIGGER, "ACTIVITY_TRIGGER"}, - {DisplayCategory::AIR_CONDITIONER, "AIR_CONDITIONER"}, - {DisplayCategory::AIR_FRESHENER, "AIR_FRESHENER"}, - {DisplayCategory::AIR_PURIFIER, "AIR_PURIFIER"}, - {DisplayCategory::AIR_QUALITY_MONITOR, "AIR_QUALITY_MONITOR"}, - {DisplayCategory::ALEXA_VOICE_ENABLED, "ALEXA_VOICE_ENABLED"}, - {DisplayCategory::AUTO_ACCESSORY, "AUTO_ACCESSORY"}, - {DisplayCategory::BLUETOOTH_SPEAKER, "BLUETOOTH_SPEAKER"}, - {DisplayCategory::CAMERA, "CAMERA"}, - {DisplayCategory::CHRISTMAS_TREE, "CHRISTMAS_TREE"}, - {DisplayCategory::COFFEE_MAKER, "COFFEE_MAKER"}, - {DisplayCategory::COMPUTER, "COMPUTER"}, - {DisplayCategory::CONTACT_SENSOR, "CONTACT_SENSOR"}, - {DisplayCategory::DISHWASHER, "DISHWASHER"}, - {DisplayCategory::DOOR, "DOOR"}, - {DisplayCategory::DOORBELL, "DOORBELL"}, - {DisplayCategory::DRYER, "DRYER"}, - {DisplayCategory::EXTERIOR_BLIND, "EXTERIOR_BLIND"}, - {DisplayCategory::FAN, "FAN"}, - {DisplayCategory::GAME_CONSOLE, "GAME_CONSOLE"}, - {DisplayCategory::GARAGE_DOOR, "GARAGE_DOOR"}, - {DisplayCategory::HEADPHONES, "HEADPHONES"}, - {DisplayCategory::HUB, "HUB"}, - {DisplayCategory::INTERIOR_BLIND, "INTERIOR_BLIND"}, - {DisplayCategory::LAPTOP, "LAPTOP"}, - {DisplayCategory::LIGHT, "LIGHT"}, - {DisplayCategory::MICROWAVE, "MICROWAVE"}, - {DisplayCategory::MOBILE_PHONE, "MOBILE_PHONE"}, - {DisplayCategory::MOTION_SENSOR, "MOTION_SENSOR"}, - {DisplayCategory::MUSIC_SYSTEM, "MUSIC_SYSTEM"}, - {DisplayCategory::NETWORK_HARDWARE, "NETWORK_HARDWARE"}, - {DisplayCategory::OTHER, "OTHER"}, - {DisplayCategory::OVEN, "OVEN"}, - {DisplayCategory::PHONE, "PHONE"}, - {DisplayCategory::PRINTER, "PRINTER"}, - {DisplayCategory::REMOTE, "REMOTE"}, - {DisplayCategory::ROUTER, "ROUTER"}, - {DisplayCategory::SCENE_TRIGGER, "SCENE_TRIGGER"}, - {DisplayCategory::SCREEN, "SCREEN"}, - {DisplayCategory::SECURITY_PANEL, "SECURITY_PANEL"}, - {DisplayCategory::SECURITY_SYSTEM, "SECURITY_SYSTEM"}, - {DisplayCategory::SLOW_COOKER, "SLOW_COOKER"}, - {DisplayCategory::SMARTLOCK, "SMARTLOCK"}, - {DisplayCategory::SMARTPLUG, "SMARTPLUG"}, - {DisplayCategory::SPEAKER, "SPEAKER"}, - {DisplayCategory::STREAMING_DEVICE, "STREAMING_DEVICE"}, - {DisplayCategory::SWITCH, "SWITCH"}, - {DisplayCategory::TABLET, "TABLET"}, - {DisplayCategory::TEMPERATURE_SENSOR, "TEMPERATURE_SENSOR"}, - {DisplayCategory::THERMOSTAT, "THERMOSTAT"}, - {DisplayCategory::TV, "TV"}, - {DisplayCategory::VACUUM_CLEANER, "VACUUM_CLEANER"}, - {DisplayCategory::VEHICLE, "VEHICLE"}, - {DisplayCategory::WASHER, "WASHER"}, - {DisplayCategory::WATER_HEATER, "WATER_HEATER"}, - {DisplayCategory::WEARABLE, "WEARABLE"} - }; - - auto it = categoryMap.find(category); - if (it != categoryMap.end()) { - return it->second; - } - return "UNKNOWN"; - } -}; - - - -class AlexaDevice { -public: - AlexaDevice(const String& name, const String& rootTopic, const String& endpointId); - - void setName(const String& name); - String getName() const; - - String getEndpointId() const; - - DisplayCategory getDisplayCategory() const; - void setDisplayCategory(DisplayCategory category); - - String getDescription() const; - void setDescription(const String& description); - - String getManufacturerName() const; - void setManufacturerName(const String& manufacturerName); - - String getManufacturer() const; - void setManufacturer(const String& manufacturer); - - String getModel() const; - void setModel(const String& model); - - String getSoftwareVersion() const; - - JsonDocument getDeviceJSON() const; - - AlexaInterface* addCapability(AlexaInterfaceType type); - - void registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&)); - - void triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type) const; - - AlexaStatusMessage buildStatusMessage(const String& correlationToken,const bool isResponse=false) { - return AlexaStatusMessage(correlationToken,rootTopic,endpointId,isResponse); - } - -private: - String name; - String endpointId; - String rootTopic; - const char* eventNames[MAX_EVENTS]; - void (*eventCallbacks[MAX_EVENTS])(const JsonDocument&, const AlexaInterfaceType&); - - DisplayCategory displayCategory = DisplayCategory::OTHER; - String description = "Alexa2MQTT Default Device"; - String manufacturerName = "Alexa2MQTT"; - String manufacturer = "Alexa2MQTT"; - String model = "Alexa2MQTT"; - const String softwareVersion = "1.0.0"; - - std::vector capabilities; - -}; - -#endif +#ifndef ALEXADEVICE_H +#define ALEXADEVICE_H + +#include +#include +#include "AlexaInterface.h" +#include +#include +#include +#include +#include +#include + + +#define MAX_EVENTS 10 + +enum class DisplayCategory { + ACTIVITY_TRIGGER, + AIR_CONDITIONER, + AIR_FRESHENER, + AIR_PURIFIER, + AIR_QUALITY_MONITOR, + ALEXA_VOICE_ENABLED, + AUTO_ACCESSORY, + BLUETOOTH_SPEAKER, + CAMERA, + CHRISTMAS_TREE, + COFFEE_MAKER, + COMPUTER, + CONTACT_SENSOR, + DISHWASHER, + DOOR, + DOORBELL, + DRYER, + EXTERIOR_BLIND, + FAN, + GAME_CONSOLE, + GARAGE_DOOR, + HEADPHONES, + HUB, + INTERIOR_BLIND, + LAPTOP, + LIGHT, + MICROWAVE, + MOBILE_PHONE, + MOTION_SENSOR, + MUSIC_SYSTEM, + NETWORK_HARDWARE, + OTHER, + OVEN, + PHONE, + PRINTER, + REMOTE, + ROUTER, + SCENE_TRIGGER, + SCREEN, + SECURITY_PANEL, + SECURITY_SYSTEM, + SLOW_COOKER, + SMARTLOCK, + SMARTPLUG, + SPEAKER, + STREAMING_DEVICE, + SWITCH, + TABLET, + TEMPERATURE_SENSOR, + THERMOSTAT, + TV, + VACUUM_CLEANER, + VEHICLE, + WASHER, + WATER_HEATER, + WEARABLE +}; + +class DisplayCategoryUtils { +public: + static String toString(DisplayCategory category) { + static const std::unordered_map categoryMap = { + {DisplayCategory::ACTIVITY_TRIGGER, "ACTIVITY_TRIGGER"}, + {DisplayCategory::AIR_CONDITIONER, "AIR_CONDITIONER"}, + {DisplayCategory::AIR_FRESHENER, "AIR_FRESHENER"}, + {DisplayCategory::AIR_PURIFIER, "AIR_PURIFIER"}, + {DisplayCategory::AIR_QUALITY_MONITOR, "AIR_QUALITY_MONITOR"}, + {DisplayCategory::ALEXA_VOICE_ENABLED, "ALEXA_VOICE_ENABLED"}, + {DisplayCategory::AUTO_ACCESSORY, "AUTO_ACCESSORY"}, + {DisplayCategory::BLUETOOTH_SPEAKER, "BLUETOOTH_SPEAKER"}, + {DisplayCategory::CAMERA, "CAMERA"}, + {DisplayCategory::CHRISTMAS_TREE, "CHRISTMAS_TREE"}, + {DisplayCategory::COFFEE_MAKER, "COFFEE_MAKER"}, + {DisplayCategory::COMPUTER, "COMPUTER"}, + {DisplayCategory::CONTACT_SENSOR, "CONTACT_SENSOR"}, + {DisplayCategory::DISHWASHER, "DISHWASHER"}, + {DisplayCategory::DOOR, "DOOR"}, + {DisplayCategory::DOORBELL, "DOORBELL"}, + {DisplayCategory::DRYER, "DRYER"}, + {DisplayCategory::EXTERIOR_BLIND, "EXTERIOR_BLIND"}, + {DisplayCategory::FAN, "FAN"}, + {DisplayCategory::GAME_CONSOLE, "GAME_CONSOLE"}, + {DisplayCategory::GARAGE_DOOR, "GARAGE_DOOR"}, + {DisplayCategory::HEADPHONES, "HEADPHONES"}, + {DisplayCategory::HUB, "HUB"}, + {DisplayCategory::INTERIOR_BLIND, "INTERIOR_BLIND"}, + {DisplayCategory::LAPTOP, "LAPTOP"}, + {DisplayCategory::LIGHT, "LIGHT"}, + {DisplayCategory::MICROWAVE, "MICROWAVE"}, + {DisplayCategory::MOBILE_PHONE, "MOBILE_PHONE"}, + {DisplayCategory::MOTION_SENSOR, "MOTION_SENSOR"}, + {DisplayCategory::MUSIC_SYSTEM, "MUSIC_SYSTEM"}, + {DisplayCategory::NETWORK_HARDWARE, "NETWORK_HARDWARE"}, + {DisplayCategory::OTHER, "OTHER"}, + {DisplayCategory::OVEN, "OVEN"}, + {DisplayCategory::PHONE, "PHONE"}, + {DisplayCategory::PRINTER, "PRINTER"}, + {DisplayCategory::REMOTE, "REMOTE"}, + {DisplayCategory::ROUTER, "ROUTER"}, + {DisplayCategory::SCENE_TRIGGER, "SCENE_TRIGGER"}, + {DisplayCategory::SCREEN, "SCREEN"}, + {DisplayCategory::SECURITY_PANEL, "SECURITY_PANEL"}, + {DisplayCategory::SECURITY_SYSTEM, "SECURITY_SYSTEM"}, + {DisplayCategory::SLOW_COOKER, "SLOW_COOKER"}, + {DisplayCategory::SMARTLOCK, "SMARTLOCK"}, + {DisplayCategory::SMARTPLUG, "SMARTPLUG"}, + {DisplayCategory::SPEAKER, "SPEAKER"}, + {DisplayCategory::STREAMING_DEVICE, "STREAMING_DEVICE"}, + {DisplayCategory::SWITCH, "SWITCH"}, + {DisplayCategory::TABLET, "TABLET"}, + {DisplayCategory::TEMPERATURE_SENSOR, "TEMPERATURE_SENSOR"}, + {DisplayCategory::THERMOSTAT, "THERMOSTAT"}, + {DisplayCategory::TV, "TV"}, + {DisplayCategory::VACUUM_CLEANER, "VACUUM_CLEANER"}, + {DisplayCategory::VEHICLE, "VEHICLE"}, + {DisplayCategory::WASHER, "WASHER"}, + {DisplayCategory::WATER_HEATER, "WATER_HEATER"}, + {DisplayCategory::WEARABLE, "WEARABLE"} + }; + + auto it = categoryMap.find(category); + if (it != categoryMap.end()) { + return it->second; + } + return "UNKNOWN"; + } +}; + + + +class AlexaDevice { +public: + AlexaDevice(const String& name, const String& rootTopic, const String& endpointId); + + void setName(const String& name); + String getName() const; + + String getEndpointId() const; + + DisplayCategory getDisplayCategory() const; + void setDisplayCategory(DisplayCategory category); + + String getDescription() const; + void setDescription(const String& description); + + String getManufacturerName() const; + void setManufacturerName(const String& manufacturerName); + + String getManufacturer() const; + void setManufacturer(const String& manufacturer); + + String getModel() const; + void setModel(const String& model); + + String getSoftwareVersion() const; + + JsonDocument getDeviceJSON() const; + + AlexaInterface* addCapability(AlexaInterfaceType type); + + void registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&)); + + void triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type) const; + + AlexaStatusMessage buildStatusMessage(const String& correlationToken,const bool isResponse=false) { + return AlexaStatusMessage(correlationToken,rootTopic,endpointId,isResponse); + } + +private: + String name; + String endpointId; + String rootTopic; + const char* eventNames[MAX_EVENTS]; + void (*eventCallbacks[MAX_EVENTS])(const JsonDocument&, const AlexaInterfaceType&); + + DisplayCategory displayCategory = DisplayCategory::OTHER; + String description = "Alexa2MQTT Default Device"; + String manufacturerName = "Alexa2MQTT"; + String manufacturer = "Alexa2MQTT"; + String model = "Alexa2MQTT"; + const String softwareVersion = "1.0.0"; + + std::vector capabilities; + +}; + +#endif diff --git a/src/AlexaInterface.h b/src/AlexaInterface.h index 9e857ac..04681ed 100644 --- a/src/AlexaInterface.h +++ b/src/AlexaInterface.h @@ -1,723 +1,723 @@ -#ifndef ALEXA_INTERFACE_H -#define ALEXA_INTERFACE_H - -#include -#include -#include - -// Enum for Alexa Interface Types -enum class AlexaInterfaceType -{ - APPLICATION_STATE_REPORTER, - AUDIO_PLAY_QUEUE, - AUTHORIZATION_CONTROLLER, - AUTOMATION_MANAGEMENT, - AUTOMOTIVE_VEHICLE_DATA, - BRIGHTNESS_CONTROLLER, - CAMERA_LIVE_VIEW_CONTROLLER, - CAMERA_STREAM_CONTROLLER, - CHANNEL_CONTROLLER, - COLOR_CONTROLLER, - COLOR_TEMPERATURE_CONTROLLER, - COMMISSIONABLE, - CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER, - CONTACT_SENSOR, - COOKING, - COOKING_FOOD_TEMPERATURE_CONTROLLER, - COOKING_FOOD_TEMPERATURE_SENSOR, - COOKING_PRESET_CONTROLLER, - COOKING_TEMPERATURE_CONTROLLER, - COOKING_TEMPERATURE_SENSOR, - COOKING_TIME_CONTROLLER, - DATA_CONTROLLER, - DEVICE_USAGE_ESTIMATION, - DEVICE_USAGE_METER, - DOORBELL_EVENT_SOURCE, - ENDPOINT_HEALTH, - EQUALIZER_CONTROLLER, - INPUT_CONTROLLER, - INVENTORY_LEVEL_SENSOR, - INVENTORY_LEVEL_USAGE_SENSOR, - INVENTORY_USAGE_SENSOR, - KEYPAD_CONTROLLER, - LAUNCHER, - LOCK_CONTROLLER, - MEDIA_PLAYBACK, - MEDIA_PLAY_QUEUE, - MEDIA_SEARCH, - MODE_CONTROLLER, - MOTION_SENSOR, - PERCENTAGE_CONTROLLER, - PLAYBACK_CONTROLLER, - PLAYBACK_STATE_REPORTER, - POWER_CONTROLLER, - POWER_LEVEL_CONTROLLER, - PROACTIVE_NOTIFICATION_SOURCE, - RANGE_CONTROLLER, - RECORD_CONTROLLER, - REMOTE_VIDEO_PLAYER, - RTC_SESSION_CONTROLLER, - SCENE_CONTROLLER, - SECURITY_PANEL_CONTROLLER, - SECURITY_PANEL_CONTROLLER_ALERT, - SEEK_CONTROLLER, - SIMPLE_EVENT_SOURCE, - SMART_VISION_OBJECT_DETECTION_SENSOR, - SMART_VISION_SNAPSHOT_PROVIDER, - SPEAKER, - STEP_SPEAKER, - TEMPERATURE_SENSOR, - THERMOSTAT_CONTROLLER, - THERMOSTAT_CONTROLLER_CONFIGURATION, - THERMOSTAT_CONTROLLER_HVAC_COMPONENTS, - THERMOSTAT_CONTROLLER_SCHEDULE, - TIME_HOLD_CONTROLLER, - TOGGLE_CONTROLLER, - UI_CONTROLLER, - USER_PREFERENCE, - VIDEO_RECORDER, - WAKE_ON_LAN_CONTROLLER, - UNKNOWN -}; - -// Utility class to handle Alexa Interface type details -class AlexaInterfaceUtils -{ -public: - - static AlexaInterfaceType fromString(const String& str) - { - if (str == "Alexa.ApplicationStateReporter") - return AlexaInterfaceType::APPLICATION_STATE_REPORTER; - if (str == "Alexa.Audio.PlayQueue") - return AlexaInterfaceType::AUDIO_PLAY_QUEUE; - if (str == "Alexa.AuthorizationController") - return AlexaInterfaceType::AUTHORIZATION_CONTROLLER; - if (str == "Alexa.AutomationManagement") - return AlexaInterfaceType::AUTOMATION_MANAGEMENT; - if (str == "Alexa.Automotive.VehicleData") - return AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA; - if (str == "Alexa.BrightnessController") - return AlexaInterfaceType::BRIGHTNESS_CONTROLLER; - if (str == "Alexa.Camera.LiveViewController") - return AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER; - if (str == "Alexa.CameraStreamController") - return AlexaInterfaceType::CAMERA_STREAM_CONTROLLER; - if (str == "Alexa.ChannelController") - return AlexaInterfaceType::CHANNEL_CONTROLLER; - if (str == "Alexa.ColorController") - return AlexaInterfaceType::COLOR_CONTROLLER; - if (str == "Alexa.ColorTemperatureController") - return AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER; - if (str == "Alexa.Commissionable") - return AlexaInterfaceType::COMMISSIONABLE; - if (str == "Alexa.ConsentManagement.ConsentRequiredReporter") - return AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER; - if (str == "Alexa.ContactSensor") - return AlexaInterfaceType::CONTACT_SENSOR; - if (str == "Alexa.Cooking") - return AlexaInterfaceType::COOKING; - if (str == "Alexa.Cooking.FoodTemperatureController") - return AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_CONTROLLER; - if (str == "Alexa.Cooking.FoodTemperatureSensor") - return AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_SENSOR; - if (str == "Alexa.Cooking.PresetController") - return AlexaInterfaceType::COOKING_PRESET_CONTROLLER; - if (str == "Alexa.Cooking.TemperatureController") - return AlexaInterfaceType::COOKING_TEMPERATURE_CONTROLLER; - if (str == "Alexa.Cooking.TemperatureSensor") - return AlexaInterfaceType::COOKING_TEMPERATURE_SENSOR; - if (str == "Alexa.Cooking.TimeController") - return AlexaInterfaceType::COOKING_TIME_CONTROLLER; - if (str == "Alexa.DataController") - return AlexaInterfaceType::DATA_CONTROLLER; - if (str == "Alexa.DeviceUsage.Estimation") - return AlexaInterfaceType::DEVICE_USAGE_ESTIMATION; - if (str == "Alexa.DeviceUsage.Meter") - return AlexaInterfaceType::DEVICE_USAGE_METER; - if (str == "Alexa.DoorbellEventSource") - return AlexaInterfaceType::DOORBELL_EVENT_SOURCE; - if (str == "Alexa.EndpointHealth") - return AlexaInterfaceType::ENDPOINT_HEALTH; - if (str == "Alexa.EqualizerController") - return AlexaInterfaceType::EQUALIZER_CONTROLLER; - if (str == "Alexa.InputController") - return AlexaInterfaceType::INPUT_CONTROLLER; - if (str == "Alexa.InventoryLevelSensor") - return AlexaInterfaceType::INVENTORY_LEVEL_SENSOR; - if (str == "Alexa.InventoryLevelUsageSensor") - return AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR; - if (str == "Alexa.InventoryUsageSensor") - return AlexaInterfaceType::INVENTORY_USAGE_SENSOR; - if (str == "Alexa.KeypadController") - return AlexaInterfaceType::KEYPAD_CONTROLLER; - if (str == "Alexa.Launcher") - return AlexaInterfaceType::LAUNCHER; - if (str == "Alexa.LockController") - return AlexaInterfaceType::LOCK_CONTROLLER; - if (str == "Alexa.Media.Playback") - return AlexaInterfaceType::MEDIA_PLAYBACK; - if (str == "Alexa.Media.PlayQueue") - return AlexaInterfaceType::MEDIA_PLAY_QUEUE; - if (str == "Alexa.Media.Search") - return AlexaInterfaceType::MEDIA_SEARCH; - if (str == "Alexa.ModeController") - return AlexaInterfaceType::MODE_CONTROLLER; - if (str == "Alexa.MotionSensor") - return AlexaInterfaceType::MOTION_SENSOR; - if (str == "Alexa.PercentageController") - return AlexaInterfaceType::PERCENTAGE_CONTROLLER; - if (str == "Alexa.PlaybackController") - return AlexaInterfaceType::PLAYBACK_CONTROLLER; - if (str == "Alexa.PlaybackStateReporter") - return AlexaInterfaceType::PLAYBACK_STATE_REPORTER; - if (str == "Alexa.PowerController") - return AlexaInterfaceType::POWER_CONTROLLER; - if (str == "Alexa.PowerLevelController") - return AlexaInterfaceType::POWER_LEVEL_CONTROLLER; - if (str == "Alexa.ProactiveNotificationSource") - return AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE; - if (str == "Alexa.RangeController") - return AlexaInterfaceType::RANGE_CONTROLLER; - if (str == "Alexa.RecordController") - return AlexaInterfaceType::RECORD_CONTROLLER; - if (str == "Alexa.RemoteVideoPlayer") - return AlexaInterfaceType::REMOTE_VIDEO_PLAYER; - if (str == "Alexa.RTCSessionController") - return AlexaInterfaceType::RTC_SESSION_CONTROLLER; - if (str == "Alexa.SceneController") - return AlexaInterfaceType::SCENE_CONTROLLER; - if (str == "Alexa.SecurityPanelController") - return AlexaInterfaceType::SECURITY_PANEL_CONTROLLER; - if (str == "Alexa.SecurityPanelController.Alert") - return AlexaInterfaceType::SECURITY_PANEL_CONTROLLER_ALERT; - if (str == "Alexa.SeekController") - return AlexaInterfaceType::SEEK_CONTROLLER; - if (str == "Alexa.SimpleEventSource") - return AlexaInterfaceType::SIMPLE_EVENT_SOURCE; - if (str == "Alexa.SmartVision.ObjectDetectionSensor") - return AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR; - if (str == "Alexa.SmartVision.SnapshotProvider") - return AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER; - if (str == "Alexa.Speaker") - return AlexaInterfaceType::SPEAKER; - if (str == "Alexa.StepSpeaker") - return AlexaInterfaceType::STEP_SPEAKER; - if (str == "Alexa.TemperatureSensor") - return AlexaInterfaceType::TEMPERATURE_SENSOR; - if (str == "Alexa.ThermostatController") - return AlexaInterfaceType::THERMOSTAT_CONTROLLER; - if (str == "Alexa.ThermostatController.Configuration") - return AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION; - if (str == "Alexa.ThermostatController.HVAC.Components") - return AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS; - if (str == "Alexa.ThermostatController.Schedule") - return AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE; - if (str == "Alexa.TimeHoldController") - return AlexaInterfaceType::TIME_HOLD_CONTROLLER; - if (str == "Alexa.ToggleController") - return AlexaInterfaceType::TOGGLE_CONTROLLER; - if (str == "Alexa.UIController") - return AlexaInterfaceType::UI_CONTROLLER; - if (str == "Alexa.UserPreference") - return AlexaInterfaceType::USER_PREFERENCE; - if (str == "Alexa.VideoRecorder") - return AlexaInterfaceType::VIDEO_RECORDER; - if (str == "Alexa.WakeOnLANController") - return AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER; - - return AlexaInterfaceType::UNKNOWN; - } - - - static String toString(AlexaInterfaceType type) - { - switch (type) - { - case AlexaInterfaceType::APPLICATION_STATE_REPORTER: - return "Alexa.ApplicationStateReporter"; - case AlexaInterfaceType::AUDIO_PLAY_QUEUE: - return "Alexa.Audio.PlayQueue"; - case AlexaInterfaceType::AUTHORIZATION_CONTROLLER: - return "Alexa.AuthorizationController"; - case AlexaInterfaceType::AUTOMATION_MANAGEMENT: - return "Alexa.AutomationManagement"; - case AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA: - return "Alexa.Automotive.VehicleData"; - case AlexaInterfaceType::BRIGHTNESS_CONTROLLER: - return "Alexa.BrightnessController"; - case AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER: - return "Alexa.Camera.LiveViewController"; - case AlexaInterfaceType::CAMERA_STREAM_CONTROLLER: - return "Alexa.CameraStreamController"; - case AlexaInterfaceType::CHANNEL_CONTROLLER: - return "Alexa.ChannelController"; - case AlexaInterfaceType::COLOR_CONTROLLER: - return "Alexa.ColorController"; - case AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER: - return "Alexa.ColorTemperatureController"; - case AlexaInterfaceType::COMMISSIONABLE: - return "Alexa.Commissionable"; - case AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER: - return "Alexa.ConsentManagement.ConsentRequiredReporter"; - case AlexaInterfaceType::CONTACT_SENSOR: - return "Alexa.ContactSensor"; - case AlexaInterfaceType::COOKING: - return "Alexa.Cooking"; - case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_CONTROLLER: - return "Alexa.Cooking.FoodTemperatureController"; - case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_SENSOR: - return "Alexa.Cooking.FoodTemperatureSensor"; - case AlexaInterfaceType::COOKING_PRESET_CONTROLLER: - return "Alexa.Cooking.PresetController"; - case AlexaInterfaceType::COOKING_TEMPERATURE_CONTROLLER: - return "Alexa.Cooking.TemperatureController"; - case AlexaInterfaceType::COOKING_TEMPERATURE_SENSOR: - return "Alexa.Cooking.TemperatureSensor"; - case AlexaInterfaceType::COOKING_TIME_CONTROLLER: - return "Alexa.Cooking.TimeController"; - case AlexaInterfaceType::DATA_CONTROLLER: - return "Alexa.DataController"; - case AlexaInterfaceType::DEVICE_USAGE_ESTIMATION: - return "Alexa.DeviceUsage.Estimation"; - case AlexaInterfaceType::DEVICE_USAGE_METER: - return "Alexa.DeviceUsage.Meter"; - case AlexaInterfaceType::DOORBELL_EVENT_SOURCE: - return "Alexa.DoorbellEventSource"; - case AlexaInterfaceType::ENDPOINT_HEALTH: - return "Alexa.EndpointHealth"; - case AlexaInterfaceType::EQUALIZER_CONTROLLER: - return "Alexa.EqualizerController"; - case AlexaInterfaceType::INPUT_CONTROLLER: - return "Alexa.InputController"; - case AlexaInterfaceType::INVENTORY_LEVEL_SENSOR: - return "Alexa.InventoryLevelSensor"; - case AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR: - return "Alexa.InventoryLevelUsageSensor"; - case AlexaInterfaceType::INVENTORY_USAGE_SENSOR: - return "Alexa.InventoryUsageSensor"; - case AlexaInterfaceType::KEYPAD_CONTROLLER: - return "Alexa.KeypadController"; - case AlexaInterfaceType::LAUNCHER: - return "Alexa.Launcher"; - case AlexaInterfaceType::LOCK_CONTROLLER: - return "Alexa.LockController"; - case AlexaInterfaceType::MEDIA_PLAYBACK: - return "Alexa.Media.Playback"; - case AlexaInterfaceType::MEDIA_PLAY_QUEUE: - return "Alexa.Media.PlayQueue"; - case AlexaInterfaceType::MEDIA_SEARCH: - return "Alexa.Media.Search"; - case AlexaInterfaceType::MODE_CONTROLLER: - return "Alexa.ModeController"; - case AlexaInterfaceType::MOTION_SENSOR: - return "Alexa.MotionSensor"; - case AlexaInterfaceType::PERCENTAGE_CONTROLLER: - return "Alexa.PercentageController"; - case AlexaInterfaceType::PLAYBACK_CONTROLLER: - return "Alexa.PlaybackController"; - case AlexaInterfaceType::PLAYBACK_STATE_REPORTER: - return "Alexa.PlaybackStateReporter"; - case AlexaInterfaceType::POWER_CONTROLLER: - return "Alexa.PowerController"; - case AlexaInterfaceType::POWER_LEVEL_CONTROLLER: - return "Alexa.PowerLevelController"; - case AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE: - return "Alexa.ProactiveNotificationSource"; - case AlexaInterfaceType::RANGE_CONTROLLER: - return "Alexa.RangeController"; - case AlexaInterfaceType::RECORD_CONTROLLER: - return "Alexa.RecordController"; - case AlexaInterfaceType::REMOTE_VIDEO_PLAYER: - return "Alexa.RemoteVideoPlayer"; - case AlexaInterfaceType::RTC_SESSION_CONTROLLER: - return "Alexa.RTCSessionController"; - case AlexaInterfaceType::SCENE_CONTROLLER: - return "Alexa.SceneController"; - case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER: - return "Alexa.SecurityPanelController"; - case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER_ALERT: - return "Alexa.SecurityPanelController.Alert"; - case AlexaInterfaceType::SEEK_CONTROLLER: - return "Alexa.SeekController"; - case AlexaInterfaceType::SIMPLE_EVENT_SOURCE: - return "Alexa.SimpleEventSource"; - case AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR: - return "Alexa.SmartVision.ObjectDetectionSensor"; - case AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER: - return "Alexa.SmartVision.SnapshotProvider"; - case AlexaInterfaceType::SPEAKER: - return "Alexa.Speaker"; - case AlexaInterfaceType::STEP_SPEAKER: - return "Alexa.StepSpeaker"; - case AlexaInterfaceType::TEMPERATURE_SENSOR: - return "Alexa.TemperatureSensor"; - case AlexaInterfaceType::THERMOSTAT_CONTROLLER: - return "Alexa.ThermostatController"; - case AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION: - return "Alexa.ThermostatController.Configuration"; - case AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS: - return "Alexa.ThermostatController.HVAC.Components"; - case AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE: - return "Alexa.ThermostatController.Schedule"; - case AlexaInterfaceType::TIME_HOLD_CONTROLLER: - return "Alexa.TimeHoldController"; - case AlexaInterfaceType::TOGGLE_CONTROLLER: - return "Alexa.ToggleController"; - case AlexaInterfaceType::UI_CONTROLLER: - return "Alexa.UIController"; - case AlexaInterfaceType::USER_PREFERENCE: - return "Alexa.UserPreference"; - case AlexaInterfaceType::VIDEO_RECORDER: - return "Alexa.VideoRecorder"; - case AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER: - return "Alexa.WakeOnLANController"; - default: - return "UNKNOWN"; - } - } - - static String getVersion(AlexaInterfaceType type) - { - switch (type) - { - case AlexaInterfaceType::APPLICATION_STATE_REPORTER: - case AlexaInterfaceType::AUDIO_PLAY_QUEUE: - case AlexaInterfaceType::AUTHORIZATION_CONTROLLER: - case AlexaInterfaceType::AUTOMATION_MANAGEMENT: - case AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA: - case AlexaInterfaceType::COMMISSIONABLE: - case AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER: - case AlexaInterfaceType::COOKING: - case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_CONTROLLER: - case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_SENSOR: - case AlexaInterfaceType::COOKING_PRESET_CONTROLLER: - case AlexaInterfaceType::COOKING_TEMPERATURE_CONTROLLER: - case AlexaInterfaceType::COOKING_TEMPERATURE_SENSOR: - case AlexaInterfaceType::COOKING_TIME_CONTROLLER: - case AlexaInterfaceType::DATA_CONTROLLER: - case AlexaInterfaceType::DEVICE_USAGE_ESTIMATION: - case AlexaInterfaceType::DEVICE_USAGE_METER: - case AlexaInterfaceType::EQUALIZER_CONTROLLER: - case AlexaInterfaceType::INVENTORY_LEVEL_SENSOR: - case AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR: - case AlexaInterfaceType::INVENTORY_USAGE_SENSOR: - case AlexaInterfaceType::KEYPAD_CONTROLLER: - case AlexaInterfaceType::MEDIA_PLAYBACK: - case AlexaInterfaceType::MEDIA_PLAY_QUEUE: - case AlexaInterfaceType::MEDIA_SEARCH: - case AlexaInterfaceType::PLAYBACK_STATE_REPORTER: - case AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE: - case AlexaInterfaceType::REMOTE_VIDEO_PLAYER: - case AlexaInterfaceType::RTC_SESSION_CONTROLLER: - case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER: - case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER_ALERT: - case AlexaInterfaceType::SIMPLE_EVENT_SOURCE: - case AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR: - case AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER: - case AlexaInterfaceType::SPEAKER: - case AlexaInterfaceType::STEP_SPEAKER: - case AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION: - case AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS: - case AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE: - case AlexaInterfaceType::UI_CONTROLLER: - case AlexaInterfaceType::USER_PREFERENCE: - case AlexaInterfaceType::VIDEO_RECORDER: - case AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER: - return "1"; - - case AlexaInterfaceType::BRIGHTNESS_CONTROLLER: - case AlexaInterfaceType::CAMERA_STREAM_CONTROLLER: - case AlexaInterfaceType::CHANNEL_CONTROLLER: - case AlexaInterfaceType::COLOR_CONTROLLER: - case AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER: - case AlexaInterfaceType::CONTACT_SENSOR: - case AlexaInterfaceType::DOORBELL_EVENT_SOURCE: - case AlexaInterfaceType::INPUT_CONTROLLER: - case AlexaInterfaceType::LOCK_CONTROLLER: - case AlexaInterfaceType::MODE_CONTROLLER: - case AlexaInterfaceType::MOTION_SENSOR: - case AlexaInterfaceType::PERCENTAGE_CONTROLLER: - case AlexaInterfaceType::PLAYBACK_CONTROLLER: - case AlexaInterfaceType::POWER_CONTROLLER: - case AlexaInterfaceType::POWER_LEVEL_CONTROLLER: - case AlexaInterfaceType::RANGE_CONTROLLER: - case AlexaInterfaceType::RECORD_CONTROLLER: - case AlexaInterfaceType::SCENE_CONTROLLER: - case AlexaInterfaceType::SEEK_CONTROLLER: - case AlexaInterfaceType::TEMPERATURE_SENSOR: - case AlexaInterfaceType::THERMOSTAT_CONTROLLER: - case AlexaInterfaceType::TOGGLE_CONTROLLER: - return "3"; - - case AlexaInterfaceType::LAUNCHER: - return "1.1"; - case AlexaInterfaceType::ENDPOINT_HEALTH: - return "3.3"; - default: - return "UNKNOWN"; - } - } - - static std::vector getProps(AlexaInterfaceType type) - { - switch (type) - { -case AlexaInterfaceType::AUTOMATION_MANAGEMENT: - return {"automationStatuses"}; -case AlexaInterfaceType::BRIGHTNESS_CONTROLLER: - return {"brightness"}; -case AlexaInterfaceType::CHANNEL_CONTROLLER: - return {"channel"}; -case AlexaInterfaceType::COLOR_CONTROLLER: - return {"color"}; -case AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER: - return {"colorTemperatureInKelvin"}; -case AlexaInterfaceType::CONTACT_SENSOR: - return {"detectionState"}; -case AlexaInterfaceType::POWER_CONTROLLER: - return {"powerState"}; -case AlexaInterfaceType::ENDPOINT_HEALTH: - return {"connectivity"}; -case AlexaInterfaceType::TOGGLE_CONTROLLER: - return {"toggleState"}; -case AlexaInterfaceType::TEMPERATURE_SENSOR: - return {"temperature"}; - -case AlexaInterfaceType::APPLICATION_STATE_REPORTER: -case AlexaInterfaceType::AUDIO_PLAY_QUEUE: -case AlexaInterfaceType::AUTHORIZATION_CONTROLLER: -case AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA: -case AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER: -case AlexaInterfaceType::CAMERA_STREAM_CONTROLLER: -case AlexaInterfaceType::COMMISSIONABLE: -case AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER: -case AlexaInterfaceType::COOKING: -case AlexaInterfaceType::DATA_CONTROLLER: -case AlexaInterfaceType::DEVICE_USAGE_ESTIMATION: -case AlexaInterfaceType::DEVICE_USAGE_METER: -case AlexaInterfaceType::DOORBELL_EVENT_SOURCE: -case AlexaInterfaceType::EQUALIZER_CONTROLLER: -case AlexaInterfaceType::INPUT_CONTROLLER: -case AlexaInterfaceType::INVENTORY_LEVEL_SENSOR: -case AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR: -case AlexaInterfaceType::INVENTORY_USAGE_SENSOR: -case AlexaInterfaceType::KEYPAD_CONTROLLER: -case AlexaInterfaceType::LAUNCHER: -case AlexaInterfaceType::LOCK_CONTROLLER: -case AlexaInterfaceType::MEDIA_PLAYBACK: -case AlexaInterfaceType::MEDIA_SEARCH: -case AlexaInterfaceType::MODE_CONTROLLER: -case AlexaInterfaceType::MOTION_SENSOR: -case AlexaInterfaceType::PERCENTAGE_CONTROLLER: -case AlexaInterfaceType::PLAYBACK_CONTROLLER: -case AlexaInterfaceType::PLAYBACK_STATE_REPORTER: -case AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE: -case AlexaInterfaceType::RANGE_CONTROLLER: -case AlexaInterfaceType::RECORD_CONTROLLER: -case AlexaInterfaceType::REMOTE_VIDEO_PLAYER: -case AlexaInterfaceType::RTC_SESSION_CONTROLLER: -case AlexaInterfaceType::SCENE_CONTROLLER: -case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER: -case AlexaInterfaceType::SEEK_CONTROLLER: -case AlexaInterfaceType::SIMPLE_EVENT_SOURCE: -case AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR: -case AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER: -case AlexaInterfaceType::SPEAKER: -case AlexaInterfaceType::STEP_SPEAKER: -case AlexaInterfaceType::THERMOSTAT_CONTROLLER: -case AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION: -case AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS: -case AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE: -case AlexaInterfaceType::TIME_HOLD_CONTROLLER: -case AlexaInterfaceType::UI_CONTROLLER: -case AlexaInterfaceType::USER_PREFERENCE: -case AlexaInterfaceType::VIDEO_RECORDER: -case AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER: - return {}; - - default: - return {"UNKNOWN"}; - } - } -}; - -enum class AlexaActions { - Open, - Close, - Raise, - Lower, - SetEcoOn, - SetEcoOff -}; - - -class AlexaActionsUtils -{ -public: - // Helper function to convert AlexaActions enum to string - static String toString(AlexaActions action) { - switch (action) { - case AlexaActions::Open: return "Alexa.Actions.Open"; - case AlexaActions::Close: return "Alexa.Actions.Close"; - case AlexaActions::Raise: return "Alexa.Actions.Raise"; - case AlexaActions::Lower: return "Alexa.Actions.Lower"; - case AlexaActions::SetEcoOn: return "Alexa.Actions.SetEcoOn"; - case AlexaActions::SetEcoOff: return "Alexa.Actions.SetEcoOff"; - default: return "Unknown"; - } - } -}; -class FriendlyName { -public: - FriendlyName(const std::string& t, const std::string& l) - : text(t), locale(l) {} - - std::string text; - std::string locale; -}; - -// Updated ActionMapping class definition -class ActionMapping { -public: - String type = "ActionsToDirective"; // Type of action mapping (always "ActionsToDirective") - std::vector actions; // Array of AlexaActions - struct Directive { - String name; // Name of the directive - String payload; // Payload for the directive (can be empty) - } directive; // Directive object containing name and payload - - // Constructor - ActionMapping(std::vector actions, String directiveName, String directivePayload="{}") - : actions(actions) { - directive.name = directiveName; - directive.payload = directivePayload; - } - - - - // Get the JSON representation of ActionMapping - JsonDocument getJSON() { - JsonDocument doc; - doc["@type"] = type; - - // Convert actions array to strings - JsonArray actionArray = doc["actions"].to(); - for (const auto& action : actions) { - actionArray.add(AlexaActionsUtils::toString(action)); - } - - JsonObject directiveObj = doc["directive"].to(); - directiveObj["name"] = directive.name; - if (directive.payload) { - directiveObj["payload"] = directive.payload; - } - return doc; - } -}; - - - -// AlexaInterface class definition -class AlexaInterface -{ -private: - AlexaInterfaceType type; - bool retrievable; - bool proactivelyReported; - std::vector actionMappings; // Array of ActionMappings - std::vector friendlyNames; // Array of FriendlyNames - - - String instance; - bool nameSet; -public: - // Constructor - AlexaInterface(AlexaInterfaceType type, bool retrievable = true, bool proactivelyReported = false) - : type(type), retrievable(retrievable), proactivelyReported(proactivelyReported) {} - - // Getters - AlexaInterfaceType getType() const { return type; } - String getTypeString() const { return AlexaInterfaceUtils::toString(type); } - String getVersion() const { return AlexaInterfaceUtils::getVersion(type); } - std::vector getProps() const { return AlexaInterfaceUtils::getProps(type); } - - void setInstance(const char* name){ - instance=name; - nameSet=true; - } - - void addFriendlyName(const char* name,const char* locale){ - friendlyNames.emplace_back(name,locale); - } - - bool isRetrievable() const { return retrievable; } - bool isProactivelyReported() const { return proactivelyReported; } - - // Setters - void setRetrievable(bool value) { retrievable = value; } - void setProactivelyReported(bool value) { proactivelyReported = value; } - - void addActionMapping(const ActionMapping& actionMapping) { - actionMappings.push_back(actionMapping); - } - - JsonDocument getJSON() - { - JsonDocument doc; - String ret; - - // Set base properties - doc["interface"] = getTypeString(); - doc["version"] = getVersion(); - doc["type"] = "AlexaInterface"; - - // Add properties - JsonObject properties = doc["properties"].to(); - properties["retrievable"] = retrievable; - properties["proactivelyReported"] = proactivelyReported; - - std::vector props = getProps(); - - JsonArray supported = properties["supported"].to(); - int index = 0; - for (String prop : props) - { - supported[index]["name"] = prop; - index++; - } - - - JsonObject semantics = doc["semantics"].to(); - - // Add action mappings - JsonArray actionMappingArray = semantics["actionMappings"].to(); - for (ActionMapping& actionMapping : actionMappings) { - actionMappingArray.add(actionMapping.getJSON().as()); - } - if(nameSet){ - doc["instance"]=instance; - } - - - // Add capabilityResources if friendlyNames is not empty - if (!friendlyNames.empty()) { - JsonObject capabilityResources = doc.createNestedObject("capabilityResources"); - JsonArray friendlyNamesArray = capabilityResources.createNestedArray("friendlyNames"); - - for (const FriendlyName& fn : friendlyNames) { - JsonObject friendlyNameObj = friendlyNamesArray.createNestedObject(); - friendlyNameObj["@type"] = "text"; - - JsonObject valueObj = friendlyNameObj.createNestedObject("value"); - valueObj["text"] = fn.text; - valueObj["locale"] = fn.locale; - } - } - - doc.shrinkToFit(); - return doc; - } -}; - -#endif // ALEXA_INTERFACE_H +#ifndef ALEXA_INTERFACE_H +#define ALEXA_INTERFACE_H + +#include +#include +#include + +// Enum for Alexa Interface Types +enum class AlexaInterfaceType +{ + APPLICATION_STATE_REPORTER, + AUDIO_PLAY_QUEUE, + AUTHORIZATION_CONTROLLER, + AUTOMATION_MANAGEMENT, + AUTOMOTIVE_VEHICLE_DATA, + BRIGHTNESS_CONTROLLER, + CAMERA_LIVE_VIEW_CONTROLLER, + CAMERA_STREAM_CONTROLLER, + CHANNEL_CONTROLLER, + COLOR_CONTROLLER, + COLOR_TEMPERATURE_CONTROLLER, + COMMISSIONABLE, + CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER, + CONTACT_SENSOR, + COOKING, + COOKING_FOOD_TEMPERATURE_CONTROLLER, + COOKING_FOOD_TEMPERATURE_SENSOR, + COOKING_PRESET_CONTROLLER, + COOKING_TEMPERATURE_CONTROLLER, + COOKING_TEMPERATURE_SENSOR, + COOKING_TIME_CONTROLLER, + DATA_CONTROLLER, + DEVICE_USAGE_ESTIMATION, + DEVICE_USAGE_METER, + DOORBELL_EVENT_SOURCE, + ENDPOINT_HEALTH, + EQUALIZER_CONTROLLER, + INPUT_CONTROLLER, + INVENTORY_LEVEL_SENSOR, + INVENTORY_LEVEL_USAGE_SENSOR, + INVENTORY_USAGE_SENSOR, + KEYPAD_CONTROLLER, + LAUNCHER, + LOCK_CONTROLLER, + MEDIA_PLAYBACK, + MEDIA_PLAY_QUEUE, + MEDIA_SEARCH, + MODE_CONTROLLER, + MOTION_SENSOR, + PERCENTAGE_CONTROLLER, + PLAYBACK_CONTROLLER, + PLAYBACK_STATE_REPORTER, + POWER_CONTROLLER, + POWER_LEVEL_CONTROLLER, + PROACTIVE_NOTIFICATION_SOURCE, + RANGE_CONTROLLER, + RECORD_CONTROLLER, + REMOTE_VIDEO_PLAYER, + RTC_SESSION_CONTROLLER, + SCENE_CONTROLLER, + SECURITY_PANEL_CONTROLLER, + SECURITY_PANEL_CONTROLLER_ALERT, + SEEK_CONTROLLER, + SIMPLE_EVENT_SOURCE, + SMART_VISION_OBJECT_DETECTION_SENSOR, + SMART_VISION_SNAPSHOT_PROVIDER, + SPEAKER, + STEP_SPEAKER, + TEMPERATURE_SENSOR, + THERMOSTAT_CONTROLLER, + THERMOSTAT_CONTROLLER_CONFIGURATION, + THERMOSTAT_CONTROLLER_HVAC_COMPONENTS, + THERMOSTAT_CONTROLLER_SCHEDULE, + TIME_HOLD_CONTROLLER, + TOGGLE_CONTROLLER, + UI_CONTROLLER, + USER_PREFERENCE, + VIDEO_RECORDER, + WAKE_ON_LAN_CONTROLLER, + UNKNOWN +}; + +// Utility class to handle Alexa Interface type details +class AlexaInterfaceUtils +{ +public: + + static AlexaInterfaceType fromString(const String& str) + { + if (str == "Alexa.ApplicationStateReporter") + return AlexaInterfaceType::APPLICATION_STATE_REPORTER; + if (str == "Alexa.Audio.PlayQueue") + return AlexaInterfaceType::AUDIO_PLAY_QUEUE; + if (str == "Alexa.AuthorizationController") + return AlexaInterfaceType::AUTHORIZATION_CONTROLLER; + if (str == "Alexa.AutomationManagement") + return AlexaInterfaceType::AUTOMATION_MANAGEMENT; + if (str == "Alexa.Automotive.VehicleData") + return AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA; + if (str == "Alexa.BrightnessController") + return AlexaInterfaceType::BRIGHTNESS_CONTROLLER; + if (str == "Alexa.Camera.LiveViewController") + return AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER; + if (str == "Alexa.CameraStreamController") + return AlexaInterfaceType::CAMERA_STREAM_CONTROLLER; + if (str == "Alexa.ChannelController") + return AlexaInterfaceType::CHANNEL_CONTROLLER; + if (str == "Alexa.ColorController") + return AlexaInterfaceType::COLOR_CONTROLLER; + if (str == "Alexa.ColorTemperatureController") + return AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER; + if (str == "Alexa.Commissionable") + return AlexaInterfaceType::COMMISSIONABLE; + if (str == "Alexa.ConsentManagement.ConsentRequiredReporter") + return AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER; + if (str == "Alexa.ContactSensor") + return AlexaInterfaceType::CONTACT_SENSOR; + if (str == "Alexa.Cooking") + return AlexaInterfaceType::COOKING; + if (str == "Alexa.Cooking.FoodTemperatureController") + return AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_CONTROLLER; + if (str == "Alexa.Cooking.FoodTemperatureSensor") + return AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_SENSOR; + if (str == "Alexa.Cooking.PresetController") + return AlexaInterfaceType::COOKING_PRESET_CONTROLLER; + if (str == "Alexa.Cooking.TemperatureController") + return AlexaInterfaceType::COOKING_TEMPERATURE_CONTROLLER; + if (str == "Alexa.Cooking.TemperatureSensor") + return AlexaInterfaceType::COOKING_TEMPERATURE_SENSOR; + if (str == "Alexa.Cooking.TimeController") + return AlexaInterfaceType::COOKING_TIME_CONTROLLER; + if (str == "Alexa.DataController") + return AlexaInterfaceType::DATA_CONTROLLER; + if (str == "Alexa.DeviceUsage.Estimation") + return AlexaInterfaceType::DEVICE_USAGE_ESTIMATION; + if (str == "Alexa.DeviceUsage.Meter") + return AlexaInterfaceType::DEVICE_USAGE_METER; + if (str == "Alexa.DoorbellEventSource") + return AlexaInterfaceType::DOORBELL_EVENT_SOURCE; + if (str == "Alexa.EndpointHealth") + return AlexaInterfaceType::ENDPOINT_HEALTH; + if (str == "Alexa.EqualizerController") + return AlexaInterfaceType::EQUALIZER_CONTROLLER; + if (str == "Alexa.InputController") + return AlexaInterfaceType::INPUT_CONTROLLER; + if (str == "Alexa.InventoryLevelSensor") + return AlexaInterfaceType::INVENTORY_LEVEL_SENSOR; + if (str == "Alexa.InventoryLevelUsageSensor") + return AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR; + if (str == "Alexa.InventoryUsageSensor") + return AlexaInterfaceType::INVENTORY_USAGE_SENSOR; + if (str == "Alexa.KeypadController") + return AlexaInterfaceType::KEYPAD_CONTROLLER; + if (str == "Alexa.Launcher") + return AlexaInterfaceType::LAUNCHER; + if (str == "Alexa.LockController") + return AlexaInterfaceType::LOCK_CONTROLLER; + if (str == "Alexa.Media.Playback") + return AlexaInterfaceType::MEDIA_PLAYBACK; + if (str == "Alexa.Media.PlayQueue") + return AlexaInterfaceType::MEDIA_PLAY_QUEUE; + if (str == "Alexa.Media.Search") + return AlexaInterfaceType::MEDIA_SEARCH; + if (str == "Alexa.ModeController") + return AlexaInterfaceType::MODE_CONTROLLER; + if (str == "Alexa.MotionSensor") + return AlexaInterfaceType::MOTION_SENSOR; + if (str == "Alexa.PercentageController") + return AlexaInterfaceType::PERCENTAGE_CONTROLLER; + if (str == "Alexa.PlaybackController") + return AlexaInterfaceType::PLAYBACK_CONTROLLER; + if (str == "Alexa.PlaybackStateReporter") + return AlexaInterfaceType::PLAYBACK_STATE_REPORTER; + if (str == "Alexa.PowerController") + return AlexaInterfaceType::POWER_CONTROLLER; + if (str == "Alexa.PowerLevelController") + return AlexaInterfaceType::POWER_LEVEL_CONTROLLER; + if (str == "Alexa.ProactiveNotificationSource") + return AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE; + if (str == "Alexa.RangeController") + return AlexaInterfaceType::RANGE_CONTROLLER; + if (str == "Alexa.RecordController") + return AlexaInterfaceType::RECORD_CONTROLLER; + if (str == "Alexa.RemoteVideoPlayer") + return AlexaInterfaceType::REMOTE_VIDEO_PLAYER; + if (str == "Alexa.RTCSessionController") + return AlexaInterfaceType::RTC_SESSION_CONTROLLER; + if (str == "Alexa.SceneController") + return AlexaInterfaceType::SCENE_CONTROLLER; + if (str == "Alexa.SecurityPanelController") + return AlexaInterfaceType::SECURITY_PANEL_CONTROLLER; + if (str == "Alexa.SecurityPanelController.Alert") + return AlexaInterfaceType::SECURITY_PANEL_CONTROLLER_ALERT; + if (str == "Alexa.SeekController") + return AlexaInterfaceType::SEEK_CONTROLLER; + if (str == "Alexa.SimpleEventSource") + return AlexaInterfaceType::SIMPLE_EVENT_SOURCE; + if (str == "Alexa.SmartVision.ObjectDetectionSensor") + return AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR; + if (str == "Alexa.SmartVision.SnapshotProvider") + return AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER; + if (str == "Alexa.Speaker") + return AlexaInterfaceType::SPEAKER; + if (str == "Alexa.StepSpeaker") + return AlexaInterfaceType::STEP_SPEAKER; + if (str == "Alexa.TemperatureSensor") + return AlexaInterfaceType::TEMPERATURE_SENSOR; + if (str == "Alexa.ThermostatController") + return AlexaInterfaceType::THERMOSTAT_CONTROLLER; + if (str == "Alexa.ThermostatController.Configuration") + return AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION; + if (str == "Alexa.ThermostatController.HVAC.Components") + return AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS; + if (str == "Alexa.ThermostatController.Schedule") + return AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE; + if (str == "Alexa.TimeHoldController") + return AlexaInterfaceType::TIME_HOLD_CONTROLLER; + if (str == "Alexa.ToggleController") + return AlexaInterfaceType::TOGGLE_CONTROLLER; + if (str == "Alexa.UIController") + return AlexaInterfaceType::UI_CONTROLLER; + if (str == "Alexa.UserPreference") + return AlexaInterfaceType::USER_PREFERENCE; + if (str == "Alexa.VideoRecorder") + return AlexaInterfaceType::VIDEO_RECORDER; + if (str == "Alexa.WakeOnLANController") + return AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER; + + return AlexaInterfaceType::UNKNOWN; + } + + + static String toString(AlexaInterfaceType type) + { + switch (type) + { + case AlexaInterfaceType::APPLICATION_STATE_REPORTER: + return "Alexa.ApplicationStateReporter"; + case AlexaInterfaceType::AUDIO_PLAY_QUEUE: + return "Alexa.Audio.PlayQueue"; + case AlexaInterfaceType::AUTHORIZATION_CONTROLLER: + return "Alexa.AuthorizationController"; + case AlexaInterfaceType::AUTOMATION_MANAGEMENT: + return "Alexa.AutomationManagement"; + case AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA: + return "Alexa.Automotive.VehicleData"; + case AlexaInterfaceType::BRIGHTNESS_CONTROLLER: + return "Alexa.BrightnessController"; + case AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER: + return "Alexa.Camera.LiveViewController"; + case AlexaInterfaceType::CAMERA_STREAM_CONTROLLER: + return "Alexa.CameraStreamController"; + case AlexaInterfaceType::CHANNEL_CONTROLLER: + return "Alexa.ChannelController"; + case AlexaInterfaceType::COLOR_CONTROLLER: + return "Alexa.ColorController"; + case AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER: + return "Alexa.ColorTemperatureController"; + case AlexaInterfaceType::COMMISSIONABLE: + return "Alexa.Commissionable"; + case AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER: + return "Alexa.ConsentManagement.ConsentRequiredReporter"; + case AlexaInterfaceType::CONTACT_SENSOR: + return "Alexa.ContactSensor"; + case AlexaInterfaceType::COOKING: + return "Alexa.Cooking"; + case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_CONTROLLER: + return "Alexa.Cooking.FoodTemperatureController"; + case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_SENSOR: + return "Alexa.Cooking.FoodTemperatureSensor"; + case AlexaInterfaceType::COOKING_PRESET_CONTROLLER: + return "Alexa.Cooking.PresetController"; + case AlexaInterfaceType::COOKING_TEMPERATURE_CONTROLLER: + return "Alexa.Cooking.TemperatureController"; + case AlexaInterfaceType::COOKING_TEMPERATURE_SENSOR: + return "Alexa.Cooking.TemperatureSensor"; + case AlexaInterfaceType::COOKING_TIME_CONTROLLER: + return "Alexa.Cooking.TimeController"; + case AlexaInterfaceType::DATA_CONTROLLER: + return "Alexa.DataController"; + case AlexaInterfaceType::DEVICE_USAGE_ESTIMATION: + return "Alexa.DeviceUsage.Estimation"; + case AlexaInterfaceType::DEVICE_USAGE_METER: + return "Alexa.DeviceUsage.Meter"; + case AlexaInterfaceType::DOORBELL_EVENT_SOURCE: + return "Alexa.DoorbellEventSource"; + case AlexaInterfaceType::ENDPOINT_HEALTH: + return "Alexa.EndpointHealth"; + case AlexaInterfaceType::EQUALIZER_CONTROLLER: + return "Alexa.EqualizerController"; + case AlexaInterfaceType::INPUT_CONTROLLER: + return "Alexa.InputController"; + case AlexaInterfaceType::INVENTORY_LEVEL_SENSOR: + return "Alexa.InventoryLevelSensor"; + case AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR: + return "Alexa.InventoryLevelUsageSensor"; + case AlexaInterfaceType::INVENTORY_USAGE_SENSOR: + return "Alexa.InventoryUsageSensor"; + case AlexaInterfaceType::KEYPAD_CONTROLLER: + return "Alexa.KeypadController"; + case AlexaInterfaceType::LAUNCHER: + return "Alexa.Launcher"; + case AlexaInterfaceType::LOCK_CONTROLLER: + return "Alexa.LockController"; + case AlexaInterfaceType::MEDIA_PLAYBACK: + return "Alexa.Media.Playback"; + case AlexaInterfaceType::MEDIA_PLAY_QUEUE: + return "Alexa.Media.PlayQueue"; + case AlexaInterfaceType::MEDIA_SEARCH: + return "Alexa.Media.Search"; + case AlexaInterfaceType::MODE_CONTROLLER: + return "Alexa.ModeController"; + case AlexaInterfaceType::MOTION_SENSOR: + return "Alexa.MotionSensor"; + case AlexaInterfaceType::PERCENTAGE_CONTROLLER: + return "Alexa.PercentageController"; + case AlexaInterfaceType::PLAYBACK_CONTROLLER: + return "Alexa.PlaybackController"; + case AlexaInterfaceType::PLAYBACK_STATE_REPORTER: + return "Alexa.PlaybackStateReporter"; + case AlexaInterfaceType::POWER_CONTROLLER: + return "Alexa.PowerController"; + case AlexaInterfaceType::POWER_LEVEL_CONTROLLER: + return "Alexa.PowerLevelController"; + case AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE: + return "Alexa.ProactiveNotificationSource"; + case AlexaInterfaceType::RANGE_CONTROLLER: + return "Alexa.RangeController"; + case AlexaInterfaceType::RECORD_CONTROLLER: + return "Alexa.RecordController"; + case AlexaInterfaceType::REMOTE_VIDEO_PLAYER: + return "Alexa.RemoteVideoPlayer"; + case AlexaInterfaceType::RTC_SESSION_CONTROLLER: + return "Alexa.RTCSessionController"; + case AlexaInterfaceType::SCENE_CONTROLLER: + return "Alexa.SceneController"; + case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER: + return "Alexa.SecurityPanelController"; + case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER_ALERT: + return "Alexa.SecurityPanelController.Alert"; + case AlexaInterfaceType::SEEK_CONTROLLER: + return "Alexa.SeekController"; + case AlexaInterfaceType::SIMPLE_EVENT_SOURCE: + return "Alexa.SimpleEventSource"; + case AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR: + return "Alexa.SmartVision.ObjectDetectionSensor"; + case AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER: + return "Alexa.SmartVision.SnapshotProvider"; + case AlexaInterfaceType::SPEAKER: + return "Alexa.Speaker"; + case AlexaInterfaceType::STEP_SPEAKER: + return "Alexa.StepSpeaker"; + case AlexaInterfaceType::TEMPERATURE_SENSOR: + return "Alexa.TemperatureSensor"; + case AlexaInterfaceType::THERMOSTAT_CONTROLLER: + return "Alexa.ThermostatController"; + case AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION: + return "Alexa.ThermostatController.Configuration"; + case AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS: + return "Alexa.ThermostatController.HVAC.Components"; + case AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE: + return "Alexa.ThermostatController.Schedule"; + case AlexaInterfaceType::TIME_HOLD_CONTROLLER: + return "Alexa.TimeHoldController"; + case AlexaInterfaceType::TOGGLE_CONTROLLER: + return "Alexa.ToggleController"; + case AlexaInterfaceType::UI_CONTROLLER: + return "Alexa.UIController"; + case AlexaInterfaceType::USER_PREFERENCE: + return "Alexa.UserPreference"; + case AlexaInterfaceType::VIDEO_RECORDER: + return "Alexa.VideoRecorder"; + case AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER: + return "Alexa.WakeOnLANController"; + default: + return "UNKNOWN"; + } + } + + static String getVersion(AlexaInterfaceType type) + { + switch (type) + { + case AlexaInterfaceType::APPLICATION_STATE_REPORTER: + case AlexaInterfaceType::AUDIO_PLAY_QUEUE: + case AlexaInterfaceType::AUTHORIZATION_CONTROLLER: + case AlexaInterfaceType::AUTOMATION_MANAGEMENT: + case AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA: + case AlexaInterfaceType::COMMISSIONABLE: + case AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER: + case AlexaInterfaceType::COOKING: + case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_CONTROLLER: + case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_SENSOR: + case AlexaInterfaceType::COOKING_PRESET_CONTROLLER: + case AlexaInterfaceType::COOKING_TEMPERATURE_CONTROLLER: + case AlexaInterfaceType::COOKING_TEMPERATURE_SENSOR: + case AlexaInterfaceType::COOKING_TIME_CONTROLLER: + case AlexaInterfaceType::DATA_CONTROLLER: + case AlexaInterfaceType::DEVICE_USAGE_ESTIMATION: + case AlexaInterfaceType::DEVICE_USAGE_METER: + case AlexaInterfaceType::EQUALIZER_CONTROLLER: + case AlexaInterfaceType::INVENTORY_LEVEL_SENSOR: + case AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR: + case AlexaInterfaceType::INVENTORY_USAGE_SENSOR: + case AlexaInterfaceType::KEYPAD_CONTROLLER: + case AlexaInterfaceType::MEDIA_PLAYBACK: + case AlexaInterfaceType::MEDIA_PLAY_QUEUE: + case AlexaInterfaceType::MEDIA_SEARCH: + case AlexaInterfaceType::PLAYBACK_STATE_REPORTER: + case AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE: + case AlexaInterfaceType::REMOTE_VIDEO_PLAYER: + case AlexaInterfaceType::RTC_SESSION_CONTROLLER: + case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER: + case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER_ALERT: + case AlexaInterfaceType::SIMPLE_EVENT_SOURCE: + case AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR: + case AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER: + case AlexaInterfaceType::SPEAKER: + case AlexaInterfaceType::STEP_SPEAKER: + case AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION: + case AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS: + case AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE: + case AlexaInterfaceType::UI_CONTROLLER: + case AlexaInterfaceType::USER_PREFERENCE: + case AlexaInterfaceType::VIDEO_RECORDER: + case AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER: + return "1"; + + case AlexaInterfaceType::BRIGHTNESS_CONTROLLER: + case AlexaInterfaceType::CAMERA_STREAM_CONTROLLER: + case AlexaInterfaceType::CHANNEL_CONTROLLER: + case AlexaInterfaceType::COLOR_CONTROLLER: + case AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER: + case AlexaInterfaceType::CONTACT_SENSOR: + case AlexaInterfaceType::DOORBELL_EVENT_SOURCE: + case AlexaInterfaceType::INPUT_CONTROLLER: + case AlexaInterfaceType::LOCK_CONTROLLER: + case AlexaInterfaceType::MODE_CONTROLLER: + case AlexaInterfaceType::MOTION_SENSOR: + case AlexaInterfaceType::PERCENTAGE_CONTROLLER: + case AlexaInterfaceType::PLAYBACK_CONTROLLER: + case AlexaInterfaceType::POWER_CONTROLLER: + case AlexaInterfaceType::POWER_LEVEL_CONTROLLER: + case AlexaInterfaceType::RANGE_CONTROLLER: + case AlexaInterfaceType::RECORD_CONTROLLER: + case AlexaInterfaceType::SCENE_CONTROLLER: + case AlexaInterfaceType::SEEK_CONTROLLER: + case AlexaInterfaceType::TEMPERATURE_SENSOR: + case AlexaInterfaceType::THERMOSTAT_CONTROLLER: + case AlexaInterfaceType::TOGGLE_CONTROLLER: + return "3"; + + case AlexaInterfaceType::LAUNCHER: + return "1.1"; + case AlexaInterfaceType::ENDPOINT_HEALTH: + return "3.3"; + default: + return "UNKNOWN"; + } + } + + static std::vector getProps(AlexaInterfaceType type) + { + switch (type) + { +case AlexaInterfaceType::AUTOMATION_MANAGEMENT: + return {"automationStatuses"}; +case AlexaInterfaceType::BRIGHTNESS_CONTROLLER: + return {"brightness"}; +case AlexaInterfaceType::CHANNEL_CONTROLLER: + return {"channel"}; +case AlexaInterfaceType::COLOR_CONTROLLER: + return {"color"}; +case AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER: + return {"colorTemperatureInKelvin"}; +case AlexaInterfaceType::CONTACT_SENSOR: + return {"detectionState"}; +case AlexaInterfaceType::POWER_CONTROLLER: + return {"powerState"}; +case AlexaInterfaceType::ENDPOINT_HEALTH: + return {"connectivity"}; +case AlexaInterfaceType::TOGGLE_CONTROLLER: + return {"toggleState"}; +case AlexaInterfaceType::TEMPERATURE_SENSOR: + return {"temperature"}; + +case AlexaInterfaceType::APPLICATION_STATE_REPORTER: +case AlexaInterfaceType::AUDIO_PLAY_QUEUE: +case AlexaInterfaceType::AUTHORIZATION_CONTROLLER: +case AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA: +case AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER: +case AlexaInterfaceType::CAMERA_STREAM_CONTROLLER: +case AlexaInterfaceType::COMMISSIONABLE: +case AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER: +case AlexaInterfaceType::COOKING: +case AlexaInterfaceType::DATA_CONTROLLER: +case AlexaInterfaceType::DEVICE_USAGE_ESTIMATION: +case AlexaInterfaceType::DEVICE_USAGE_METER: +case AlexaInterfaceType::DOORBELL_EVENT_SOURCE: +case AlexaInterfaceType::EQUALIZER_CONTROLLER: +case AlexaInterfaceType::INPUT_CONTROLLER: +case AlexaInterfaceType::INVENTORY_LEVEL_SENSOR: +case AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR: +case AlexaInterfaceType::INVENTORY_USAGE_SENSOR: +case AlexaInterfaceType::KEYPAD_CONTROLLER: +case AlexaInterfaceType::LAUNCHER: +case AlexaInterfaceType::LOCK_CONTROLLER: +case AlexaInterfaceType::MEDIA_PLAYBACK: +case AlexaInterfaceType::MEDIA_SEARCH: +case AlexaInterfaceType::MODE_CONTROLLER: +case AlexaInterfaceType::MOTION_SENSOR: +case AlexaInterfaceType::PERCENTAGE_CONTROLLER: +case AlexaInterfaceType::PLAYBACK_CONTROLLER: +case AlexaInterfaceType::PLAYBACK_STATE_REPORTER: +case AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE: +case AlexaInterfaceType::RANGE_CONTROLLER: +case AlexaInterfaceType::RECORD_CONTROLLER: +case AlexaInterfaceType::REMOTE_VIDEO_PLAYER: +case AlexaInterfaceType::RTC_SESSION_CONTROLLER: +case AlexaInterfaceType::SCENE_CONTROLLER: +case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER: +case AlexaInterfaceType::SEEK_CONTROLLER: +case AlexaInterfaceType::SIMPLE_EVENT_SOURCE: +case AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR: +case AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER: +case AlexaInterfaceType::SPEAKER: +case AlexaInterfaceType::STEP_SPEAKER: +case AlexaInterfaceType::THERMOSTAT_CONTROLLER: +case AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION: +case AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS: +case AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE: +case AlexaInterfaceType::TIME_HOLD_CONTROLLER: +case AlexaInterfaceType::UI_CONTROLLER: +case AlexaInterfaceType::USER_PREFERENCE: +case AlexaInterfaceType::VIDEO_RECORDER: +case AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER: + return {}; + + default: + return {"UNKNOWN"}; + } + } +}; + +enum class AlexaActions { + Open, + Close, + Raise, + Lower, + SetEcoOn, + SetEcoOff +}; + + +class AlexaActionsUtils +{ +public: + // Helper function to convert AlexaActions enum to string + static String toString(AlexaActions action) { + switch (action) { + case AlexaActions::Open: return "Alexa.Actions.Open"; + case AlexaActions::Close: return "Alexa.Actions.Close"; + case AlexaActions::Raise: return "Alexa.Actions.Raise"; + case AlexaActions::Lower: return "Alexa.Actions.Lower"; + case AlexaActions::SetEcoOn: return "Alexa.Actions.SetEcoOn"; + case AlexaActions::SetEcoOff: return "Alexa.Actions.SetEcoOff"; + default: return "Unknown"; + } + } +}; +class FriendlyName { +public: + FriendlyName(const std::string& t, const std::string& l) + : text(t), locale(l) {} + + std::string text; + std::string locale; +}; + +// Updated ActionMapping class definition +class ActionMapping { +public: + String type = "ActionsToDirective"; // Type of action mapping (always "ActionsToDirective") + std::vector actions; // Array of AlexaActions + struct Directive { + String name; // Name of the directive + String payload; // Payload for the directive (can be empty) + } directive; // Directive object containing name and payload + + // Constructor + ActionMapping(std::vector actions, String directiveName, String directivePayload="{}") + : actions(actions) { + directive.name = directiveName; + directive.payload = directivePayload; + } + + + + // Get the JSON representation of ActionMapping + JsonDocument getJSON() { + JsonDocument doc; + doc["@type"] = type; + + // Convert actions array to strings + JsonArray actionArray = doc["actions"].to(); + for (const auto& action : actions) { + actionArray.add(AlexaActionsUtils::toString(action)); + } + + JsonObject directiveObj = doc["directive"].to(); + directiveObj["name"] = directive.name; + if (directive.payload) { + directiveObj["payload"] = directive.payload; + } + return doc; + } +}; + + + +// AlexaInterface class definition +class AlexaInterface +{ +private: + AlexaInterfaceType type; + bool retrievable; + bool proactivelyReported; + std::vector actionMappings; // Array of ActionMappings + std::vector friendlyNames; // Array of FriendlyNames + + + String instance; + bool nameSet; +public: + // Constructor + AlexaInterface(AlexaInterfaceType type, bool retrievable = true, bool proactivelyReported = false) + : type(type), retrievable(retrievable), proactivelyReported(proactivelyReported) {} + + // Getters + AlexaInterfaceType getType() const { return type; } + String getTypeString() const { return AlexaInterfaceUtils::toString(type); } + String getVersion() const { return AlexaInterfaceUtils::getVersion(type); } + std::vector getProps() const { return AlexaInterfaceUtils::getProps(type); } + + void setInstance(const char* name){ + instance=name; + nameSet=true; + } + + void addFriendlyName(const char* name,const char* locale){ + friendlyNames.emplace_back(name,locale); + } + + bool isRetrievable() const { return retrievable; } + bool isProactivelyReported() const { return proactivelyReported; } + + // Setters + void setRetrievable(bool value) { retrievable = value; } + void setProactivelyReported(bool value) { proactivelyReported = value; } + + void addActionMapping(const ActionMapping& actionMapping) { + actionMappings.push_back(actionMapping); + } + + JsonDocument getJSON() + { + JsonDocument doc; + String ret; + + // Set base properties + doc["interface"] = getTypeString(); + doc["version"] = getVersion(); + doc["type"] = "AlexaInterface"; + + // Add properties + JsonObject properties = doc["properties"].to(); + properties["retrievable"] = retrievable; + properties["proactivelyReported"] = proactivelyReported; + + std::vector props = getProps(); + + JsonArray supported = properties["supported"].to(); + int index = 0; + for (String prop : props) + { + supported[index]["name"] = prop; + index++; + } + + + JsonObject semantics = doc["semantics"].to(); + + // Add action mappings + JsonArray actionMappingArray = semantics["actionMappings"].to(); + for (ActionMapping& actionMapping : actionMappings) { + actionMappingArray.add(actionMapping.getJSON().as()); + } + if(nameSet){ + doc["instance"]=instance; + } + + + // Add capabilityResources if friendlyNames is not empty + if (!friendlyNames.empty()) { + JsonObject capabilityResources = doc.createNestedObject("capabilityResources"); + JsonArray friendlyNamesArray = capabilityResources.createNestedArray("friendlyNames"); + + for (const FriendlyName& fn : friendlyNames) { + JsonObject friendlyNameObj = friendlyNamesArray.createNestedObject(); + friendlyNameObj["@type"] = "text"; + + JsonObject valueObj = friendlyNameObj.createNestedObject("value"); + valueObj["text"] = fn.text; + valueObj["locale"] = fn.locale; + } + } + + doc.shrinkToFit(); + return doc; + } +}; + +#endif // ALEXA_INTERFACE_H diff --git a/src/AlexaStatusMessage.cpp b/src/AlexaStatusMessage.cpp index 3ab44b7..35cf55f 100644 --- a/src/AlexaStatusMessage.cpp +++ b/src/AlexaStatusMessage.cpp @@ -1,3 +1,3 @@ -#include "AlexaStatusMessage.h" - -char AlexaStatusMessage::outputString[MAX_STATUS_REPORT_SIZE]; +#include "AlexaStatusMessage.h" + +char AlexaStatusMessage::outputString[MAX_STATUS_REPORT_SIZE]; diff --git a/src/AlexaStatusMessage.h b/src/AlexaStatusMessage.h index 3f0eef5..9849ca2 100644 --- a/src/AlexaStatusMessage.h +++ b/src/AlexaStatusMessage.h @@ -1,167 +1,167 @@ -#include -#include -#include - -#ifndef ALEXA_STATUS_MESSAGE_H -#define ALEXA_STATUS_MESSAGE_H - -#define MAX_STATUS_REPORT_SIZE 2048 - -enum class EndpointHealth -{ - OK, - UNREACHABLE -}; - -enum class PowerController -{ - ON, - OFF -}; -enum class TemperatureSensorScale -{ - CELSIUS, - FAHRENHEIT -}; - -class AlexaStatusMessage -{ -public: - AlexaStatusMessage(const String &correlationToken, const String &rootTopic, const String &endpointId, const bool isResponse) - { - this->endpointId = endpointId; - this->rootTopic = rootTopic; - - JsonObject event = doc["event"].to(); - - JsonObject event_header = event["header"].to(); - event_header["namespace"] = "Alexa"; - if (isResponse) - { - event_header["name"] = "Response"; - } - else - { - event_header["name"] = "StateReport"; - } - event_header["payloadVersion"] = "3"; - event_header["messageId"] = generateMessageId(); - event_header["correlationToken"] = correlationToken; - - JsonObject event_endpoint = event["endpoint"].to(); - event_endpoint["endpointId"] = endpointId; - contextProperties = doc["context"]["properties"].to(); - } - - void setEndpointId(const String &endpointId) - { - // event["endpoint"]["endpointId"] = endpointId; - } - - AlexaStatusMessage &AddHealthProp(EndpointHealth endpointHealth, unsigned int uncertaintyInMs = 0) - { - JsonDocument healthValue; - healthValue["value"] = (endpointHealth == EndpointHealth::OK) ? "OK" : "UNREACHABLE"; - return AddProperty(AlexaInterfaceType::ENDPOINT_HEALTH, "connectivity", healthValue.as(), uncertaintyInMs); - } - - AlexaStatusMessage &AddPowerControllerProp(PowerController powerController, unsigned int uncertaintyInMs = 0) - { - String value = (powerController == PowerController::ON) ? "ON" : "OFF"; - return AddProperty(AlexaInterfaceType::POWER_CONTROLLER, "powerState", value, uncertaintyInMs); - } - - AlexaStatusMessage &AddTemperatureSensorProp(TemperatureSensorScale tempSensor,float value, unsigned int uncertaintyInMs = 0) - { - JsonDocument tempValue; - tempValue["scale"] = "CELSIUS"; - tempValue["value"] = value; - if((tempSensor == TemperatureSensorScale::FAHRENHEIT)){ - tempValue["value"] = (value - 32) * 5.0 / 9.0; - } - return AddProperty(AlexaInterfaceType::TEMPERATURE_SENSOR, "temperature", tempValue.as(), uncertaintyInMs); - } - - AlexaStatusMessage &AddBrightnessControllerProp(unsigned int brightness, unsigned int uncertaintyInMs = 0) - { - return AddProperty(AlexaInterfaceType::BRIGHTNESS_CONTROLLER, "brightness", brightness, uncertaintyInMs); - } - AlexaStatusMessage &AddColorTemperatureControllerProp(unsigned int colorTemperature, unsigned int uncertaintyInMs = 0) - { - return AddProperty(AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER, "colorTemperatureInKelvin", colorTemperature, uncertaintyInMs); - } - AlexaStatusMessage &AddToggleControllerProp(PowerController powerController,String instanceName, unsigned int uncertaintyInMs = 0) - { - String value = (powerController == PowerController::ON) ? "ON" : "OFF"; - return AddProperty(AlexaInterfaceType::TOGGLE_CONTROLLER, "toggleState", value, uncertaintyInMs,instanceName); - } - - AlexaStatusMessage &AddContextProp(const JsonObject &property) - { - contextProperties.add(property); - return *this; // Return a reference to the current object - } - - void send() - { - doc.shrinkToFit(); - serializeJson(doc, outputString); - doc.clear(); - String topic = rootTopic + "/" + endpointId + "/alexaResponce"; - AlexaUtils::enqueue(outputString, topic.c_str()); - - } - -private: - String rootTopic; - String endpointId; - JsonDocument doc; - JsonArray contextProperties; - static char outputString[MAX_STATUS_REPORT_SIZE]; - - // TODO: make real uuid4 gen function - String generateMessageId() - { - char buffer[38]; - const char charset[] = "0123456789abcdefABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // Allowed characters - - // Seed the random number generator (optional) - srand(static_cast(time(nullptr))); - - // Generate 37 random characters - for (int i = 0; i < 37; ++i) - { - buffer[i] = charset[rand() % (sizeof(charset) - 1)]; // Pick a random character - } - - buffer[37] = '\0'; // Null-terminate the string - return String(buffer); - } - - template - AlexaStatusMessage &AddProperty(AlexaInterfaceType type, const String &propertyName, const T &value, unsigned int uncertaintyInMs = 0,const String &instanceName="") - { - JsonDocument prop; - prop["namespace"] = AlexaInterfaceUtils::toString(type); - prop["name"] = propertyName; - - if (!instanceName.isEmpty()) { - prop["instance"] = instanceName; - } - if constexpr (std::is_same_v) - { - prop["value"] = value; - } - else - { - prop["value"] = value; - } - - prop["timeOfSample"] = "{REPLACE_WITH_DATETIME}"; - prop["uncertaintyInMilliseconds"] = uncertaintyInMs; - - contextProperties.add(prop.as()); - return *this; - } -}; -#endif +#include +#include +#include + +#ifndef ALEXA_STATUS_MESSAGE_H +#define ALEXA_STATUS_MESSAGE_H + +#define MAX_STATUS_REPORT_SIZE 2048 + +enum class EndpointHealth +{ + OK, + UNREACHABLE +}; + +enum class PowerController +{ + ON, + OFF +}; +enum class TemperatureSensorScale +{ + CELSIUS, + FAHRENHEIT +}; + +class AlexaStatusMessage +{ +public: + AlexaStatusMessage(const String &correlationToken, const String &rootTopic, const String &endpointId, const bool isResponse) + { + this->endpointId = endpointId; + this->rootTopic = rootTopic; + + JsonObject event = doc["event"].to(); + + JsonObject event_header = event["header"].to(); + event_header["namespace"] = "Alexa"; + if (isResponse) + { + event_header["name"] = "Response"; + } + else + { + event_header["name"] = "StateReport"; + } + event_header["payloadVersion"] = "3"; + event_header["messageId"] = generateMessageId(); + event_header["correlationToken"] = correlationToken; + + JsonObject event_endpoint = event["endpoint"].to(); + event_endpoint["endpointId"] = endpointId; + contextProperties = doc["context"]["properties"].to(); + } + + void setEndpointId(const String &endpointId) + { + // event["endpoint"]["endpointId"] = endpointId; + } + + AlexaStatusMessage &AddHealthProp(EndpointHealth endpointHealth, unsigned int uncertaintyInMs = 0) + { + JsonDocument healthValue; + healthValue["value"] = (endpointHealth == EndpointHealth::OK) ? "OK" : "UNREACHABLE"; + return AddProperty(AlexaInterfaceType::ENDPOINT_HEALTH, "connectivity", healthValue.as(), uncertaintyInMs); + } + + AlexaStatusMessage &AddPowerControllerProp(PowerController powerController, unsigned int uncertaintyInMs = 0) + { + String value = (powerController == PowerController::ON) ? "ON" : "OFF"; + return AddProperty(AlexaInterfaceType::POWER_CONTROLLER, "powerState", value, uncertaintyInMs); + } + + AlexaStatusMessage &AddTemperatureSensorProp(TemperatureSensorScale tempSensor,float value, unsigned int uncertaintyInMs = 0) + { + JsonDocument tempValue; + tempValue["scale"] = "CELSIUS"; + tempValue["value"] = value; + if((tempSensor == TemperatureSensorScale::FAHRENHEIT)){ + tempValue["value"] = (value - 32) * 5.0 / 9.0; + } + return AddProperty(AlexaInterfaceType::TEMPERATURE_SENSOR, "temperature", tempValue.as(), uncertaintyInMs); + } + + AlexaStatusMessage &AddBrightnessControllerProp(unsigned int brightness, unsigned int uncertaintyInMs = 0) + { + return AddProperty(AlexaInterfaceType::BRIGHTNESS_CONTROLLER, "brightness", brightness, uncertaintyInMs); + } + AlexaStatusMessage &AddColorTemperatureControllerProp(unsigned int colorTemperature, unsigned int uncertaintyInMs = 0) + { + return AddProperty(AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER, "colorTemperatureInKelvin", colorTemperature, uncertaintyInMs); + } + AlexaStatusMessage &AddToggleControllerProp(PowerController powerController,String instanceName, unsigned int uncertaintyInMs = 0) + { + String value = (powerController == PowerController::ON) ? "ON" : "OFF"; + return AddProperty(AlexaInterfaceType::TOGGLE_CONTROLLER, "toggleState", value, uncertaintyInMs,instanceName); + } + + AlexaStatusMessage &AddContextProp(const JsonObject &property) + { + contextProperties.add(property); + return *this; // Return a reference to the current object + } + + void send() + { + doc.shrinkToFit(); + serializeJson(doc, outputString); + doc.clear(); + String topic = rootTopic + "/" + endpointId + "/alexaResponce"; + AlexaUtils::enqueue(outputString, topic.c_str()); + + } + +private: + String rootTopic; + String endpointId; + JsonDocument doc; + JsonArray contextProperties; + static char outputString[MAX_STATUS_REPORT_SIZE]; + + // TODO: make real uuid4 gen function + String generateMessageId() + { + char buffer[38]; + const char charset[] = "0123456789abcdefABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // Allowed characters + + // Seed the random number generator (optional) + srand(static_cast(time(nullptr))); + + // Generate 37 random characters + for (int i = 0; i < 37; ++i) + { + buffer[i] = charset[rand() % (sizeof(charset) - 1)]; // Pick a random character + } + + buffer[37] = '\0'; // Null-terminate the string + return String(buffer); + } + + template + AlexaStatusMessage &AddProperty(AlexaInterfaceType type, const String &propertyName, const T &value, unsigned int uncertaintyInMs = 0,const String &instanceName="") + { + JsonDocument prop; + prop["namespace"] = AlexaInterfaceUtils::toString(type); + prop["name"] = propertyName; + + if (!instanceName.isEmpty()) { + prop["instance"] = instanceName; + } + if constexpr (std::is_same_v) + { + prop["value"] = value; + } + else + { + prop["value"] = value; + } + + prop["timeOfSample"] = "{REPLACE_WITH_DATETIME}"; + prop["uncertaintyInMilliseconds"] = uncertaintyInMs; + + contextProperties.add(prop.as()); + return *this; + } +}; +#endif diff --git a/src/AlexaUtils.cpp b/src/AlexaUtils.cpp index fa8335f..28d8ee2 100644 --- a/src/AlexaUtils.cpp +++ b/src/AlexaUtils.cpp @@ -1,220 +1,220 @@ -#include "AlexaUtils.h" - -// #define Alex2ESP_DEBUG - -uint8_t AlexaUtils::nextMessageId = 0; - -char AlexaUtils::receiveQueue[MAX_QUEUE_LENGTH][16]={}; -char AlexaUtils::receivePayload[MAX_PAYLOAD_LENGTH]; -char AlexaUtils::topicQueue[MAX_QUEUE_LENGTH][MAX_TOPIC_LENGTH] = {}; -char AlexaUtils::packetQueue[MAX_QUEUE_LENGTH][MAX_PACKET_LENGTH] = {}; -char AlexaUtils::combinedData[MAX_TOPIC_LENGTH + MAX_PACKET_LENGTH + 32]; - -int AlexaUtils::queueStart = 0; -int AlexaUtils::queueEnd = 0; -int AlexaUtils::queueCount = 0; - - -int AlexaUtils::queueStartReceive = 0; -int AlexaUtils::queueEndReceive = 0; -int AlexaUtils::queueCountReceive = 0; - -// Enqueue a packet into the receive queue -bool AlexaUtils::enqueueReceive(const char* packet) { - if (isReceiveQueueFull()) { - return false; // Queue is full - } - AlexaUtils::log("Enqueing: "); - AlexaUtils::logln(packet); - - // Copy the packet into the receive queue - strncpy(receiveQueue[queueEndReceive], packet, sizeof(receiveQueue[0]) - 1); - receiveQueue[queueEndReceive][sizeof(receiveQueue[0]) - 1] = '\0'; // Ensure null-termination - - // Update the queueEnd and queueCount - queueEndReceive = (queueEndReceive + 1) % MAX_QUEUE_LENGTH; - queueCountReceive++; - - return true; -} - -// Dequeue a packet from the receive queue -bool AlexaUtils::dequeueReceive(String& packet,bool remove) { - if (isReceiveQueueEmpty()) { - return false; // Queue is empty - } - - // Read the front of the receive queue - packet = String(receiveQueue[queueStartReceive]); - - if(remove){ - // Clear the dequeued slot (optional but good for debugging) - memset(receiveQueue[queueStartReceive], 0, sizeof(receiveQueue[0])); - - // Update the queueStart and queueCount - queueStartReceive = (queueStartReceive + 1) % MAX_QUEUE_LENGTH; - queueCountReceive--; - } - return true; -} - -// Check if the receive queue is empty -bool AlexaUtils::isReceiveQueueEmpty() { - return queueCountReceive == 0; -} - -// Check if the receive queue is full -bool AlexaUtils::isReceiveQueueFull() { - return queueCountReceive == MAX_QUEUE_LENGTH; -} - - -// Enqueue a topic and packet into the queue -bool AlexaUtils::enqueue(const char* packet,const char* topic) { - if (isQueueFull()) { - return false; // Queue is full - } - - // Copy the topic and packet into the respective arrays - strncpy(topicQueue[queueEnd], topic, MAX_TOPIC_LENGTH - 1); - topicQueue[queueEnd][MAX_TOPIC_LENGTH - 1] = '\0'; // Ensure null-termination - - strncpy(packetQueue[queueEnd], packet, MAX_PACKET_LENGTH - 1); - packetQueue[queueEnd][MAX_PACKET_LENGTH - 1] = '\0'; // Ensure null-termination - - // Update the queueEnd and queueCount - queueEnd = (queueEnd + 1) % MAX_QUEUE_LENGTH; - queueCount++; - - return true; -} - -// Dequeue a topic and packet from the queue -bool AlexaUtils::dequeue(String& topic, String& packet) { - if (isQueueEmpty()) { - return false; // Queue is empty - } - - // Read the front of the queue - topic = String(topicQueue[queueStart]); - packet = String(packetQueue[queueStart]); - - // Clear the dequeued slot (optional but good for debugging) - memset(topicQueue[queueStart], 0, MAX_TOPIC_LENGTH); - memset(packetQueue[queueStart], 0, MAX_PACKET_LENGTH); - - // Update the queueStart and queueCount - queueStart = (queueStart + 1) % MAX_QUEUE_LENGTH; - queueCount--; - - return true; -} - - -const char* AlexaUtils::dequeueVals(bool remove) { - if (isQueueEmpty()) { - return nullptr; // Queue is empty - } - - // Reset combinedData before use to ensure no leftover data from previous calls - memset(combinedData, 0, sizeof(combinedData)); - - // Get lengths of the topic and packet - int topicLen = strlen(topicQueue[queueStart]); - int packetLen = strlen(packetQueue[queueStart]); - - // Calculate available space in the combinedData buffer - size_t availableSpace = sizeof(combinedData) - 1; // Reserve space for the null terminator - - // Check if the combined data can fit in the buffer - if ((topicLen + packetLen + strlen(MESSAGE_PAYLOAD_SPLIT)) > availableSpace) { - // Return null if the combined data is too large for the buffer - AlexaUtils::logln("Error: Combined data exceeds buffer size."); - return nullptr; - } - - // Concatenate the topic, separator, and packet into combinedData - snprintf(combinedData, sizeof(combinedData), "%s%s%s", topicQueue[queueStart], MESSAGE_PAYLOAD_SPLIT, packetQueue[queueStart]); - - if (remove) { - // Clear the dequeued slot - memset(topicQueue[queueStart], 0, MAX_TOPIC_LENGTH); - memset(packetQueue[queueStart], 0, MAX_PACKET_LENGTH); - - // Update the queueStart and queueCount - queueStart = (queueStart + 1) % MAX_QUEUE_LENGTH; - queueCount--; - } - - // Return the pointer to the combined data - return combinedData; -} - - - -// Check if the queue is empty -bool AlexaUtils::isQueueEmpty() { - return queueCount == 0; -} - -// Check if the queue is full -bool AlexaUtils::isQueueFull() { - return queueCount == MAX_QUEUE_LENGTH; -} - - - -void AlexaUtils::log(const char *string) { -#ifdef Alex2ESP_DEBUG - Serial.print(string); -#endif -} - -void AlexaUtils::logln(const char *string) { -#ifdef Alex2ESP_DEBUG - Serial.println(string); -#endif -} - -void AlexaUtils::log(int num) { -#ifdef Alex2ESP_DEBUG - Serial.print(num); -#endif -} - -void AlexaUtils::logln(int num) { -#ifdef Alex2ESP_DEBUG - Serial.println(num); -#endif -} - -void AlexaUtils::log(uint16_t num) { -#ifdef Alex2ESP_DEBUG - Serial.print(num); -#endif -} - -void AlexaUtils::logln(uint16_t num) { -#ifdef Alex2ESP_DEBUG - Serial.println(num); -#endif -} - -void AlexaUtils::log(uint8_t num) { -#ifdef Alex2ESP_DEBUG - Serial.print(num); -#endif -} - -void AlexaUtils::logln(uint8_t num) { -#ifdef Alex2ESP_DEBUG - Serial.println(num); -#endif -} - - -void AlexaUtils::logln(String string) { -#ifdef Alex2ESP_DEBUG - Serial.println(string); -#endif -} +#include "AlexaUtils.h" + +// #define Alex2ESP_DEBUG + +uint8_t AlexaUtils::nextMessageId = 0; + +char AlexaUtils::receiveQueue[MAX_QUEUE_LENGTH][16]={}; +char AlexaUtils::receivePayload[MAX_PAYLOAD_LENGTH]; +char AlexaUtils::topicQueue[MAX_QUEUE_LENGTH][MAX_TOPIC_LENGTH] = {}; +char AlexaUtils::packetQueue[MAX_QUEUE_LENGTH][MAX_PACKET_LENGTH] = {}; +char AlexaUtils::combinedData[MAX_TOPIC_LENGTH + MAX_PACKET_LENGTH + 32]; + +int AlexaUtils::queueStart = 0; +int AlexaUtils::queueEnd = 0; +int AlexaUtils::queueCount = 0; + + +int AlexaUtils::queueStartReceive = 0; +int AlexaUtils::queueEndReceive = 0; +int AlexaUtils::queueCountReceive = 0; + +// Enqueue a packet into the receive queue +bool AlexaUtils::enqueueReceive(const char* packet) { + if (isReceiveQueueFull()) { + return false; // Queue is full + } + AlexaUtils::log("Enqueing: "); + AlexaUtils::logln(packet); + + // Copy the packet into the receive queue + strncpy(receiveQueue[queueEndReceive], packet, sizeof(receiveQueue[0]) - 1); + receiveQueue[queueEndReceive][sizeof(receiveQueue[0]) - 1] = '\0'; // Ensure null-termination + + // Update the queueEnd and queueCount + queueEndReceive = (queueEndReceive + 1) % MAX_QUEUE_LENGTH; + queueCountReceive++; + + return true; +} + +// Dequeue a packet from the receive queue +bool AlexaUtils::dequeueReceive(String& packet,bool remove) { + if (isReceiveQueueEmpty()) { + return false; // Queue is empty + } + + // Read the front of the receive queue + packet = String(receiveQueue[queueStartReceive]); + + if(remove){ + // Clear the dequeued slot (optional but good for debugging) + memset(receiveQueue[queueStartReceive], 0, sizeof(receiveQueue[0])); + + // Update the queueStart and queueCount + queueStartReceive = (queueStartReceive + 1) % MAX_QUEUE_LENGTH; + queueCountReceive--; + } + return true; +} + +// Check if the receive queue is empty +bool AlexaUtils::isReceiveQueueEmpty() { + return queueCountReceive == 0; +} + +// Check if the receive queue is full +bool AlexaUtils::isReceiveQueueFull() { + return queueCountReceive == MAX_QUEUE_LENGTH; +} + + +// Enqueue a topic and packet into the queue +bool AlexaUtils::enqueue(const char* packet,const char* topic) { + if (isQueueFull()) { + return false; // Queue is full + } + + // Copy the topic and packet into the respective arrays + strncpy(topicQueue[queueEnd], topic, MAX_TOPIC_LENGTH - 1); + topicQueue[queueEnd][MAX_TOPIC_LENGTH - 1] = '\0'; // Ensure null-termination + + strncpy(packetQueue[queueEnd], packet, MAX_PACKET_LENGTH - 1); + packetQueue[queueEnd][MAX_PACKET_LENGTH - 1] = '\0'; // Ensure null-termination + + // Update the queueEnd and queueCount + queueEnd = (queueEnd + 1) % MAX_QUEUE_LENGTH; + queueCount++; + + return true; +} + +// Dequeue a topic and packet from the queue +bool AlexaUtils::dequeue(String& topic, String& packet) { + if (isQueueEmpty()) { + return false; // Queue is empty + } + + // Read the front of the queue + topic = String(topicQueue[queueStart]); + packet = String(packetQueue[queueStart]); + + // Clear the dequeued slot (optional but good for debugging) + memset(topicQueue[queueStart], 0, MAX_TOPIC_LENGTH); + memset(packetQueue[queueStart], 0, MAX_PACKET_LENGTH); + + // Update the queueStart and queueCount + queueStart = (queueStart + 1) % MAX_QUEUE_LENGTH; + queueCount--; + + return true; +} + + +const char* AlexaUtils::dequeueVals(bool remove) { + if (isQueueEmpty()) { + return nullptr; // Queue is empty + } + + // Reset combinedData before use to ensure no leftover data from previous calls + memset(combinedData, 0, sizeof(combinedData)); + + // Get lengths of the topic and packet + int topicLen = strlen(topicQueue[queueStart]); + int packetLen = strlen(packetQueue[queueStart]); + + // Calculate available space in the combinedData buffer + size_t availableSpace = sizeof(combinedData) - 1; // Reserve space for the null terminator + + // Check if the combined data can fit in the buffer + if ((topicLen + packetLen + strlen(MESSAGE_PAYLOAD_SPLIT)) > availableSpace) { + // Return null if the combined data is too large for the buffer + AlexaUtils::logln("Error: Combined data exceeds buffer size."); + return nullptr; + } + + // Concatenate the topic, separator, and packet into combinedData + snprintf(combinedData, sizeof(combinedData), "%s%s%s", topicQueue[queueStart], MESSAGE_PAYLOAD_SPLIT, packetQueue[queueStart]); + + if (remove) { + // Clear the dequeued slot + memset(topicQueue[queueStart], 0, MAX_TOPIC_LENGTH); + memset(packetQueue[queueStart], 0, MAX_PACKET_LENGTH); + + // Update the queueStart and queueCount + queueStart = (queueStart + 1) % MAX_QUEUE_LENGTH; + queueCount--; + } + + // Return the pointer to the combined data + return combinedData; +} + + + +// Check if the queue is empty +bool AlexaUtils::isQueueEmpty() { + return queueCount == 0; +} + +// Check if the queue is full +bool AlexaUtils::isQueueFull() { + return queueCount == MAX_QUEUE_LENGTH; +} + + + +void AlexaUtils::log(const char *string) { +#ifdef Alex2ESP_DEBUG + Serial.print(string); +#endif +} + +void AlexaUtils::logln(const char *string) { +#ifdef Alex2ESP_DEBUG + Serial.println(string); +#endif +} + +void AlexaUtils::log(int num) { +#ifdef Alex2ESP_DEBUG + Serial.print(num); +#endif +} + +void AlexaUtils::logln(int num) { +#ifdef Alex2ESP_DEBUG + Serial.println(num); +#endif +} + +void AlexaUtils::log(uint16_t num) { +#ifdef Alex2ESP_DEBUG + Serial.print(num); +#endif +} + +void AlexaUtils::logln(uint16_t num) { +#ifdef Alex2ESP_DEBUG + Serial.println(num); +#endif +} + +void AlexaUtils::log(uint8_t num) { +#ifdef Alex2ESP_DEBUG + Serial.print(num); +#endif +} + +void AlexaUtils::logln(uint8_t num) { +#ifdef Alex2ESP_DEBUG + Serial.println(num); +#endif +} + + +void AlexaUtils::logln(String string) { +#ifdef Alex2ESP_DEBUG + Serial.println(string); +#endif +} diff --git a/src/AlexaUtils.h b/src/AlexaUtils.h index 84970ef..55eac0d 100644 --- a/src/AlexaUtils.h +++ b/src/AlexaUtils.h @@ -1,83 +1,83 @@ -#ifndef ALEXA_UTILS_H -#define ALEXA_UTILS_H - -#include -#include -#include -#include -#include // Include for strcpy and memset - -class AlexaUtils -{ -public: - static const int MAX_PAYLOAD_LENGTH = 2048; // Maximum length for receive packets - static char receivePayload[MAX_PAYLOAD_LENGTH]; - - static bool enqueueReceive(const char* packet); - static bool dequeueReceive(String& packet,bool remove); - static bool isReceiveQueueEmpty(); - static bool isReceiveQueueFull(); - - static bool enqueue(const char* topic, const char* packet); - static bool dequeue(String& topic, String& packet); - static bool isQueueEmpty(); - static bool isQueueFull(); - static const char* dequeueVals(bool remove); - - static void log(const char *string); - static void logln(const char *string); - - static void log(int num); - static void logln(int num); - - static void log(uint16_t num); - static void logln(uint16_t num); - - static void log(uint8_t num); - static void logln(uint8_t num); - - static void logln(String string); - - // Incrementing message ID - static uint8_t nextMessageId; - - // Static function to set the MQTT client reference - static void printMemoryInfo() - { - size_t freeHeap = ESP.getFreeHeap(); - size_t freeStack = ESP.getFreeContStack(); // Requires ESP8266 core 3.0.0+ - size_t sketchSize = ESP.getSketchSize(); - size_t freeSketchSpace = ESP.getFreeSketchSpace(); - - // Print in custom format - Serial.printf("freeHeap: %u, freeStack: %u, freeROM: %u, usedROM: %u\n", - freeHeap, - freeStack, - freeSketchSpace, - sketchSize); - } - - -private: - static const int MAX_QUEUE_LENGTH = 5; // Define maximum queue length - static const int MAX_TOPIC_LENGTH = 128; // Maximum length for topics - static const int MAX_PACKET_LENGTH = 2048; // Maximum length for sending packets - static constexpr char MESSAGE_PAYLOAD_SPLIT[32] = "{MESSAGE_PAYLOAD_SPLIT}"; - - - // Static arrays for topics and packets - static char topicQueue[MAX_QUEUE_LENGTH][MAX_TOPIC_LENGTH]; - static char packetQueue[MAX_QUEUE_LENGTH][MAX_PACKET_LENGTH]; - static char combinedData[MAX_TOPIC_LENGTH + MAX_PACKET_LENGTH + 32]; - - static char receiveQueue[MAX_QUEUE_LENGTH][16]; - static int queueStart; // Index of the front of the queue - static int queueEnd; // Index of the back of the queue - static int queueCount; // Number of items in the queue - - static int queueStartReceive; - static int queueEndReceive; - static int queueCountReceive; -}; - -#endif // ALEXA_UTILS_H +#ifndef ALEXA_UTILS_H +#define ALEXA_UTILS_H + +#include +#include +#include +#include +#include // Include for strcpy and memset + +class AlexaUtils +{ +public: + static const int MAX_PAYLOAD_LENGTH = 2048; // Maximum length for receive packets + static char receivePayload[MAX_PAYLOAD_LENGTH]; + + static bool enqueueReceive(const char* packet); + static bool dequeueReceive(String& packet,bool remove); + static bool isReceiveQueueEmpty(); + static bool isReceiveQueueFull(); + + static bool enqueue(const char* topic, const char* packet); + static bool dequeue(String& topic, String& packet); + static bool isQueueEmpty(); + static bool isQueueFull(); + static const char* dequeueVals(bool remove); + + static void log(const char *string); + static void logln(const char *string); + + static void log(int num); + static void logln(int num); + + static void log(uint16_t num); + static void logln(uint16_t num); + + static void log(uint8_t num); + static void logln(uint8_t num); + + static void logln(String string); + + // Incrementing message ID + static uint8_t nextMessageId; + + // Static function to set the MQTT client reference + static void printMemoryInfo() + { + size_t freeHeap = ESP.getFreeHeap(); + size_t freeStack = ESP.getFreeContStack(); // Requires ESP8266 core 3.0.0+ + size_t sketchSize = ESP.getSketchSize(); + size_t freeSketchSpace = ESP.getFreeSketchSpace(); + + // Print in custom format + Serial.printf("freeHeap: %u, freeStack: %u, freeROM: %u, usedROM: %u\n", + freeHeap, + freeStack, + freeSketchSpace, + sketchSize); + } + + +private: + static const int MAX_QUEUE_LENGTH = 5; // Define maximum queue length + static const int MAX_TOPIC_LENGTH = 128; // Maximum length for topics + static const int MAX_PACKET_LENGTH = 2048; // Maximum length for sending packets + static constexpr char MESSAGE_PAYLOAD_SPLIT[32] = "{MESSAGE_PAYLOAD_SPLIT}"; + + + // Static arrays for topics and packets + static char topicQueue[MAX_QUEUE_LENGTH][MAX_TOPIC_LENGTH]; + static char packetQueue[MAX_QUEUE_LENGTH][MAX_PACKET_LENGTH]; + static char combinedData[MAX_TOPIC_LENGTH + MAX_PACKET_LENGTH + 32]; + + static char receiveQueue[MAX_QUEUE_LENGTH][16]; + static int queueStart; // Index of the front of the queue + static int queueEnd; // Index of the back of the queue + static int queueCount; // Number of items in the queue + + static int queueStartReceive; + static int queueEndReceive; + static int queueCountReceive; +}; + +#endif // ALEXA_UTILS_H