initial release
This commit is contained in:
commit
85d59985d2
18 changed files with 2520 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/.vscode
|
||||||
|
/test
|
||||||
93
examples/basicLight.cpp
Normal file
93
examples/basicLight.cpp
Normal 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
127
examples/blindControl.cpp
Normal 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);
|
||||||
|
}
|
||||||
0
examples/lightWithBrightness.cpp
Normal file
0
examples/lightWithBrightness.cpp
Normal file
0
examples/lightWithColor.cpp
Normal file
0
examples/lightWithColor.cpp
Normal file
7
keywords.txt
Normal file
7
keywords.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#######################################
|
||||||
|
# Syntax Coloring Map For Espalexa
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Datatypes (KEYWORD1)
|
||||||
|
#######################################
|
||||||
59
library.json
Normal file
59
library.json
Normal 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
10
library.properties
Normal 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
191
readme.md
Normal 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
389
src/Alex2ESP.cpp
Normal 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
104
src/Alex2ESP.h
Normal 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
164
src/AlexaDevice.cpp
Normal 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
204
src/AlexaDevice.h
Normal 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
721
src/AlexaInterface.h
Normal 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
|
||||||
3
src/AlexaStatusMessage.cpp
Normal file
3
src/AlexaStatusMessage.cpp
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#include "AlexaStatusMessage.h"
|
||||||
|
|
||||||
|
char AlexaStatusMessage::outputString[MAX_STATUS_REPORT_SIZE];
|
||||||
143
src/AlexaStatusMessage.h
Normal file
143
src/AlexaStatusMessage.h
Normal 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
220
src/AlexaUtils.cpp
Normal 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
83
src/AlexaUtils.h
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue