1.1.0: begin(username, password, rootTopic) - the library signature now matches every example, the readme and the website (as published since 2024 no sketch could authenticate: the 64-hex password became the MQTT username). Discovery answers go out over MQTT directly, every device, resumed from loop() under heap pressure within the backend's 5 s window, instead of the 5-slot HTTP queue that silently dropped the rest (which the backend's reconcile then deleted from Alexa). JSON aligned with Alexa / alex2node: event.payload present, ActionMapping payload an object, semantics only when a capability has mappings. Pointer-stable device/capability containers (std::deque), the MQTT payload is copied by length before parsing (no write past the buffer), fragmented directive ids are ignored (a Discover is answered on its first fragment), deprecated ArduinoJson 7 calls replaced, credential Serial prints removed, ESP32 include guards (untested; the ESP8266 is the target). Examples: WIFI_PASSWORD, LED_BUILTIN fallback, string+int print fixes. library.json + library.properties restored (1.1.0; AsyncMqttClient, ArduinoJson 7, ESP Async TCP), LICENSE (MIT), a real keywords.txt, .gitattributes text=auto eol=lf (tree normalised to LF). readme: Forgejo URLs, platformio.ini snippet, Library Manager install, begin() order, INTERIOR_BLIND for blinds, 1.1.0 changelog. All five examples compile warning-free for d1_mini (PlatformIO 6.2, espressif8266) on pve-B450.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
1313f47856
commit
87caad25be
20 changed files with 3117 additions and 2782 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
* text=auto eol=lf
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 David (chaos511)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -8,8 +8,14 @@
|
||||||
|
|
||||||
#include <Alex2ESP.h>
|
#include <Alex2ESP.h>
|
||||||
|
|
||||||
|
// The on-board LED: GPIO2 on a Wemos D1 mini (active-low). Defined here for boards whose variant leaves it out.
|
||||||
|
#ifndef LED_BUILTIN
|
||||||
|
#define LED_BUILTIN 2
|
||||||
|
#endif
|
||||||
|
|
||||||
// Define constants for WiFi and Alexa Client configuration
|
// Define constants for WiFi and Alexa Client configuration
|
||||||
const char* WIFI_SSID = "";
|
const char* WIFI_SSID = "";
|
||||||
|
const char* WIFI_PASSWORD = "";
|
||||||
|
|
||||||
const char* ALEXA_USERNAME = "";
|
const char* ALEXA_USERNAME = "";
|
||||||
const char* ALEXA_PASSWORD = "";
|
const char* ALEXA_PASSWORD = "";
|
||||||
|
|
@ -31,7 +37,7 @@ void setup() {
|
||||||
|
|
||||||
// Connect to Wi-Fi
|
// Connect to Wi-Fi
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.begin(WIFI_SSID);
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
|
||||||
Serial.print("[WIFI] Connecting to WiFi");
|
Serial.print("[WIFI] Connecting to WiFi");
|
||||||
|
|
||||||
|
|
@ -43,7 +49,7 @@ void setup() {
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
||||||
|
|
||||||
// Initialize the Alexa client
|
// Initialize the Alexa client (MQTT username, MQTT password, root topic)
|
||||||
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
||||||
|
|
||||||
// Register a new device with a unique ID and name
|
// Register a new device with a unique ID and name
|
||||||
|
|
@ -88,6 +94,6 @@ void loop() {
|
||||||
// Process Alexa client communication
|
// Process Alexa client communication
|
||||||
alexClient.loop();
|
alexClient.loop();
|
||||||
|
|
||||||
// Update the state of the LED based on the power controller state
|
// Update the state of the LED based on the power controller state (the on-board LED is active-low)
|
||||||
digitalWrite(LED_BUILTIN, outputState != PowerController::ON);
|
digitalWrite(LED_BUILTIN, outputState != PowerController::ON);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,14 @@
|
||||||
|
|
||||||
#include <Alex2ESP.h>
|
#include <Alex2ESP.h>
|
||||||
|
|
||||||
|
// The on-board LED: GPIO2 on a Wemos D1 mini (active-low). Defined here for boards whose variant leaves it out.
|
||||||
|
#ifndef LED_BUILTIN
|
||||||
|
#define LED_BUILTIN 2
|
||||||
|
#endif
|
||||||
|
|
||||||
// Define constants for WiFi and Alexa Client configuration
|
// Define constants for WiFi and Alexa Client configuration
|
||||||
const char *WIFI_SSID = "";
|
const char *WIFI_SSID = "";
|
||||||
|
const char *WIFI_PASSWORD = "";
|
||||||
|
|
||||||
const char *ALEXA_USERNAME = "";
|
const char *ALEXA_USERNAME = "";
|
||||||
const char *ALEXA_PASSWORD = "";
|
const char *ALEXA_PASSWORD = "";
|
||||||
|
|
@ -32,7 +38,7 @@ void setup()
|
||||||
|
|
||||||
// Connect to Wi-Fi
|
// Connect to Wi-Fi
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.begin(WIFI_SSID);
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
|
||||||
Serial.print("[WIFI] Connecting to WiFi");
|
Serial.print("[WIFI] Connecting to WiFi");
|
||||||
|
|
||||||
|
|
@ -45,7 +51,7 @@ void setup()
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
||||||
|
|
||||||
// Initialize the Alexa client
|
// Initialize the Alexa client (MQTT username, MQTT password, root topic)
|
||||||
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
||||||
|
|
||||||
String deviceName="Bedroom Blinds";
|
String deviceName="Bedroom Blinds";
|
||||||
|
|
@ -53,8 +59,8 @@ void setup()
|
||||||
// Register a new device with a unique ID and name
|
// Register a new device with a unique ID and name
|
||||||
device1 = alexClient.getDevice(deviceName, "ESP-01");
|
device1 = alexClient.getDevice(deviceName, "ESP-01");
|
||||||
|
|
||||||
// Set the device's display category to LIGHT
|
// Set the device's display category to INTERIOR_BLIND
|
||||||
device1->setDisplayCategory(DisplayCategory::LIGHT);
|
device1->setDisplayCategory(DisplayCategory::INTERIOR_BLIND);
|
||||||
|
|
||||||
//a power controller will allow us to "turn on/off" the blinds (not really usefull but is possable)
|
//a power controller will allow us to "turn on/off" the blinds (not really usefull but is possable)
|
||||||
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
|
device1->addCapability(AlexaInterfaceType::POWER_CONTROLLER);
|
||||||
|
|
@ -122,6 +128,6 @@ void loop()
|
||||||
// Process Alexa client communication
|
// Process Alexa client communication
|
||||||
alexClient.loop();
|
alexClient.loop();
|
||||||
|
|
||||||
// Update the state of the LED based on the power controller state
|
// Update the state of the LED based on the power controller state (the on-board LED is active-low)
|
||||||
digitalWrite(LED_BUILTIN, outputState != PowerController::ON);
|
digitalWrite(LED_BUILTIN, outputState != PowerController::ON);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,14 @@
|
||||||
|
|
||||||
#include <Alex2ESP.h>
|
#include <Alex2ESP.h>
|
||||||
|
|
||||||
|
// The on-board LED: GPIO2 on a Wemos D1 mini (active-low). Defined here for boards whose variant leaves it out.
|
||||||
|
#ifndef LED_BUILTIN
|
||||||
|
#define LED_BUILTIN 2
|
||||||
|
#endif
|
||||||
|
|
||||||
// Define constants for WiFi and Alexa Client configuration
|
// Define constants for WiFi and Alexa Client configuration
|
||||||
const char *WIFI_SSID = "";
|
const char *WIFI_SSID = "";
|
||||||
|
const char *WIFI_PASSWORD = "";
|
||||||
|
|
||||||
const char *ALEXA_USERNAME = "";
|
const char *ALEXA_USERNAME = "";
|
||||||
const char *ALEXA_PASSWORD = "";
|
const char *ALEXA_PASSWORD = "";
|
||||||
|
|
@ -31,7 +37,7 @@ void setup() {
|
||||||
|
|
||||||
// Connect to Wi-Fi
|
// Connect to Wi-Fi
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.begin(WIFI_SSID);
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
|
||||||
Serial.print("[WIFI] Connecting to WiFi");
|
Serial.print("[WIFI] Connecting to WiFi");
|
||||||
|
|
||||||
|
|
@ -43,7 +49,7 @@ void setup() {
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
||||||
|
|
||||||
// Initialize the Alexa client
|
// Initialize the Alexa client (MQTT username, MQTT password, root topic)
|
||||||
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
||||||
|
|
||||||
// Register a new device with a unique ID and name
|
// Register a new device with a unique ID and name
|
||||||
|
|
@ -81,7 +87,7 @@ void setup() {
|
||||||
}else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) {
|
}else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) {
|
||||||
if(directive["header"]["name"]=="SetBrightness"){
|
if(directive["header"]["name"]=="SetBrightness"){
|
||||||
brightness=directive["payload"]["brightness"];
|
brightness=directive["payload"]["brightness"];
|
||||||
Serial.println("setting brightness to: "+brightness);
|
Serial.printf("setting brightness to: %d\n", brightness);
|
||||||
outputState = PowerController::ON;
|
outputState = PowerController::ON;
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
|
@ -101,7 +107,7 @@ void loop() {
|
||||||
// Process Alexa client communication
|
// Process Alexa client communication
|
||||||
alexClient.loop();
|
alexClient.loop();
|
||||||
|
|
||||||
// Update the state of the LED based on the power controller state and brightness
|
// Update the state of the LED based on the power controller state and brightness (the on-board LED is active-low)
|
||||||
if(outputState == PowerController::ON){
|
if(outputState == PowerController::ON){
|
||||||
analogWrite(LED_BUILTIN,map(brightness,0,100,255,0));
|
analogWrite(LED_BUILTIN,map(brightness,0,100,255,0));
|
||||||
}else{
|
}else{
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,14 @@
|
||||||
|
|
||||||
#include <Alex2ESP.h>
|
#include <Alex2ESP.h>
|
||||||
|
|
||||||
|
// The on-board LED: GPIO2 on a Wemos D1 mini (active-low). Defined here for boards whose variant leaves it out.
|
||||||
|
#ifndef LED_BUILTIN
|
||||||
|
#define LED_BUILTIN 2
|
||||||
|
#endif
|
||||||
|
|
||||||
// Define constants for WiFi and Alexa Client configuration
|
// Define constants for WiFi and Alexa Client configuration
|
||||||
const char *WIFI_SSID = "";
|
const char *WIFI_SSID = "";
|
||||||
|
const char *WIFI_PASSWORD = "";
|
||||||
|
|
||||||
const char *ALEXA_USERNAME = "";
|
const char *ALEXA_USERNAME = "";
|
||||||
const char *ALEXA_PASSWORD = "";
|
const char *ALEXA_PASSWORD = "";
|
||||||
|
|
@ -32,7 +38,7 @@ void setup() {
|
||||||
|
|
||||||
// Connect to Wi-Fi
|
// Connect to Wi-Fi
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.begin(WIFI_SSID);
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
|
||||||
Serial.print("[WIFI] Connecting to WiFi");
|
Serial.print("[WIFI] Connecting to WiFi");
|
||||||
|
|
||||||
|
|
@ -44,7 +50,7 @@ void setup() {
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
||||||
|
|
||||||
// Initialize the Alexa client
|
// Initialize the Alexa client (MQTT username, MQTT password, root topic)
|
||||||
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
||||||
|
|
||||||
// Register a new device with a unique ID and name
|
// Register a new device with a unique ID and name
|
||||||
|
|
@ -86,13 +92,13 @@ void setup() {
|
||||||
}else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) {
|
}else if (type == AlexaInterfaceType::BRIGHTNESS_CONTROLLER) {
|
||||||
if(directive["header"]["name"]=="SetBrightness"){
|
if(directive["header"]["name"]=="SetBrightness"){
|
||||||
brightness=directive["payload"]["brightness"];
|
brightness=directive["payload"]["brightness"];
|
||||||
Serial.println("setting brightness to: "+brightness);
|
Serial.printf("setting brightness to: %d\n", brightness);
|
||||||
outputState = PowerController::ON;
|
outputState = PowerController::ON;
|
||||||
}
|
}
|
||||||
}else if (type == AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER) {
|
}else if (type == AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER) {
|
||||||
if(directive["header"]["name"]=="SetColorTemperature"){
|
if(directive["header"]["name"]=="SetColorTemperature"){
|
||||||
colorTemp=directive["payload"]["colorTemperatureInKelvin"];
|
colorTemp=directive["payload"]["colorTemperatureInKelvin"];
|
||||||
Serial.println("setting color temperature to: "+colorTemp);
|
Serial.printf("setting color temperature to: %d\n", colorTemp);
|
||||||
outputState = PowerController::ON;
|
outputState = PowerController::ON;
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
|
@ -113,7 +119,7 @@ void loop() {
|
||||||
// Process Alexa client communication
|
// Process Alexa client communication
|
||||||
alexClient.loop();
|
alexClient.loop();
|
||||||
|
|
||||||
// Update the state of the LED based on the power controller state and brightness
|
// Update the state of the LED based on the power controller state and brightness (the on-board LED is active-low)
|
||||||
if(outputState == PowerController::ON){
|
if(outputState == PowerController::ON){
|
||||||
analogWrite(LED_BUILTIN,map(brightness,0,100,255,0));
|
analogWrite(LED_BUILTIN,map(brightness,0,100,255,0));
|
||||||
}else{
|
}else{
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
// Define constants for WiFi and Alexa Client configuration
|
// Define constants for WiFi and Alexa Client configuration
|
||||||
const char *WIFI_SSID = "";
|
const char *WIFI_SSID = "";
|
||||||
|
const char *WIFI_PASSWORD = "";
|
||||||
|
|
||||||
const char *ALEXA_USERNAME = "";
|
const char *ALEXA_USERNAME = "";
|
||||||
const char *ALEXA_PASSWORD = "";
|
const char *ALEXA_PASSWORD = "";
|
||||||
|
|
@ -26,7 +27,7 @@ void setup() {
|
||||||
|
|
||||||
// Connect to Wi-Fi
|
// Connect to Wi-Fi
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.begin(WIFI_SSID);
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
||||||
|
|
||||||
Serial.print("[WIFI] Connecting to WiFi");
|
Serial.print("[WIFI] Connecting to WiFi");
|
||||||
|
|
||||||
|
|
@ -38,7 +39,7 @@ void setup() {
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
Serial.printf("[WIFI] Connected to SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
|
||||||
|
|
||||||
// Initialize the Alexa client
|
// Initialize the Alexa client (MQTT username, MQTT password, root topic)
|
||||||
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
||||||
|
|
||||||
// Register a new device with a unique ID and name
|
// Register a new device with a unique ID and name
|
||||||
|
|
|
||||||
76
keywords.txt
76
keywords.txt
|
|
@ -1,7 +1,81 @@
|
||||||
#######################################
|
#######################################
|
||||||
# Syntax Coloring Map For Espalexa
|
# Syntax Coloring Map For Alex2ESP
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# Datatypes (KEYWORD1)
|
# Datatypes (KEYWORD1)
|
||||||
#######################################
|
#######################################
|
||||||
|
|
||||||
|
Alex2ESP KEYWORD1
|
||||||
|
Alex2ESPState KEYWORD1
|
||||||
|
AlexaDevice KEYWORD1
|
||||||
|
AlexaInterface KEYWORD1
|
||||||
|
AlexaInterfaceType KEYWORD1
|
||||||
|
AlexaInterfaceUtils KEYWORD1
|
||||||
|
AlexaStatusMessage KEYWORD1
|
||||||
|
ActionMapping KEYWORD1
|
||||||
|
AlexaActions KEYWORD1
|
||||||
|
AlexaActionsUtils KEYWORD1
|
||||||
|
FriendlyName KEYWORD1
|
||||||
|
DisplayCategory KEYWORD1
|
||||||
|
DisplayCategoryUtils KEYWORD1
|
||||||
|
EndpointHealth KEYWORD1
|
||||||
|
PowerController KEYWORD1
|
||||||
|
TemperatureSensorScale KEYWORD1
|
||||||
|
AlexaUtils KEYWORD1
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
begin KEYWORD2
|
||||||
|
loop KEYWORD2
|
||||||
|
getState KEYWORD2
|
||||||
|
getDisconnectReason KEYWORD2
|
||||||
|
getDevice KEYWORD2
|
||||||
|
setName KEYWORD2
|
||||||
|
getName KEYWORD2
|
||||||
|
getEndpointId KEYWORD2
|
||||||
|
setDisplayCategory KEYWORD2
|
||||||
|
getDisplayCategory KEYWORD2
|
||||||
|
setDescription KEYWORD2
|
||||||
|
getDescription KEYWORD2
|
||||||
|
setManufacturerName KEYWORD2
|
||||||
|
getManufacturerName KEYWORD2
|
||||||
|
setManufacturer KEYWORD2
|
||||||
|
getManufacturer KEYWORD2
|
||||||
|
setModel KEYWORD2
|
||||||
|
getModel KEYWORD2
|
||||||
|
getSoftwareVersion KEYWORD2
|
||||||
|
getDeviceJSON KEYWORD2
|
||||||
|
addCapability KEYWORD2
|
||||||
|
registerEvent KEYWORD2
|
||||||
|
triggerEvent KEYWORD2
|
||||||
|
buildStatusMessage KEYWORD2
|
||||||
|
getType KEYWORD2
|
||||||
|
getTypeString KEYWORD2
|
||||||
|
getVersion KEYWORD2
|
||||||
|
getProps KEYWORD2
|
||||||
|
setInstance KEYWORD2
|
||||||
|
addFriendlyName KEYWORD2
|
||||||
|
isRetrievable KEYWORD2
|
||||||
|
isProactivelyReported KEYWORD2
|
||||||
|
setRetrievable KEYWORD2
|
||||||
|
setProactivelyReported KEYWORD2
|
||||||
|
addActionMapping KEYWORD2
|
||||||
|
getJSON KEYWORD2
|
||||||
|
AddHealthProp KEYWORD2
|
||||||
|
AddPowerControllerProp KEYWORD2
|
||||||
|
AddTemperatureSensorProp KEYWORD2
|
||||||
|
AddBrightnessControllerProp KEYWORD2
|
||||||
|
AddColorTemperatureControllerProp KEYWORD2
|
||||||
|
AddToggleControllerProp KEYWORD2
|
||||||
|
AddContextProp KEYWORD2
|
||||||
|
send KEYWORD2
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
MAX_STATUS_REPORT_SIZE LITERAL1
|
||||||
|
MAX_EVENTS LITERAL1
|
||||||
|
|
|
||||||
47
library.json
Normal file
47
library.json
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"name": "Alex2ESP",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"description": "Companion library for the Alex2MQTT Alexa skill: exposes ESP8266 devices to Alexa through alex2mqtt.stormysdream.club over MQTT (discovery, directives, state reports).",
|
||||||
|
"keywords": [
|
||||||
|
"alexa",
|
||||||
|
"mqtt",
|
||||||
|
"esp8266",
|
||||||
|
"smart home",
|
||||||
|
"voice assistant",
|
||||||
|
"home automation"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.stormysdream.club/platformio/Alex2ESP.git"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "David",
|
||||||
|
"email": "Alex2ESP@stormysdream.club",
|
||||||
|
"maintainer": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"frameworks": [
|
||||||
|
"arduino"
|
||||||
|
],
|
||||||
|
"platforms": [
|
||||||
|
"espressif8266"
|
||||||
|
],
|
||||||
|
"headers": "Alex2ESP.h",
|
||||||
|
"examples": [
|
||||||
|
"examples/*.cpp"
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"owner": "marvinroger",
|
||||||
|
"name": "AsyncMqttClient",
|
||||||
|
"version": "^0.9.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "bblanchon",
|
||||||
|
"name": "ArduinoJson",
|
||||||
|
"version": "^7.2.1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
library.properties
Normal file
11
library.properties
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
name=Alex2ESP
|
||||||
|
version=1.1.0
|
||||||
|
author=David <Alex2ESP@stormysdream.club>
|
||||||
|
maintainer=David <Alex2ESP@stormysdream.club>
|
||||||
|
sentence=A companion library for the Alex2MQTT Alexa Skill (ESP8266).
|
||||||
|
paragraph=Connects ESP8266 devices to alex2mqtt.stormysdream.club so Alexa can discover and control them over MQTT. Needs AsyncMqttClient (with ESPAsyncTCP) and ArduinoJson 7.
|
||||||
|
category=Communication
|
||||||
|
url=https://git.stormysdream.club/platformio/Alex2ESP
|
||||||
|
architectures=esp8266
|
||||||
|
includes=Alex2ESP.h
|
||||||
|
depends=AsyncMqttClient,ArduinoJson,ESP Async TCP
|
||||||
74
readme.md
74
readme.md
|
|
@ -1,6 +1,8 @@
|
||||||
# Alex2ESP
|
# Alex2ESP
|
||||||
|
|
||||||
Alex2ESP is a lightweight Arduino/PlatformIO library for integrating ESP8266 and ESP32 microcontrollers with Amazon Alexa smart home APIs. The library simplifies the process of creating Alexa-compatible devices using MQTT.
|
Alex2ESP is a lightweight Arduino/PlatformIO library for integrating ESP8266 microcontrollers with Amazon Alexa smart home APIs through the [Alex2MQTT](https://alex2mqtt.stormysdream.club/) skill. The library simplifies the process of creating Alexa-compatible devices using MQTT.
|
||||||
|
|
||||||
|
**Supported target: ESP8266** (developed and tested on a Wemos D1 mini). ESP32: untested - the code carries include guards for it, but it has never been compiled or run there.
|
||||||
|
|
||||||
For more details on how to configure Alex2ESP, visit [Alex2MQTT Documentation](https://alex2mqtt.stormysdream.club/).
|
For more details on how to configure Alex2ESP, visit [Alex2MQTT Documentation](https://alex2mqtt.stormysdream.club/).
|
||||||
|
|
||||||
|
|
@ -24,11 +26,36 @@ For more details on how to configure Alex2ESP, visit [Alex2MQTT Documentation](h
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
### Installation
|
### Installation
|
||||||
Download the library as zip or via PlatformIO and include it in your project:
|
|
||||||
|
The library lives on Forgejo at https://git.stormysdream.club/platformio/Alex2ESP (public). It is not in the PlatformIO registry or the Arduino Library Manager: install it from git.
|
||||||
|
|
||||||
|
#### PlatformIO
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[env:d1_mini]
|
||||||
|
platform = espressif8266
|
||||||
|
board = d1_mini
|
||||||
|
framework = arduino
|
||||||
|
monitor_speed = 74880
|
||||||
|
lib_deps =
|
||||||
|
https://git.stormysdream.club/platformio/Alex2ESP.git
|
||||||
|
marvinroger/AsyncMqttClient@^0.9.0
|
||||||
|
bblanchon/ArduinoJson@^7
|
||||||
|
```
|
||||||
|
|
||||||
|
To pin a release instead of `main`, append the tag: `https://git.stormysdream.club/platformio/Alex2ESP.git#v1.1.0`. AsyncMqttClient pulls in ESPAsyncTCP by itself; if PlatformIO notes that more than one `ESPAsyncTCP` package matches, add `esp32async/ESPAsyncTCP@^2.0.0` to `lib_deps` to pick one explicitly. The examples print at 74880 baud, the ESP8266 boot-ROM rate, so the boot messages stay readable in the same monitor.
|
||||||
|
|
||||||
|
#### Arduino IDE
|
||||||
|
|
||||||
|
Download the repository as a ZIP (https://git.stormysdream.club/platformio/Alex2ESP/archive/main.zip) and add it with *Sketch -> Include Library -> Add .ZIP Library*. Install **AsyncMqttClient**, **ArduinoJson** (7.x) and **ESP Async TCP** (by ESP32Async) from the Library Manager. Install the ESP8266 board package and select your board (for example *LOLIN(WEMOS) D1 R2 & mini*).
|
||||||
|
|
||||||
|
Then include the library in your project:
|
||||||
```cpp
|
```cpp
|
||||||
#include <Alex2ESP.h>
|
#include <Alex2ESP.h>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Complete sketches are in [`examples/`](examples/): `basicLight.cpp`, `lightWithBrightness.cpp`, `lightWithColorTemp.cpp`, `tempSensor.cpp` and `blindControl.cpp`. Every example joins Wi-Fi with `WiFi.begin(WIFI_SSID, WIFI_PASSWORD)`; fill in the SSID, the password and your Alex2MQTT credentials before flashing.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Creating a Basic Device
|
## Creating a Basic Device
|
||||||
|
|
@ -41,7 +68,7 @@ First create an instance of the alexa client and initilize it with your MQTT Cre
|
||||||
Alex2ESP alexClient;
|
Alex2ESP alexClient;
|
||||||
```
|
```
|
||||||
|
|
||||||
Credentials can be found at https://alex2mqtt.stormysdream.club/ after logging in with amazon.
|
Credentials can be found at https://alex2mqtt.stormysdream.club/ after logging in with amazon. `begin()` takes the MQTT username, the MQTT password and the root topic, in that order (the same order as alex2node's `new Alex2MQTT(username, password, rootTopic)`).
|
||||||
```cpp
|
```cpp
|
||||||
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
alexClient.begin(ALEXA_USERNAME, ALEXA_PASSWORD, ALEXA_ROOT_TOPIC);
|
||||||
```
|
```
|
||||||
|
|
@ -80,6 +107,9 @@ Once initilized you can begin to add virtual devices, in this example we add a p
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The pointers returned by `getDevice()` and `addCapability()` stay valid for the lifetime of the client, so keeping them in globals and adding more devices or capabilities later is fine.
|
||||||
|
|
||||||
---
|
---
|
||||||
### Example: Toggle Controller for Blinds
|
### Example: Toggle Controller for Blinds
|
||||||
Alex2ESP also supports action mapping so we can map keywords such as "open" and "close" to a toggle controller
|
Alex2ESP also supports action mapping so we can map keywords such as "open" and "close" to a toggle controller
|
||||||
|
|
@ -87,7 +117,7 @@ For a comprehensive list of Alexa actions and mappings, visit the [Alexa Develop
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
AlexaDevice* device = alexClient.getDevice("Bedroom Blinds", "ESP-01");
|
AlexaDevice* device = alexClient.getDevice("Bedroom Blinds", "ESP-01");
|
||||||
device->setDisplayCategory(DisplayCategory::LIGHT);
|
device->setDisplayCategory(DisplayCategory::INTERIOR_BLIND);
|
||||||
|
|
||||||
AlexaInterface* toggleController = device->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER);
|
AlexaInterface* toggleController = device->addCapability(AlexaInterfaceType::TOGGLE_CONTROLLER);
|
||||||
toggleController->setInstance("ESP-01.Toggle");
|
toggleController->setInstance("ESP-01.Toggle");
|
||||||
|
|
@ -99,6 +129,16 @@ toggleController->addActionMapping(closeMapping);
|
||||||
toggleController->addActionMapping(openMapping);
|
toggleController->addActionMapping(openMapping);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
An `ActionMapping` takes an optional third argument, the directive payload as JSON text (for example `"{\"rangeValue\": 0}"` for a RangeController). It is emitted as an object in the discovery response and omitted when empty.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How it talks to Alex2MQTT
|
||||||
|
|
||||||
|
- **Discovery.** On `<root>/discover` the library answers with one discovery object per device, published straight to `<root>/discover_r` over MQTT (no HTTP round trip, no queue slot). The backend accepts one endpoint object per message and collects everything that arrives within 1 s for Alexa's discovery answer (up to 5 s for its proactive AddOrUpdate push), so all devices are published back to back the moment the request arrives. Each object sits on the heap (about 1 KB) until the broker acknowledges it; when the MQTT client refuses another one (free heap under 4 KB), the library prints `[Alex2ESP] discovery publish deferred at <endpointId>` and sends the rest from `loop()` as the queue drains, for up to 5 s after the request. `[Alex2ESP] discovery gave up: N device(s) not announced` means those devices missed this answer - on the backend's proactive discovery that can remove them from Alexa until the next one.
|
||||||
|
- **Directives.** A directive arrives as a short id on `<root>/<endpointId>/alexaDirective_e`; the library fetches the full directive over HTTP, fires `ReportState` or `Event` (and `DirectiveReceived`, if registered), and your status report is queued for an HTTP POST that the backend republishes on `<root>/<endpointId>/alexaResponce`, replacing `{REPLACE_WITH_DATETIME}` with the current time.
|
||||||
|
- **Limits.** The send queue holds 5 reports of up to 2047 bytes each. `send()` returns `false` and prints a `[Alex2ESP]` line on Serial when a report does not fit or the queue is full; nothing is ever sent truncated. Debug logging of the library's internals is compiled in by defining `Alex2ESP_DEBUG` in `AlexaUtils.cpp`; credentials are never printed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Interface Types
|
## Interface Types
|
||||||
|
|
@ -171,7 +211,7 @@ toggleController->addActionMapping(openMapping);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
## Advanced ussage
|
## Advanced usage
|
||||||
For devices with limited or partial support, Alex2ESP provides functions that allow you to attach custom JSON objects to the status report. For example, to report the status of a PowerController type, you can use the `AddPowerControllerProp` function. However, if a specific "add props" function does not exist for your use case, you can utilize the `AddContextProp` function to pass a custom JSON object.
|
For devices with limited or partial support, Alex2ESP provides functions that allow you to attach custom JSON objects to the status report. For example, to report the status of a PowerController type, you can use the `AddPowerControllerProp` function. However, if a specific "add props" function does not exist for your use case, you can utilize the `AddContextProp` function to pass a custom JSON object.
|
||||||
|
|
||||||
For instance, to manually report the state of a PowerController, you can use the following code:
|
For instance, to manually report the state of a PowerController, you can use the following code:
|
||||||
|
|
@ -189,6 +229,29 @@ For instance, to manually report the state of a PowerController, you can use the
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### 1.1.0
|
||||||
|
|
||||||
|
Behaviour changes:
|
||||||
|
- `begin()` now takes `(username, password, rootTopic)`, the order every example and this readme always used (and the order alex2node uses). 1.0.0 declared `(rootTopic, username, password)`, so a sketch written from the examples could not authenticate with the broker.
|
||||||
|
- Discovery answers go straight to MQTT (`<root>/discover_r`), one object per device, all devices at once; if the MQTT client cannot take another object (free heap under 4 KB) the rest is sent from `loop()` as the client's queue drains, for up to 5 s. 1.0.0 pushed them one per `loop()` through a 5-slot HTTP queue and silently dropped the sixth device onwards, which the backend then removed from Alexa.
|
||||||
|
- `AlexaStatusMessage::send()` returns `bool`: an oversized report (over 2047 bytes) or a full queue is reported on Serial and dropped instead of being sent truncated. `AlexaUtils::enqueue()` refuses packets that would not fit instead of truncating them.
|
||||||
|
- Discovery JSON: `semantics` is emitted only when a capability has action mappings; an `ActionMapping` directive payload is emitted as a JSON object (parsed from the text you pass) or omitted when empty, instead of the string `"{}"`.
|
||||||
|
- `Response`/`StateReport` events include the required `"payload": {}`.
|
||||||
|
- Devices and capabilities are stored in `std::deque`; pointers from `getDevice()`/`addCapability()` no longer dangle once another device or capability is added.
|
||||||
|
- The MQTT payload is copied using its length (no write past the client's buffer); fragmented directive ids are ignored (a Discover is answered on its first fragment, its payload is unused).
|
||||||
|
- `begin()` no longer prints the MQTT credentials to Serial in debug builds.
|
||||||
|
- `DirectiveReceived` no longer prints "No event registered" when the sketch has not registered it.
|
||||||
|
- Removed: `Alex2ESP::messageSplitAndSend` (declared, never defined) and `AlexaStatusMessage::setEndpointId` (did nothing).
|
||||||
|
- Reported `softwareVersion`/`firmwareVersion` in the discovery attributes are now `1.1.0`.
|
||||||
|
|
||||||
|
Examples: `WiFi.begin(WIFI_SSID, WIFI_PASSWORD)` (1.0.0 could only join open networks), `LED_BUILTIN` fallback, fixed brightness/colour-temperature Serial output, blinds use `DisplayCategory::INTERIOR_BLIND`.
|
||||||
|
|
||||||
|
Packaging: `library.json` and `library.properties` restored with the Forgejo URL and the dependencies, `LICENSE` (MIT), a real `keywords.txt`, LF line endings (`.gitattributes`), ArduinoJson 7 deprecated calls replaced (warning-free build). ESP8266 is the supported target; ESP32 is untested.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Feel free to submit pull requests or issues for feature requests and bug fixes.
|
Feel free to submit pull requests or issues for feature requests and bug fixes.
|
||||||
|
|
||||||
|
|
@ -196,4 +259,3 @@ Feel free to submit pull requests or issues for feature requests and bug fixes.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
This project is licensed under the MIT License. See the LICENSE file for details.
|
This project is licensed under the MIT License. See the LICENSE file for details.
|
||||||
|
|
||||||
|
|
|
||||||
108
src/Alex2ESP.cpp
108
src/Alex2ESP.cpp
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* @title Alex2ESP Library
|
* @title Alex2ESP Library
|
||||||
* @version 1.0.0
|
* @version 1.1.0
|
||||||
* @author David
|
* @author David
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @contributors chaos511
|
* @contributors chaos511
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
#include "Alex2ESP.h"
|
#include "Alex2ESP.h"
|
||||||
|
|
||||||
Alex2ESP::Alex2ESP()
|
Alex2ESP::Alex2ESP()
|
||||||
: rootTopic(), mqttUsername(nullptr), mqttPassword(nullptr), _state(Alex2ESPState::UNINITIALIZED), _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {}
|
: rootTopic(), mqttUsername(nullptr), mqttPassword(nullptr), lastReconnectTime(0), reconnectAttempt(0), _state(Alex2ESPState::UNINITIALIZED), _disconnectReason(AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {}
|
||||||
|
|
||||||
void Alex2ESP::begin(const char *rootTopic, const char *username, const char *password)
|
void Alex2ESP::begin(const char *username, const char *password, const char *rootTopic)
|
||||||
{
|
{
|
||||||
|
|
||||||
_state = Alex2ESPState::INITIALIZED;
|
_state = Alex2ESPState::INITIALIZED;
|
||||||
|
|
@ -30,13 +30,9 @@ void Alex2ESP::begin(const char *rootTopic, const char *username, const char *pa
|
||||||
|
|
||||||
TopicESP = (String(this->rootTopic) + String("/+/alexaDirective_e"));
|
TopicESP = (String(this->rootTopic) + String("/+/alexaDirective_e"));
|
||||||
|
|
||||||
// Log the root topic and credentials
|
// Log the root topic (never the credentials)
|
||||||
AlexaUtils::log("Setting root topic: ");
|
AlexaUtils::log("Setting root topic: ");
|
||||||
AlexaUtils::log(rootTopic);
|
AlexaUtils::logln(rootTopic);
|
||||||
AlexaUtils::log(" with creds ");
|
|
||||||
AlexaUtils::log(username);
|
|
||||||
AlexaUtils::log(":");
|
|
||||||
AlexaUtils::logln(password);
|
|
||||||
AlexaUtils::printMemoryInfo();
|
AlexaUtils::printMemoryInfo();
|
||||||
|
|
||||||
// Set up MQTT client callbacks
|
// Set up MQTT client callbacks
|
||||||
|
|
@ -98,24 +94,26 @@ void Alex2ESP::onMessage(char *topic, char *payload, AsyncMqttClientMessagePrope
|
||||||
|
|
||||||
if (strcmp(topic, discoverTopic.c_str()) == 0)
|
if (strcmp(topic, discoverTopic.c_str()) == 0)
|
||||||
{
|
{
|
||||||
|
// The payload is unused: answer once per message even when TCP split it, starting over if an answer is still going out
|
||||||
// boolean successful = messagePackExtractor(topic, payload, length, index, total);
|
if (index == 0)
|
||||||
|
|
||||||
// if (successful && inputDoc["name"] == "Discover")
|
|
||||||
// {
|
|
||||||
for (const AlexaDevice &device : devices)
|
|
||||||
{
|
{
|
||||||
AlexaUtils::logln("Getting device json");
|
discoveryNext = 0;
|
||||||
JsonDocument jsonData = device.getDeviceJSON();
|
discoveryPending = false;
|
||||||
String jsonString;
|
discoveryStarted = millis();
|
||||||
serializeJson(jsonData, jsonString);
|
publishDiscovery();
|
||||||
jsonData.clear();
|
|
||||||
AlexaUtils::enqueue(jsonString.c_str(), discoverTopicSend.c_str());
|
|
||||||
}
|
}
|
||||||
// }
|
}
|
||||||
|
else if (index != 0 || total != length)
|
||||||
|
{
|
||||||
|
// AsyncMqttClient hands a large publish over in fragments; a directive id never is, so a fragment can only be part of one
|
||||||
|
AlexaUtils::logln("Ignoring fragmented directive id");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// The payload is not NUL-terminated and belongs to the MQTT client: copy exactly `length` bytes out of it
|
||||||
|
String uuid;
|
||||||
|
uuid.concat(payload, length);
|
||||||
|
|
||||||
for (auto &device : devices)
|
for (auto &device : devices)
|
||||||
{
|
{
|
||||||
String directiveTopic = String(rootTopic) + "/" + device.getEndpointId() + "/alexaDirective_e";
|
String directiveTopic = String(rootTopic) + "/" + device.getEndpointId() + "/alexaDirective_e";
|
||||||
|
|
@ -123,9 +121,11 @@ void Alex2ESP::onMessage(char *topic, char *payload, AsyncMqttClientMessagePrope
|
||||||
if (strcmp(topic, directiveTopic.c_str()) == 0)
|
if (strcmp(topic, directiveTopic.c_str()) == 0)
|
||||||
{
|
{
|
||||||
AlexaUtils::logln(topic);
|
AlexaUtils::logln(topic);
|
||||||
payload[length] = '\0';
|
AlexaUtils::logln(uuid);
|
||||||
AlexaUtils::logln(payload);
|
if (!AlexaUtils::enqueueReceive(uuid.c_str()))
|
||||||
AlexaUtils::enqueueReceive(payload);
|
{
|
||||||
|
Serial.println("[Alex2ESP] receive queue full, directive dropped");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,6 +134,57 @@ void Alex2ESP::onMessage(char *topic, char *payload, AsyncMqttClientMessagePrope
|
||||||
clearToSend = true;
|
clearToSend = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Answer a Discover: one discovery object per device, published straight to <root>/discover_r over MQTT (async, no
|
||||||
|
// HTTP round trip, no queue slot). The backend accepts one endpoint object per message and collects everything that
|
||||||
|
// arrives within 1 s for Alexa's answer (5 s for its proactive push). publish() copies the object into the client's
|
||||||
|
// out-queue, where it stays on the heap until the broker has acknowledged it, and returns 0 once the free heap drops
|
||||||
|
// under 4 KB (or the client is not connected) - so rather than skip that device we stop, remember where we got to and
|
||||||
|
// let loop() carry on once the queue has drained, inside the backend's 5 s window.
|
||||||
|
void Alex2ESP::publishDiscovery()
|
||||||
|
{
|
||||||
|
while (discoveryNext < devices.size())
|
||||||
|
{
|
||||||
|
const AlexaDevice &device = devices[discoveryNext];
|
||||||
|
AlexaUtils::logln("Getting device json");
|
||||||
|
JsonDocument jsonData = device.getDeviceJSON();
|
||||||
|
String jsonString;
|
||||||
|
serializeJson(jsonData, jsonString);
|
||||||
|
jsonData.clear();
|
||||||
|
if (mqttClient.publish(discoverTopicSend.c_str(), 0, false, jsonString.c_str(), jsonString.length()) == 0)
|
||||||
|
{
|
||||||
|
if (!discoveryPending)
|
||||||
|
{
|
||||||
|
Serial.print("[Alex2ESP] discovery publish deferred at ");
|
||||||
|
Serial.println(device.getEndpointId());
|
||||||
|
}
|
||||||
|
discoveryPending = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
discoveryNext++;
|
||||||
|
}
|
||||||
|
discoveryNext = 0;
|
||||||
|
discoveryPending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish a discovery answer that publishDiscovery() had to cut short, or give it up once the backend has stopped listening
|
||||||
|
void Alex2ESP::finishDiscovery()
|
||||||
|
{
|
||||||
|
if (!discoveryPending)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (millis() - discoveryStarted > DISCOVERY_WINDOW_MS)
|
||||||
|
{
|
||||||
|
Serial.printf("[Alex2ESP] discovery gave up: %u device(s) not announced\n", (unsigned)(devices.size() - discoveryNext));
|
||||||
|
discoveryNext = 0;
|
||||||
|
discoveryPending = false;
|
||||||
|
}
|
||||||
|
else if (mqttClient.connected())
|
||||||
|
{
|
||||||
|
publishDiscovery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// boolean Alex2ESP::messagePackExtractor(char *topic, char *payload, size_t length, size_t index, size_t total)
|
// boolean Alex2ESP::messagePackExtractor(char *topic, char *payload, size_t length, size_t index, size_t total)
|
||||||
// {
|
// {
|
||||||
// payload[length] = '\0'; // Truncate the payload
|
// payload[length] = '\0'; // Truncate the payload
|
||||||
|
|
@ -333,7 +384,7 @@ void Alex2ESP::processHttpGet()
|
||||||
String payloadStr = httpGET.getString();
|
String payloadStr = httpGET.getString();
|
||||||
size_t length = payloadStr.length();
|
size_t length = payloadStr.length();
|
||||||
|
|
||||||
if (length < AlexaUtils::MAX_PAYLOAD_LENGTH - 1)
|
if (length < (size_t)(AlexaUtils::MAX_PAYLOAD_LENGTH - 1))
|
||||||
{
|
{
|
||||||
payloadStr.toCharArray(AlexaUtils::receivePayload, length + 1);
|
payloadStr.toCharArray(AlexaUtils::receivePayload, length + 1);
|
||||||
payloadStr = "";
|
payloadStr = "";
|
||||||
|
|
@ -353,11 +404,11 @@ void Alex2ESP::processHttpGet()
|
||||||
|
|
||||||
for (auto &device : devices)
|
for (auto &device : devices)
|
||||||
{
|
{
|
||||||
if (strcmp(device.getEndpointId().c_str(), inputDoc["directive"]["endpoint"]["endpointId"]) == 0)
|
if (strcmp(device.getEndpointId().c_str(), inputDoc["directive"]["endpoint"]["endpointId"] | "") == 0)
|
||||||
{
|
{
|
||||||
serializeJson(inputDoc, Serial);
|
serializeJson(inputDoc, Serial);
|
||||||
Serial.println();
|
Serial.println();
|
||||||
device.triggerEvent("DirectiveReceived", inputDoc["directive"], AlexaInterfaceType::UNKNOWN);
|
device.triggerEvent("DirectiveReceived", inputDoc["directive"], AlexaInterfaceType::UNKNOWN, false);
|
||||||
|
|
||||||
if (inputDoc["directive"]["header"]["name"] == "ReportState")
|
if (inputDoc["directive"]["header"]["name"] == "ReportState")
|
||||||
{
|
{
|
||||||
|
|
@ -380,6 +431,7 @@ void Alex2ESP::loop()
|
||||||
{
|
{
|
||||||
|
|
||||||
handleMqttReconnection();
|
handleMqttReconnection();
|
||||||
|
finishDiscovery();
|
||||||
if(clearToSend){
|
if(clearToSend){
|
||||||
processHttpPost();
|
processHttpPost();
|
||||||
processHttpGet();
|
processHttpGet();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* @title Alex2ESP Library
|
* @title Alex2ESP Library
|
||||||
* @version 1.0.0
|
* @version 1.1.0
|
||||||
* @author David
|
* @author David
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @contributors chaos511
|
* @contributors chaos511
|
||||||
|
|
@ -14,17 +14,20 @@
|
||||||
|
|
||||||
#ifndef ALEX2ESP_H
|
#ifndef ALEX2ESP_H
|
||||||
#define ALEX2ESP_H
|
#define ALEX2ESP_H
|
||||||
class AlexaDevice;
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <AsyncMqttClient.h>
|
#include <AsyncMqttClient.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include "AlexaDevice.h"
|
#include "AlexaDevice.h"
|
||||||
#include <AlexaInterface.h>
|
#include "AlexaInterface.h"
|
||||||
#include <vector>
|
#include "AlexaUtils.h"
|
||||||
#include <AlexaUtils.h>
|
#include <deque>
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <HTTPClient.h> // ESP32: untested
|
||||||
|
#else
|
||||||
#include <ESP8266HTTPClient.h>
|
#include <ESP8266HTTPClient.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
enum class Alex2ESPState
|
enum class Alex2ESPState
|
||||||
|
|
@ -43,21 +46,27 @@ public:
|
||||||
// Constructor
|
// Constructor
|
||||||
Alex2ESP();
|
Alex2ESP();
|
||||||
|
|
||||||
// Begin function for initialization
|
// Begin function for initialization: MQTT username, MQTT password, root topic (the same order as alex2node)
|
||||||
void begin(const char *rootTopic, const char *username, const char *password);
|
void begin(const char *username, const char *password, const char *rootTopic);
|
||||||
Alex2ESPState getState() const;
|
Alex2ESPState getState() const;
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
AsyncMqttClientDisconnectReason getDisconnectReason() const;
|
AsyncMqttClientDisconnectReason getDisconnectReason() const;
|
||||||
|
|
||||||
|
// Returns the device with this endpointId, creating it on first use. The pointer stays valid for the lifetime
|
||||||
|
// of the client: devices live in a std::deque, which never relocates its elements when another one is added.
|
||||||
AlexaDevice *getDevice(const String &name, const String &endpointId);
|
AlexaDevice *getDevice(const String &name, const String &endpointId);
|
||||||
void messageSplitAndSend(String messagepackString, String topic);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int MAX_RETRY_COUNT = 2; // Define maximum retry count
|
static const int MAX_RETRY_COUNT = 2; // Define maximum retry count
|
||||||
|
static const unsigned long DISCOVERY_WINDOW_MS = 5000; // How long the backend keeps collecting a discovery answer
|
||||||
int retryCountPOST=0;
|
int retryCountPOST=0;
|
||||||
int retryCountGET=0;
|
int retryCountGET=0;
|
||||||
|
|
||||||
|
size_t discoveryNext = 0; // Next device to announce while a discovery answer is still going out
|
||||||
|
bool discoveryPending = false; // publishDiscovery() stopped early (client out-queue full); loop() finishes it
|
||||||
|
unsigned long discoveryStarted = 0; // millis() when the Discover arrived
|
||||||
|
|
||||||
boolean clearToSend=false;
|
boolean clearToSend=false;
|
||||||
AsyncMqttClient mqttClient; // MQTT client instance
|
AsyncMqttClient mqttClient; // MQTT client instance
|
||||||
String rootTopic; // Root topic for communication
|
String rootTopic; // Root topic for communication
|
||||||
|
|
@ -79,7 +88,7 @@ private:
|
||||||
WiFiClient wifiPOST;
|
WiFiClient wifiPOST;
|
||||||
JsonDocument inputDoc;
|
JsonDocument inputDoc;
|
||||||
|
|
||||||
std::vector<AlexaDevice> devices; // Collection of devices
|
std::deque<AlexaDevice> devices; // Collection of devices (deque: pointers handed out by getDevice stay valid)
|
||||||
|
|
||||||
Alex2ESPState _state;
|
Alex2ESPState _state;
|
||||||
AsyncMqttClientDisconnectReason _disconnectReason;
|
AsyncMqttClientDisconnectReason _disconnectReason;
|
||||||
|
|
@ -96,6 +105,8 @@ private:
|
||||||
|
|
||||||
//loop processing function
|
//loop processing function
|
||||||
void handleMqttReconnection();
|
void handleMqttReconnection();
|
||||||
|
void publishDiscovery();
|
||||||
|
void finishDiscovery();
|
||||||
void processHttpPost();
|
void processHttpPost();
|
||||||
void processHttpGet();
|
void processHttpGet();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,13 +106,13 @@ JsonDocument AlexaDevice::getDeviceJSON() const {
|
||||||
additionalAttributes["manufacturer"] = manufacturer;
|
additionalAttributes["manufacturer"] = manufacturer;
|
||||||
additionalAttributes["model"] = model;
|
additionalAttributes["model"] = model;
|
||||||
additionalAttributes["serialNumber"] = "ESP2Alex";
|
additionalAttributes["serialNumber"] = "ESP2Alex";
|
||||||
additionalAttributes["firmwareVersion"] = "1.0.0";
|
additionalAttributes["firmwareVersion"] = "1.1.0";
|
||||||
additionalAttributes["softwareVersion"] = softwareVersion;
|
additionalAttributes["softwareVersion"] = softwareVersion;
|
||||||
additionalAttributes["customIdentifier"] = "ESP2Alex";
|
additionalAttributes["customIdentifier"] = "ESP2Alex";
|
||||||
|
|
||||||
JsonArray capabilitiesArray = json["capabilities"].to<JsonArray>();
|
JsonArray capabilitiesArray = json["capabilities"].to<JsonArray>();
|
||||||
|
|
||||||
for (AlexaInterface capability : capabilities) {
|
for (const AlexaInterface& capability : capabilities) {
|
||||||
capabilitiesArray.add(capability.getJSON().as<JsonObject>());
|
capabilitiesArray.add(capability.getJSON().as<JsonObject>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,7 +150,7 @@ void AlexaDevice::registerEvent(const char* eventName, void (*callback)(const Js
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger the event and invoke the corresponding callback
|
// Trigger the event and invoke the corresponding callback
|
||||||
void AlexaDevice::triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type) const {
|
void AlexaDevice::triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type, bool warnIfMissing) const {
|
||||||
for (int i = 0; i < MAX_EVENTS; ++i) {
|
for (int i = 0; i < MAX_EVENTS; ++i) {
|
||||||
if (eventNames[i] != nullptr && strcmp(eventNames[i], eventName) == 0) {
|
if (eventNames[i] != nullptr && strcmp(eventNames[i], eventName) == 0) {
|
||||||
// Found the event name, call the corresponding callback function
|
// Found the event name, call the corresponding callback function
|
||||||
|
|
@ -159,6 +159,8 @@ void AlexaDevice::triggerEvent(const char* eventName, const JsonDocument& direct
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If no event was found, print an error message
|
// If no event was found, print an error message
|
||||||
|
if (warnIfMissing) {
|
||||||
Serial.print("No event registered for: ");
|
Serial.print("No event registered for: ");
|
||||||
Serial.println(eventName);
|
Serial.println(eventName);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <AlexaStatusMessage.h>
|
#include <deque>
|
||||||
#include <Alex2ESP.h>
|
#include "AlexaStatusMessage.h"
|
||||||
|
|
||||||
|
|
||||||
#define MAX_EVENTS 10
|
#define MAX_EVENTS 10
|
||||||
|
|
@ -173,11 +173,14 @@ public:
|
||||||
|
|
||||||
JsonDocument getDeviceJSON() const;
|
JsonDocument getDeviceJSON() const;
|
||||||
|
|
||||||
|
// Returns the capability of this type, creating it on first use. The pointer stays valid for the lifetime of
|
||||||
|
// the device: capabilities live in a std::deque, which never relocates its elements when another one is added.
|
||||||
AlexaInterface* addCapability(AlexaInterfaceType type);
|
AlexaInterface* addCapability(AlexaInterfaceType type);
|
||||||
|
|
||||||
void registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&));
|
void registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&));
|
||||||
|
|
||||||
void triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type) const;
|
// warnIfMissing=false keeps optional events (DirectiveReceived) quiet when the sketch did not register them
|
||||||
|
void triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type, bool warnIfMissing = true) const;
|
||||||
|
|
||||||
AlexaStatusMessage buildStatusMessage(const String& correlationToken,const bool isResponse=false) {
|
AlexaStatusMessage buildStatusMessage(const String& correlationToken,const bool isResponse=false) {
|
||||||
return AlexaStatusMessage(correlationToken,rootTopic,endpointId,isResponse);
|
return AlexaStatusMessage(correlationToken,rootTopic,endpointId,isResponse);
|
||||||
|
|
@ -195,9 +198,9 @@ private:
|
||||||
String manufacturerName = "Alexa2MQTT";
|
String manufacturerName = "Alexa2MQTT";
|
||||||
String manufacturer = "Alexa2MQTT";
|
String manufacturer = "Alexa2MQTT";
|
||||||
String model = "Alexa2MQTT";
|
String model = "Alexa2MQTT";
|
||||||
const String softwareVersion = "1.0.0";
|
const String softwareVersion = "1.1.0";
|
||||||
|
|
||||||
std::vector<AlexaInterface> capabilities;
|
std::deque<AlexaInterface> capabilities; // deque: pointers handed out by addCapability stay valid
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
#define ALEXA_INTERFACE_H
|
#define ALEXA_INTERFACE_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
// Enum for Alexa Interface Types
|
// Enum for Alexa Interface Types
|
||||||
enum class AlexaInterfaceType
|
enum class AlexaInterfaceType
|
||||||
|
|
@ -584,11 +586,11 @@ public:
|
||||||
std::vector<AlexaActions> actions; // Array of AlexaActions
|
std::vector<AlexaActions> actions; // Array of AlexaActions
|
||||||
struct Directive {
|
struct Directive {
|
||||||
String name; // Name of the directive
|
String name; // Name of the directive
|
||||||
String payload; // Payload for the directive (can be empty)
|
String payload; // JSON text of the directive's payload object, empty when there is none
|
||||||
} directive; // Directive object containing name and payload
|
} directive; // Directive object containing name and payload
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
ActionMapping(std::vector<AlexaActions> actions, String directiveName, String directivePayload="{}")
|
ActionMapping(std::vector<AlexaActions> actions, String directiveName, String directivePayload="")
|
||||||
: actions(actions) {
|
: actions(actions) {
|
||||||
directive.name = directiveName;
|
directive.name = directiveName;
|
||||||
directive.payload = directivePayload;
|
directive.payload = directivePayload;
|
||||||
|
|
@ -597,7 +599,7 @@ public:
|
||||||
|
|
||||||
|
|
||||||
// Get the JSON representation of ActionMapping
|
// Get the JSON representation of ActionMapping
|
||||||
JsonDocument getJSON() {
|
JsonDocument getJSON() const {
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc["@type"] = type;
|
doc["@type"] = type;
|
||||||
|
|
||||||
|
|
@ -609,8 +611,15 @@ public:
|
||||||
|
|
||||||
JsonObject directiveObj = doc["directive"].to<JsonObject>();
|
JsonObject directiveObj = doc["directive"].to<JsonObject>();
|
||||||
directiveObj["name"] = directive.name;
|
directiveObj["name"] = directive.name;
|
||||||
if (directive.payload) {
|
if (directive.payload.length() > 0) {
|
||||||
directiveObj["payload"] = directive.payload;
|
// Alexa expects the payload as an object, not as a string holding JSON: parse the text we were given
|
||||||
|
JsonDocument payloadDoc;
|
||||||
|
if (deserializeJson(payloadDoc, directive.payload) == DeserializationError::Ok && payloadDoc.is<JsonObject>()) {
|
||||||
|
directiveObj["payload"] = payloadDoc.as<JsonObject>();
|
||||||
|
} else {
|
||||||
|
Serial.print("[Alex2ESP] action mapping payload is not a JSON object, omitted: ");
|
||||||
|
Serial.println(directive.payload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
@ -630,7 +639,7 @@ private:
|
||||||
|
|
||||||
|
|
||||||
String instance;
|
String instance;
|
||||||
bool nameSet;
|
bool nameSet = false;
|
||||||
public:
|
public:
|
||||||
// Constructor
|
// Constructor
|
||||||
AlexaInterface(AlexaInterfaceType type, bool retrievable = true, bool proactivelyReported = false)
|
AlexaInterface(AlexaInterfaceType type, bool retrievable = true, bool proactivelyReported = false)
|
||||||
|
|
@ -662,10 +671,9 @@ public:
|
||||||
actionMappings.push_back(actionMapping);
|
actionMappings.push_back(actionMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonDocument getJSON()
|
JsonDocument getJSON() const
|
||||||
{
|
{
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
String ret;
|
|
||||||
|
|
||||||
// Set base properties
|
// Set base properties
|
||||||
doc["interface"] = getTypeString();
|
doc["interface"] = getTypeString();
|
||||||
|
|
@ -688,13 +696,14 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Add action mappings - semantics only exist when there is something to map (as alex2node does)
|
||||||
|
if (!actionMappings.empty()) {
|
||||||
JsonObject semantics = doc["semantics"].to<JsonObject>();
|
JsonObject semantics = doc["semantics"].to<JsonObject>();
|
||||||
|
|
||||||
// Add action mappings
|
|
||||||
JsonArray actionMappingArray = semantics["actionMappings"].to<JsonArray>();
|
JsonArray actionMappingArray = semantics["actionMappings"].to<JsonArray>();
|
||||||
for (ActionMapping& actionMapping : actionMappings) {
|
for (const ActionMapping& actionMapping : actionMappings) {
|
||||||
actionMappingArray.add(actionMapping.getJSON().as<JsonObject>());
|
actionMappingArray.add(actionMapping.getJSON().as<JsonObject>());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if(nameSet){
|
if(nameSet){
|
||||||
doc["instance"]=instance;
|
doc["instance"]=instance;
|
||||||
}
|
}
|
||||||
|
|
@ -702,14 +711,14 @@ public:
|
||||||
|
|
||||||
// Add capabilityResources if friendlyNames is not empty
|
// Add capabilityResources if friendlyNames is not empty
|
||||||
if (!friendlyNames.empty()) {
|
if (!friendlyNames.empty()) {
|
||||||
JsonObject capabilityResources = doc.createNestedObject("capabilityResources");
|
JsonObject capabilityResources = doc["capabilityResources"].to<JsonObject>();
|
||||||
JsonArray friendlyNamesArray = capabilityResources.createNestedArray("friendlyNames");
|
JsonArray friendlyNamesArray = capabilityResources["friendlyNames"].to<JsonArray>();
|
||||||
|
|
||||||
for (const FriendlyName& fn : friendlyNames) {
|
for (const FriendlyName& fn : friendlyNames) {
|
||||||
JsonObject friendlyNameObj = friendlyNamesArray.createNestedObject();
|
JsonObject friendlyNameObj = friendlyNamesArray.add<JsonObject>();
|
||||||
friendlyNameObj["@type"] = "text";
|
friendlyNameObj["@type"] = "text";
|
||||||
|
|
||||||
JsonObject valueObj = friendlyNameObj.createNestedObject("value");
|
JsonObject valueObj = friendlyNameObj["value"].to<JsonObject>();
|
||||||
valueObj["text"] = fn.text;
|
valueObj["text"] = fn.text;
|
||||||
valueObj["locale"] = fn.locale;
|
valueObj["locale"] = fn.locale;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <Alex2ESP.h>
|
|
||||||
#include <AlexaUtils.h>
|
|
||||||
|
|
||||||
#ifndef ALEXA_STATUS_MESSAGE_H
|
#ifndef ALEXA_STATUS_MESSAGE_H
|
||||||
#define ALEXA_STATUS_MESSAGE_H
|
#define ALEXA_STATUS_MESSAGE_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include "AlexaInterface.h"
|
||||||
|
#include "AlexaUtils.h"
|
||||||
|
|
||||||
#define MAX_STATUS_REPORT_SIZE 2048
|
#define MAX_STATUS_REPORT_SIZE 2048
|
||||||
|
|
||||||
enum class EndpointHealth
|
enum class EndpointHealth
|
||||||
|
|
@ -50,14 +51,10 @@ public:
|
||||||
|
|
||||||
JsonObject event_endpoint = event["endpoint"].to<JsonObject>();
|
JsonObject event_endpoint = event["endpoint"].to<JsonObject>();
|
||||||
event_endpoint["endpointId"] = endpointId;
|
event_endpoint["endpointId"] = endpointId;
|
||||||
|
event["payload"].to<JsonObject>(); // required by Alexa.Response / StateReport, empty when there is nothing to add
|
||||||
contextProperties = doc["context"]["properties"].to<JsonArray>();
|
contextProperties = doc["context"]["properties"].to<JsonArray>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setEndpointId(const String &endpointId)
|
|
||||||
{
|
|
||||||
// event["endpoint"]["endpointId"] = endpointId;
|
|
||||||
}
|
|
||||||
|
|
||||||
AlexaStatusMessage &AddHealthProp(EndpointHealth endpointHealth, unsigned int uncertaintyInMs = 0)
|
AlexaStatusMessage &AddHealthProp(EndpointHealth endpointHealth, unsigned int uncertaintyInMs = 0)
|
||||||
{
|
{
|
||||||
JsonDocument healthValue;
|
JsonDocument healthValue;
|
||||||
|
|
@ -102,14 +99,28 @@ public:
|
||||||
return *this; // Return a reference to the current object
|
return *this; // Return a reference to the current object
|
||||||
}
|
}
|
||||||
|
|
||||||
void send()
|
// Queue the report for delivery. Returns false (and says so on Serial) when the report does not fit the
|
||||||
|
// MAX_STATUS_REPORT_SIZE buffer or the send queue is full: a report is never sent truncated.
|
||||||
|
bool send()
|
||||||
{
|
{
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
|
String topic = rootTopic + "/" + endpointId + "/alexaResponce";
|
||||||
|
size_t needed = measureJson(doc);
|
||||||
|
if (needed > sizeof(outputString) - 1)
|
||||||
|
{
|
||||||
|
Serial.printf("[Alex2ESP] status report for %s is %u bytes, limit is %u - not sent\n",
|
||||||
|
endpointId.c_str(), (unsigned)needed, (unsigned)(sizeof(outputString) - 1));
|
||||||
|
doc.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
serializeJson(doc, outputString);
|
serializeJson(doc, outputString);
|
||||||
doc.clear();
|
doc.clear();
|
||||||
String topic = rootTopic + "/" + endpointId + "/alexaResponce";
|
if (!AlexaUtils::enqueue(outputString, topic.c_str()))
|
||||||
AlexaUtils::enqueue(outputString, topic.c_str());
|
{
|
||||||
|
Serial.println("[Alex2ESP] send queue full, status report dropped");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -148,14 +159,7 @@ private:
|
||||||
if (!instanceName.isEmpty()) {
|
if (!instanceName.isEmpty()) {
|
||||||
prop["instance"] = instanceName;
|
prop["instance"] = instanceName;
|
||||||
}
|
}
|
||||||
if constexpr (std::is_same_v<T, JsonObject>)
|
|
||||||
{
|
|
||||||
prop["value"] = value;
|
prop["value"] = value;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
prop["value"] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
prop["timeOfSample"] = "{REPLACE_WITH_DATETIME}";
|
prop["timeOfSample"] = "{REPLACE_WITH_DATETIME}";
|
||||||
prop["uncertaintyInMilliseconds"] = uncertaintyInMs;
|
prop["uncertaintyInMilliseconds"] = uncertaintyInMs;
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,13 @@ bool AlexaUtils::enqueue(const char* packet,const char* topic) {
|
||||||
return false; // Queue is full
|
return false; // Queue is full
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refuse anything that would be cut short by the slot size - a truncated JSON packet is worthless
|
||||||
|
if (strlen(topic) > MAX_TOPIC_LENGTH - 1 || strlen(packet) > MAX_PACKET_LENGTH - 1) {
|
||||||
|
Serial.printf("[Alex2ESP] packet of %u bytes does not fit the %d byte send slot - dropped\n",
|
||||||
|
(unsigned)strlen(packet), MAX_PACKET_LENGTH - 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Copy the topic and packet into the respective arrays
|
// Copy the topic and packet into the respective arrays
|
||||||
strncpy(topicQueue[queueEnd], topic, MAX_TOPIC_LENGTH - 1);
|
strncpy(topicQueue[queueEnd], topic, MAX_TOPIC_LENGTH - 1);
|
||||||
topicQueue[queueEnd][MAX_TOPIC_LENGTH - 1] = '\0'; // Ensure null-termination
|
topicQueue[queueEnd][MAX_TOPIC_LENGTH - 1] = '\0'; // Ensure null-termination
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ public:
|
||||||
static bool isReceiveQueueEmpty();
|
static bool isReceiveQueueEmpty();
|
||||||
static bool isReceiveQueueFull();
|
static bool isReceiveQueueFull();
|
||||||
|
|
||||||
static bool enqueue(const char* topic, const char* packet);
|
// Queue a packet for the HTTP sender. Returns false (nothing queued) when the queue is full or the
|
||||||
|
// packet/topic would not fit its slot - a packet is never truncated.
|
||||||
|
static bool enqueue(const char* packet, const char* topic);
|
||||||
static bool dequeue(String& topic, String& packet);
|
static bool dequeue(String& topic, String& packet);
|
||||||
static bool isQueueEmpty();
|
static bool isQueueEmpty();
|
||||||
static bool isQueueFull();
|
static bool isQueueFull();
|
||||||
|
|
@ -45,7 +47,11 @@ public:
|
||||||
static void printMemoryInfo()
|
static void printMemoryInfo()
|
||||||
{
|
{
|
||||||
size_t freeHeap = ESP.getFreeHeap();
|
size_t freeHeap = ESP.getFreeHeap();
|
||||||
|
#ifdef ESP8266
|
||||||
size_t freeStack = ESP.getFreeContStack(); // Requires ESP8266 core 3.0.0+
|
size_t freeStack = ESP.getFreeContStack(); // Requires ESP8266 core 3.0.0+
|
||||||
|
#else
|
||||||
|
size_t freeStack = 0; // ESP32: untested, no getFreeContStack()
|
||||||
|
#endif
|
||||||
size_t sketchSize = ESP.getSketchSize();
|
size_t sketchSize = ESP.getSketchSize();
|
||||||
size_t freeSketchSpace = ESP.getFreeSketchSpace();
|
size_t freeSketchSpace = ESP.getFreeSketchSpace();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue