initial release

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

389
src/Alex2ESP.cpp Normal file
View file

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

104
src/Alex2ESP.h Normal file
View file

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

164
src/AlexaDevice.cpp Normal file
View file

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

204
src/AlexaDevice.h Normal file
View file

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

721
src/AlexaInterface.h Normal file
View file

@ -0,0 +1,721 @@
#ifndef ALEXA_INTERFACE_H
#define ALEXA_INTERFACE_H
#include <string>
#include <unordered_map>
#include <Arduino.h>
// Enum for Alexa Interface Types
enum class AlexaInterfaceType
{
APPLICATION_STATE_REPORTER,
AUDIO_PLAY_QUEUE,
AUTHORIZATION_CONTROLLER,
AUTOMATION_MANAGEMENT,
AUTOMOTIVE_VEHICLE_DATA,
BRIGHTNESS_CONTROLLER,
CAMERA_LIVE_VIEW_CONTROLLER,
CAMERA_STREAM_CONTROLLER,
CHANNEL_CONTROLLER,
COLOR_CONTROLLER,
COLOR_TEMPERATURE_CONTROLLER,
COMMISSIONABLE,
CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER,
CONTACT_SENSOR,
COOKING,
COOKING_FOOD_TEMPERATURE_CONTROLLER,
COOKING_FOOD_TEMPERATURE_SENSOR,
COOKING_PRESET_CONTROLLER,
COOKING_TEMPERATURE_CONTROLLER,
COOKING_TEMPERATURE_SENSOR,
COOKING_TIME_CONTROLLER,
DATA_CONTROLLER,
DEVICE_USAGE_ESTIMATION,
DEVICE_USAGE_METER,
DOORBELL_EVENT_SOURCE,
ENDPOINT_HEALTH,
EQUALIZER_CONTROLLER,
INPUT_CONTROLLER,
INVENTORY_LEVEL_SENSOR,
INVENTORY_LEVEL_USAGE_SENSOR,
INVENTORY_USAGE_SENSOR,
KEYPAD_CONTROLLER,
LAUNCHER,
LOCK_CONTROLLER,
MEDIA_PLAYBACK,
MEDIA_PLAY_QUEUE,
MEDIA_SEARCH,
MODE_CONTROLLER,
MOTION_SENSOR,
PERCENTAGE_CONTROLLER,
PLAYBACK_CONTROLLER,
PLAYBACK_STATE_REPORTER,
POWER_CONTROLLER,
POWER_LEVEL_CONTROLLER,
PROACTIVE_NOTIFICATION_SOURCE,
RANGE_CONTROLLER,
RECORD_CONTROLLER,
REMOTE_VIDEO_PLAYER,
RTC_SESSION_CONTROLLER,
SCENE_CONTROLLER,
SECURITY_PANEL_CONTROLLER,
SECURITY_PANEL_CONTROLLER_ALERT,
SEEK_CONTROLLER,
SIMPLE_EVENT_SOURCE,
SMART_VISION_OBJECT_DETECTION_SENSOR,
SMART_VISION_SNAPSHOT_PROVIDER,
SPEAKER,
STEP_SPEAKER,
TEMPERATURE_SENSOR,
THERMOSTAT_CONTROLLER,
THERMOSTAT_CONTROLLER_CONFIGURATION,
THERMOSTAT_CONTROLLER_HVAC_COMPONENTS,
THERMOSTAT_CONTROLLER_SCHEDULE,
TIME_HOLD_CONTROLLER,
TOGGLE_CONTROLLER,
UI_CONTROLLER,
USER_PREFERENCE,
VIDEO_RECORDER,
WAKE_ON_LAN_CONTROLLER,
UNKNOWN
};
// Utility class to handle Alexa Interface type details
class AlexaInterfaceUtils
{
public:
static AlexaInterfaceType fromString(const String& str)
{
if (str == "Alexa.ApplicationStateReporter")
return AlexaInterfaceType::APPLICATION_STATE_REPORTER;
if (str == "Alexa.Audio.PlayQueue")
return AlexaInterfaceType::AUDIO_PLAY_QUEUE;
if (str == "Alexa.AuthorizationController")
return AlexaInterfaceType::AUTHORIZATION_CONTROLLER;
if (str == "Alexa.AutomationManagement")
return AlexaInterfaceType::AUTOMATION_MANAGEMENT;
if (str == "Alexa.Automotive.VehicleData")
return AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA;
if (str == "Alexa.BrightnessController")
return AlexaInterfaceType::BRIGHTNESS_CONTROLLER;
if (str == "Alexa.Camera.LiveViewController")
return AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER;
if (str == "Alexa.CameraStreamController")
return AlexaInterfaceType::CAMERA_STREAM_CONTROLLER;
if (str == "Alexa.ChannelController")
return AlexaInterfaceType::CHANNEL_CONTROLLER;
if (str == "Alexa.ColorController")
return AlexaInterfaceType::COLOR_CONTROLLER;
if (str == "Alexa.ColorTemperatureController")
return AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER;
if (str == "Alexa.Commissionable")
return AlexaInterfaceType::COMMISSIONABLE;
if (str == "Alexa.ConsentManagement.ConsentRequiredReporter")
return AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER;
if (str == "Alexa.ContactSensor")
return AlexaInterfaceType::CONTACT_SENSOR;
if (str == "Alexa.Cooking")
return AlexaInterfaceType::COOKING;
if (str == "Alexa.Cooking.FoodTemperatureController")
return AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_CONTROLLER;
if (str == "Alexa.Cooking.FoodTemperatureSensor")
return AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_SENSOR;
if (str == "Alexa.Cooking.PresetController")
return AlexaInterfaceType::COOKING_PRESET_CONTROLLER;
if (str == "Alexa.Cooking.TemperatureController")
return AlexaInterfaceType::COOKING_TEMPERATURE_CONTROLLER;
if (str == "Alexa.Cooking.TemperatureSensor")
return AlexaInterfaceType::COOKING_TEMPERATURE_SENSOR;
if (str == "Alexa.Cooking.TimeController")
return AlexaInterfaceType::COOKING_TIME_CONTROLLER;
if (str == "Alexa.DataController")
return AlexaInterfaceType::DATA_CONTROLLER;
if (str == "Alexa.DeviceUsage.Estimation")
return AlexaInterfaceType::DEVICE_USAGE_ESTIMATION;
if (str == "Alexa.DeviceUsage.Meter")
return AlexaInterfaceType::DEVICE_USAGE_METER;
if (str == "Alexa.DoorbellEventSource")
return AlexaInterfaceType::DOORBELL_EVENT_SOURCE;
if (str == "Alexa.EndpointHealth")
return AlexaInterfaceType::ENDPOINT_HEALTH;
if (str == "Alexa.EqualizerController")
return AlexaInterfaceType::EQUALIZER_CONTROLLER;
if (str == "Alexa.InputController")
return AlexaInterfaceType::INPUT_CONTROLLER;
if (str == "Alexa.InventoryLevelSensor")
return AlexaInterfaceType::INVENTORY_LEVEL_SENSOR;
if (str == "Alexa.InventoryLevelUsageSensor")
return AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR;
if (str == "Alexa.InventoryUsageSensor")
return AlexaInterfaceType::INVENTORY_USAGE_SENSOR;
if (str == "Alexa.KeypadController")
return AlexaInterfaceType::KEYPAD_CONTROLLER;
if (str == "Alexa.Launcher")
return AlexaInterfaceType::LAUNCHER;
if (str == "Alexa.LockController")
return AlexaInterfaceType::LOCK_CONTROLLER;
if (str == "Alexa.Media.Playback")
return AlexaInterfaceType::MEDIA_PLAYBACK;
if (str == "Alexa.Media.PlayQueue")
return AlexaInterfaceType::MEDIA_PLAY_QUEUE;
if (str == "Alexa.Media.Search")
return AlexaInterfaceType::MEDIA_SEARCH;
if (str == "Alexa.ModeController")
return AlexaInterfaceType::MODE_CONTROLLER;
if (str == "Alexa.MotionSensor")
return AlexaInterfaceType::MOTION_SENSOR;
if (str == "Alexa.PercentageController")
return AlexaInterfaceType::PERCENTAGE_CONTROLLER;
if (str == "Alexa.PlaybackController")
return AlexaInterfaceType::PLAYBACK_CONTROLLER;
if (str == "Alexa.PlaybackStateReporter")
return AlexaInterfaceType::PLAYBACK_STATE_REPORTER;
if (str == "Alexa.PowerController")
return AlexaInterfaceType::POWER_CONTROLLER;
if (str == "Alexa.PowerLevelController")
return AlexaInterfaceType::POWER_LEVEL_CONTROLLER;
if (str == "Alexa.ProactiveNotificationSource")
return AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE;
if (str == "Alexa.RangeController")
return AlexaInterfaceType::RANGE_CONTROLLER;
if (str == "Alexa.RecordController")
return AlexaInterfaceType::RECORD_CONTROLLER;
if (str == "Alexa.RemoteVideoPlayer")
return AlexaInterfaceType::REMOTE_VIDEO_PLAYER;
if (str == "Alexa.RTCSessionController")
return AlexaInterfaceType::RTC_SESSION_CONTROLLER;
if (str == "Alexa.SceneController")
return AlexaInterfaceType::SCENE_CONTROLLER;
if (str == "Alexa.SecurityPanelController")
return AlexaInterfaceType::SECURITY_PANEL_CONTROLLER;
if (str == "Alexa.SecurityPanelController.Alert")
return AlexaInterfaceType::SECURITY_PANEL_CONTROLLER_ALERT;
if (str == "Alexa.SeekController")
return AlexaInterfaceType::SEEK_CONTROLLER;
if (str == "Alexa.SimpleEventSource")
return AlexaInterfaceType::SIMPLE_EVENT_SOURCE;
if (str == "Alexa.SmartVision.ObjectDetectionSensor")
return AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR;
if (str == "Alexa.SmartVision.SnapshotProvider")
return AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER;
if (str == "Alexa.Speaker")
return AlexaInterfaceType::SPEAKER;
if (str == "Alexa.StepSpeaker")
return AlexaInterfaceType::STEP_SPEAKER;
if (str == "Alexa.TemperatureSensor")
return AlexaInterfaceType::TEMPERATURE_SENSOR;
if (str == "Alexa.ThermostatController")
return AlexaInterfaceType::THERMOSTAT_CONTROLLER;
if (str == "Alexa.ThermostatController.Configuration")
return AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION;
if (str == "Alexa.ThermostatController.HVAC.Components")
return AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS;
if (str == "Alexa.ThermostatController.Schedule")
return AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE;
if (str == "Alexa.TimeHoldController")
return AlexaInterfaceType::TIME_HOLD_CONTROLLER;
if (str == "Alexa.ToggleController")
return AlexaInterfaceType::TOGGLE_CONTROLLER;
if (str == "Alexa.UIController")
return AlexaInterfaceType::UI_CONTROLLER;
if (str == "Alexa.UserPreference")
return AlexaInterfaceType::USER_PREFERENCE;
if (str == "Alexa.VideoRecorder")
return AlexaInterfaceType::VIDEO_RECORDER;
if (str == "Alexa.WakeOnLANController")
return AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER;
return AlexaInterfaceType::UNKNOWN;
}
static String toString(AlexaInterfaceType type)
{
switch (type)
{
case AlexaInterfaceType::APPLICATION_STATE_REPORTER:
return "Alexa.ApplicationStateReporter";
case AlexaInterfaceType::AUDIO_PLAY_QUEUE:
return "Alexa.Audio.PlayQueue";
case AlexaInterfaceType::AUTHORIZATION_CONTROLLER:
return "Alexa.AuthorizationController";
case AlexaInterfaceType::AUTOMATION_MANAGEMENT:
return "Alexa.AutomationManagement";
case AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA:
return "Alexa.Automotive.VehicleData";
case AlexaInterfaceType::BRIGHTNESS_CONTROLLER:
return "Alexa.BrightnessController";
case AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER:
return "Alexa.Camera.LiveViewController";
case AlexaInterfaceType::CAMERA_STREAM_CONTROLLER:
return "Alexa.CameraStreamController";
case AlexaInterfaceType::CHANNEL_CONTROLLER:
return "Alexa.ChannelController";
case AlexaInterfaceType::COLOR_CONTROLLER:
return "Alexa.ColorController";
case AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER:
return "Alexa.ColorTemperatureController";
case AlexaInterfaceType::COMMISSIONABLE:
return "Alexa.Commissionable";
case AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER:
return "Alexa.ConsentManagement.ConsentRequiredReporter";
case AlexaInterfaceType::CONTACT_SENSOR:
return "Alexa.ContactSensor";
case AlexaInterfaceType::COOKING:
return "Alexa.Cooking";
case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_CONTROLLER:
return "Alexa.Cooking.FoodTemperatureController";
case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_SENSOR:
return "Alexa.Cooking.FoodTemperatureSensor";
case AlexaInterfaceType::COOKING_PRESET_CONTROLLER:
return "Alexa.Cooking.PresetController";
case AlexaInterfaceType::COOKING_TEMPERATURE_CONTROLLER:
return "Alexa.Cooking.TemperatureController";
case AlexaInterfaceType::COOKING_TEMPERATURE_SENSOR:
return "Alexa.Cooking.TemperatureSensor";
case AlexaInterfaceType::COOKING_TIME_CONTROLLER:
return "Alexa.Cooking.TimeController";
case AlexaInterfaceType::DATA_CONTROLLER:
return "Alexa.DataController";
case AlexaInterfaceType::DEVICE_USAGE_ESTIMATION:
return "Alexa.DeviceUsage.Estimation";
case AlexaInterfaceType::DEVICE_USAGE_METER:
return "Alexa.DeviceUsage.Meter";
case AlexaInterfaceType::DOORBELL_EVENT_SOURCE:
return "Alexa.DoorbellEventSource";
case AlexaInterfaceType::ENDPOINT_HEALTH:
return "Alexa.EndpointHealth";
case AlexaInterfaceType::EQUALIZER_CONTROLLER:
return "Alexa.EqualizerController";
case AlexaInterfaceType::INPUT_CONTROLLER:
return "Alexa.InputController";
case AlexaInterfaceType::INVENTORY_LEVEL_SENSOR:
return "Alexa.InventoryLevelSensor";
case AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR:
return "Alexa.InventoryLevelUsageSensor";
case AlexaInterfaceType::INVENTORY_USAGE_SENSOR:
return "Alexa.InventoryUsageSensor";
case AlexaInterfaceType::KEYPAD_CONTROLLER:
return "Alexa.KeypadController";
case AlexaInterfaceType::LAUNCHER:
return "Alexa.Launcher";
case AlexaInterfaceType::LOCK_CONTROLLER:
return "Alexa.LockController";
case AlexaInterfaceType::MEDIA_PLAYBACK:
return "Alexa.Media.Playback";
case AlexaInterfaceType::MEDIA_PLAY_QUEUE:
return "Alexa.Media.PlayQueue";
case AlexaInterfaceType::MEDIA_SEARCH:
return "Alexa.Media.Search";
case AlexaInterfaceType::MODE_CONTROLLER:
return "Alexa.ModeController";
case AlexaInterfaceType::MOTION_SENSOR:
return "Alexa.MotionSensor";
case AlexaInterfaceType::PERCENTAGE_CONTROLLER:
return "Alexa.PercentageController";
case AlexaInterfaceType::PLAYBACK_CONTROLLER:
return "Alexa.PlaybackController";
case AlexaInterfaceType::PLAYBACK_STATE_REPORTER:
return "Alexa.PlaybackStateReporter";
case AlexaInterfaceType::POWER_CONTROLLER:
return "Alexa.PowerController";
case AlexaInterfaceType::POWER_LEVEL_CONTROLLER:
return "Alexa.PowerLevelController";
case AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE:
return "Alexa.ProactiveNotificationSource";
case AlexaInterfaceType::RANGE_CONTROLLER:
return "Alexa.RangeController";
case AlexaInterfaceType::RECORD_CONTROLLER:
return "Alexa.RecordController";
case AlexaInterfaceType::REMOTE_VIDEO_PLAYER:
return "Alexa.RemoteVideoPlayer";
case AlexaInterfaceType::RTC_SESSION_CONTROLLER:
return "Alexa.RTCSessionController";
case AlexaInterfaceType::SCENE_CONTROLLER:
return "Alexa.SceneController";
case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER:
return "Alexa.SecurityPanelController";
case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER_ALERT:
return "Alexa.SecurityPanelController.Alert";
case AlexaInterfaceType::SEEK_CONTROLLER:
return "Alexa.SeekController";
case AlexaInterfaceType::SIMPLE_EVENT_SOURCE:
return "Alexa.SimpleEventSource";
case AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR:
return "Alexa.SmartVision.ObjectDetectionSensor";
case AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER:
return "Alexa.SmartVision.SnapshotProvider";
case AlexaInterfaceType::SPEAKER:
return "Alexa.Speaker";
case AlexaInterfaceType::STEP_SPEAKER:
return "Alexa.StepSpeaker";
case AlexaInterfaceType::TEMPERATURE_SENSOR:
return "Alexa.TemperatureSensor";
case AlexaInterfaceType::THERMOSTAT_CONTROLLER:
return "Alexa.ThermostatController";
case AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION:
return "Alexa.ThermostatController.Configuration";
case AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS:
return "Alexa.ThermostatController.HVAC.Components";
case AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE:
return "Alexa.ThermostatController.Schedule";
case AlexaInterfaceType::TIME_HOLD_CONTROLLER:
return "Alexa.TimeHoldController";
case AlexaInterfaceType::TOGGLE_CONTROLLER:
return "Alexa.ToggleController";
case AlexaInterfaceType::UI_CONTROLLER:
return "Alexa.UIController";
case AlexaInterfaceType::USER_PREFERENCE:
return "Alexa.UserPreference";
case AlexaInterfaceType::VIDEO_RECORDER:
return "Alexa.VideoRecorder";
case AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER:
return "Alexa.WakeOnLANController";
default:
return "UNKNOWN";
}
}
static String getVersion(AlexaInterfaceType type)
{
switch (type)
{
case AlexaInterfaceType::APPLICATION_STATE_REPORTER:
case AlexaInterfaceType::AUDIO_PLAY_QUEUE:
case AlexaInterfaceType::AUTHORIZATION_CONTROLLER:
case AlexaInterfaceType::AUTOMATION_MANAGEMENT:
case AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA:
case AlexaInterfaceType::COMMISSIONABLE:
case AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER:
case AlexaInterfaceType::COOKING:
case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_CONTROLLER:
case AlexaInterfaceType::COOKING_FOOD_TEMPERATURE_SENSOR:
case AlexaInterfaceType::COOKING_PRESET_CONTROLLER:
case AlexaInterfaceType::COOKING_TEMPERATURE_CONTROLLER:
case AlexaInterfaceType::COOKING_TEMPERATURE_SENSOR:
case AlexaInterfaceType::COOKING_TIME_CONTROLLER:
case AlexaInterfaceType::DATA_CONTROLLER:
case AlexaInterfaceType::DEVICE_USAGE_ESTIMATION:
case AlexaInterfaceType::DEVICE_USAGE_METER:
case AlexaInterfaceType::EQUALIZER_CONTROLLER:
case AlexaInterfaceType::INVENTORY_LEVEL_SENSOR:
case AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR:
case AlexaInterfaceType::INVENTORY_USAGE_SENSOR:
case AlexaInterfaceType::KEYPAD_CONTROLLER:
case AlexaInterfaceType::MEDIA_PLAYBACK:
case AlexaInterfaceType::MEDIA_PLAY_QUEUE:
case AlexaInterfaceType::MEDIA_SEARCH:
case AlexaInterfaceType::PLAYBACK_STATE_REPORTER:
case AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE:
case AlexaInterfaceType::REMOTE_VIDEO_PLAYER:
case AlexaInterfaceType::RTC_SESSION_CONTROLLER:
case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER:
case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER_ALERT:
case AlexaInterfaceType::SIMPLE_EVENT_SOURCE:
case AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR:
case AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER:
case AlexaInterfaceType::SPEAKER:
case AlexaInterfaceType::STEP_SPEAKER:
case AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION:
case AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS:
case AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE:
case AlexaInterfaceType::UI_CONTROLLER:
case AlexaInterfaceType::USER_PREFERENCE:
case AlexaInterfaceType::VIDEO_RECORDER:
case AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER:
return "1";
case AlexaInterfaceType::BRIGHTNESS_CONTROLLER:
case AlexaInterfaceType::CAMERA_STREAM_CONTROLLER:
case AlexaInterfaceType::CHANNEL_CONTROLLER:
case AlexaInterfaceType::COLOR_CONTROLLER:
case AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER:
case AlexaInterfaceType::CONTACT_SENSOR:
case AlexaInterfaceType::DOORBELL_EVENT_SOURCE:
case AlexaInterfaceType::INPUT_CONTROLLER:
case AlexaInterfaceType::LOCK_CONTROLLER:
case AlexaInterfaceType::MODE_CONTROLLER:
case AlexaInterfaceType::MOTION_SENSOR:
case AlexaInterfaceType::PERCENTAGE_CONTROLLER:
case AlexaInterfaceType::PLAYBACK_CONTROLLER:
case AlexaInterfaceType::POWER_CONTROLLER:
case AlexaInterfaceType::POWER_LEVEL_CONTROLLER:
case AlexaInterfaceType::RANGE_CONTROLLER:
case AlexaInterfaceType::RECORD_CONTROLLER:
case AlexaInterfaceType::SCENE_CONTROLLER:
case AlexaInterfaceType::SEEK_CONTROLLER:
case AlexaInterfaceType::TEMPERATURE_SENSOR:
case AlexaInterfaceType::THERMOSTAT_CONTROLLER:
case AlexaInterfaceType::TOGGLE_CONTROLLER:
return "3";
case AlexaInterfaceType::LAUNCHER:
return "1.1";
case AlexaInterfaceType::ENDPOINT_HEALTH:
return "3.3";
default:
return "UNKNOWN";
}
}
static std::vector<String> getProps(AlexaInterfaceType type)
{
switch (type)
{
case AlexaInterfaceType::AUTOMATION_MANAGEMENT:
return {"automationStatuses"};
case AlexaInterfaceType::BRIGHTNESS_CONTROLLER:
return {"brightness"};
case AlexaInterfaceType::CHANNEL_CONTROLLER:
return {"channel"};
case AlexaInterfaceType::COLOR_CONTROLLER:
return {"color"};
case AlexaInterfaceType::COLOR_TEMPERATURE_CONTROLLER:
return {"colorTemperatureInKelvin"};
case AlexaInterfaceType::CONTACT_SENSOR:
return {"detectionState"};
case AlexaInterfaceType::POWER_CONTROLLER:
return {"powerState"};
case AlexaInterfaceType::ENDPOINT_HEALTH:
return {"connectivity"};
case AlexaInterfaceType::TOGGLE_CONTROLLER:
return {"toggleState"};
case AlexaInterfaceType::APPLICATION_STATE_REPORTER:
case AlexaInterfaceType::AUDIO_PLAY_QUEUE:
case AlexaInterfaceType::AUTHORIZATION_CONTROLLER:
case AlexaInterfaceType::AUTOMOTIVE_VEHICLE_DATA:
case AlexaInterfaceType::CAMERA_LIVE_VIEW_CONTROLLER:
case AlexaInterfaceType::CAMERA_STREAM_CONTROLLER:
case AlexaInterfaceType::COMMISSIONABLE:
case AlexaInterfaceType::CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER:
case AlexaInterfaceType::COOKING:
case AlexaInterfaceType::DATA_CONTROLLER:
case AlexaInterfaceType::DEVICE_USAGE_ESTIMATION:
case AlexaInterfaceType::DEVICE_USAGE_METER:
case AlexaInterfaceType::DOORBELL_EVENT_SOURCE:
case AlexaInterfaceType::EQUALIZER_CONTROLLER:
case AlexaInterfaceType::INPUT_CONTROLLER:
case AlexaInterfaceType::INVENTORY_LEVEL_SENSOR:
case AlexaInterfaceType::INVENTORY_LEVEL_USAGE_SENSOR:
case AlexaInterfaceType::INVENTORY_USAGE_SENSOR:
case AlexaInterfaceType::KEYPAD_CONTROLLER:
case AlexaInterfaceType::LAUNCHER:
case AlexaInterfaceType::LOCK_CONTROLLER:
case AlexaInterfaceType::MEDIA_PLAYBACK:
case AlexaInterfaceType::MEDIA_SEARCH:
case AlexaInterfaceType::MODE_CONTROLLER:
case AlexaInterfaceType::MOTION_SENSOR:
case AlexaInterfaceType::PERCENTAGE_CONTROLLER:
case AlexaInterfaceType::PLAYBACK_CONTROLLER:
case AlexaInterfaceType::PLAYBACK_STATE_REPORTER:
case AlexaInterfaceType::PROACTIVE_NOTIFICATION_SOURCE:
case AlexaInterfaceType::RANGE_CONTROLLER:
case AlexaInterfaceType::RECORD_CONTROLLER:
case AlexaInterfaceType::REMOTE_VIDEO_PLAYER:
case AlexaInterfaceType::RTC_SESSION_CONTROLLER:
case AlexaInterfaceType::SCENE_CONTROLLER:
case AlexaInterfaceType::SECURITY_PANEL_CONTROLLER:
case AlexaInterfaceType::SEEK_CONTROLLER:
case AlexaInterfaceType::SIMPLE_EVENT_SOURCE:
case AlexaInterfaceType::SMART_VISION_OBJECT_DETECTION_SENSOR:
case AlexaInterfaceType::SMART_VISION_SNAPSHOT_PROVIDER:
case AlexaInterfaceType::SPEAKER:
case AlexaInterfaceType::STEP_SPEAKER:
case AlexaInterfaceType::TEMPERATURE_SENSOR:
case AlexaInterfaceType::THERMOSTAT_CONTROLLER:
case AlexaInterfaceType::THERMOSTAT_CONTROLLER_CONFIGURATION:
case AlexaInterfaceType::THERMOSTAT_CONTROLLER_HVAC_COMPONENTS:
case AlexaInterfaceType::THERMOSTAT_CONTROLLER_SCHEDULE:
case AlexaInterfaceType::TIME_HOLD_CONTROLLER:
case AlexaInterfaceType::UI_CONTROLLER:
case AlexaInterfaceType::USER_PREFERENCE:
case AlexaInterfaceType::VIDEO_RECORDER:
case AlexaInterfaceType::WAKE_ON_LAN_CONTROLLER:
return {};
default:
return {"UNKNOWN"};
}
}
};
enum class AlexaActions {
Open,
Close,
Raise,
Lower,
SetEcoOn,
SetEcoOff
};
class AlexaActionsUtils
{
public:
// Helper function to convert AlexaActions enum to string
static String toString(AlexaActions action) {
switch (action) {
case AlexaActions::Open: return "Alexa.Actions.Open";
case AlexaActions::Close: return "Alexa.Actions.Close";
case AlexaActions::Raise: return "Alexa.Actions.Raise";
case AlexaActions::Lower: return "Alexa.Actions.Lower";
case AlexaActions::SetEcoOn: return "Alexa.Actions.SetEcoOn";
case AlexaActions::SetEcoOff: return "Alexa.Actions.SetEcoOff";
default: return "Unknown";
}
}
};
class FriendlyName {
public:
FriendlyName(const std::string& t, const std::string& l)
: text(t), locale(l) {}
std::string text;
std::string locale;
};
// Updated ActionMapping class definition
class ActionMapping {
public:
String type = "ActionsToDirective"; // Type of action mapping (always "ActionsToDirective")
std::vector<AlexaActions> actions; // Array of AlexaActions
struct Directive {
String name; // Name of the directive
String payload; // Payload for the directive (can be empty)
} directive; // Directive object containing name and payload
// Constructor
ActionMapping(std::vector<AlexaActions> actions, String directiveName, String directivePayload="{}")
: actions(actions) {
directive.name = directiveName;
directive.payload = directivePayload;
}
// Get the JSON representation of ActionMapping
JsonDocument getJSON() {
JsonDocument doc;
doc["@type"] = type;
// Convert actions array to strings
JsonArray actionArray = doc["actions"].to<JsonArray>();
for (const auto& action : actions) {
actionArray.add(AlexaActionsUtils::toString(action));
}
JsonObject directiveObj = doc["directive"].to<JsonObject>();
directiveObj["name"] = directive.name;
if (directive.payload) {
directiveObj["payload"] = directive.payload;
}
return doc;
}
};
// AlexaInterface class definition
class AlexaInterface
{
private:
AlexaInterfaceType type;
bool retrievable;
bool proactivelyReported;
std::vector<ActionMapping> actionMappings; // Array of ActionMappings
std::vector<FriendlyName> friendlyNames; // Array of FriendlyNames
String instance;
bool nameSet;
public:
// Constructor
AlexaInterface(AlexaInterfaceType type, bool retrievable = true, bool proactivelyReported = false)
: type(type), retrievable(retrievable), proactivelyReported(proactivelyReported) {}
// Getters
AlexaInterfaceType getType() const { return type; }
String getTypeString() const { return AlexaInterfaceUtils::toString(type); }
String getVersion() const { return AlexaInterfaceUtils::getVersion(type); }
std::vector<String> getProps() const { return AlexaInterfaceUtils::getProps(type); }
void setInstance(const char* name){
instance=name;
nameSet=true;
}
void addFriendlyName(const char* name,const char* locale){
friendlyNames.emplace_back(name,locale);
}
bool isRetrievable() const { return retrievable; }
bool isProactivelyReported() const { return proactivelyReported; }
// Setters
void setRetrievable(bool value) { retrievable = value; }
void setProactivelyReported(bool value) { proactivelyReported = value; }
void addActionMapping(const ActionMapping& actionMapping) {
actionMappings.push_back(actionMapping);
}
JsonDocument getJSON()
{
JsonDocument doc;
String ret;
// Set base properties
doc["interface"] = getTypeString();
doc["version"] = getVersion();
doc["type"] = "AlexaInterface";
// Add properties
JsonObject properties = doc["properties"].to<JsonObject>();
properties["retrievable"] = retrievable;
properties["proactivelyReported"] = proactivelyReported;
std::vector<String> props = getProps();
JsonArray supported = properties["supported"].to<JsonArray>();
int index = 0;
for (String prop : props)
{
supported[index]["name"] = prop;
index++;
}
JsonObject semantics = doc["semantics"].to<JsonObject>();
// Add action mappings
JsonArray actionMappingArray = semantics["actionMappings"].to<JsonArray>();
for (ActionMapping& actionMapping : actionMappings) {
actionMappingArray.add(actionMapping.getJSON().as<JsonObject>());
}
if(nameSet){
doc["instance"]=instance;
}
// Add capabilityResources if friendlyNames is not empty
if (!friendlyNames.empty()) {
JsonObject capabilityResources = doc.createNestedObject("capabilityResources");
JsonArray friendlyNamesArray = capabilityResources.createNestedArray("friendlyNames");
for (const FriendlyName& fn : friendlyNames) {
JsonObject friendlyNameObj = friendlyNamesArray.createNestedObject();
friendlyNameObj["@type"] = "text";
JsonObject valueObj = friendlyNameObj.createNestedObject("value");
valueObj["text"] = fn.text;
valueObj["locale"] = fn.locale;
}
}
doc.shrinkToFit();
return doc;
}
};
#endif // ALEXA_INTERFACE_H

View file

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

143
src/AlexaStatusMessage.h Normal file
View file

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

220
src/AlexaUtils.cpp Normal file
View file

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

83
src/AlexaUtils.h Normal file
View file

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