initial release

This commit is contained in:
chaos511 2024-12-18 16:44:06 -06:00
commit 85d59985d2
18 changed files with 2520 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/.vscode
/test

93
examples/basicLight.cpp Normal file
View file

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

127
examples/blindControl.cpp Normal file
View file

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

View file

View file

7
keywords.txt Normal file
View file

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

59
library.json Normal file
View file

@ -0,0 +1,59 @@
{
"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"
}

10
library.properties Normal file
View file

@ -0,0 +1,10 @@
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

191
readme.md Normal file
View file

@ -0,0 +1,191 @@
# Alex2ESP
Alex2ESP is a lightweight C++ 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 [Alex2ESP Documentation](https://alex2mqtt.stormysdream.club/).
---
## Features
- Supports various Alexa smart home capabilities.
- Provides action mapping for advanced customization.
- Simple integration with Wi-Fi and MQTT.
- Event-driven architecture for handling Alexa directives.
---
## Quick Start
### Installation
Download the library and include it in your Arduino IDE project:
```cpp
#include <Alex2ESP.h>
```
---
## Creating a Basic Device
### Example: Light Control
Here’s how to create a simple device that controls a light:
First create an instance of the alexa client and initilize it with your MQTT Credentials
```cpp
Alex2ESP alexClient;
```
Credentials can be found at https://alex2mqtt.stormysdream.club/ after logging in with amazin
```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::AUTOMATION_MANAGEMENT | Supported* |
| AlexaInterfaceType::BRIGHTNESS_CONTROLLER | Supported* |
| AlexaInterfaceType::CHANNEL_CONTROLLER | Supported* |
| AlexaInterfaceType::COLOR_CONTROLLER | Supported* |
| AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER | Supported* |
| AlexaInterfaceType::CONTACT_SENSOR | Supported* |
| AlexaInterfaceType::POWER_CONTROLLER | Fully Supported |
| AlexaInterfaceType::ENDPOINT_HEALTH | Supported* |
| AlexaInterfaceType::TOGGLE_CONTROLLER | Fully 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::TEMPERATURE_SENSOR | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS | Supported* |
| AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE | Supported* |
| AlexaInterfaceType::TIME_HOLD_CONTROLLER | Supported* |
| AlexaInterfaceType::UI_CONTROLLER | Supported* |
| AlexaInterfaceType::USER_PREFERENCE | Supported* |
| AlexaInterfaceType::VIDEO_RECORDER | Supported* |
| AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER | Supported* |
(*Partial support or limited implementation advanced configuration is required)
---
## Advanced ussage
For devices with limited or partial support, Alex2ESP provides functions that allow you to attach custom JSON objects to the status report. For example, to report the status of a PowerController type, you can use the `AddPowerControllerProp` function. However, if a specific "add props" function does not exist for your use case, you can utilize the `AddContextProp` function to pass a custom JSON object.
For instance, to manually report the state of a PowerController, you can use the following code:
```cpp
JsonDocument doc;
doc["namespace"] = "Alexa.PowerController";
doc["name"] = "powerState";
doc["value"] = "ON";
doc["timeOfSample"] = "{REPLACE_WITH_DATETIME}"; // Alex2MQTT server wil do the replace
doc["uncertaintyInMilliseconds"] = 0;
AddContextProp(doc.as<JsonObject>())
```
---
## Contributing
Feel free to submit pull requests or issues for feature requests and bug fixes.
---
## License
This project is licensed under the MIT License. See the LICENSE file for details.

389
src/Alex2ESP.cpp Normal file
View file

@ -0,0 +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;
}

104
src/Alex2ESP.h Normal file
View file

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

164
src/AlexaDevice.cpp Normal file
View file

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

204
src/AlexaDevice.h Normal file
View file

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

721
src/AlexaInterface.h Normal file
View file

@ -0,0 +1,721 @@
#ifndef ALEXA_INTERFACE_H
#define ALEXA_INTERFACE_H
#include <string>
#include <unordered_map>
#include <Arduino.h>
// 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<String> 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::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::TEMPERATURE_SENSOR:
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<AlexaActions> 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<AlexaActions> 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<JsonArray>();
for (const auto& action : actions) {
actionArray.add(AlexaActionsUtils::toString(action));
}
JsonObject directiveObj = doc["directive"].to<JsonObject>();
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<ActionMapping> actionMappings; // Array of ActionMappings
std::vector<FriendlyName> 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<String> 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<JsonObject>();
properties["retrievable"] = retrievable;
properties["proactivelyReported"] = proactivelyReported;
std::vector<String> props = getProps();
JsonArray supported = properties["supported"].to<JsonArray>();
int index = 0;
for (String prop : props)
{
supported[index]["name"] = prop;
index++;
}
JsonObject semantics = doc["semantics"].to<JsonObject>();
// Add action mappings
JsonArray actionMappingArray = semantics["actionMappings"].to<JsonArray>();
for (ActionMapping& actionMapping : actionMappings) {
actionMappingArray.add(actionMapping.getJSON().as<JsonObject>());
}
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

View file

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

143
src/AlexaStatusMessage.h Normal file
View file

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

220
src/AlexaUtils.cpp Normal file
View file

@ -0,0 +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
}

83
src/AlexaUtils.h Normal file
View file

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