AlexaCapability: instance, asset and text names, configuration, action and state mappings, nonControllable
AlexaCapability replaces the header-only AlexaInterface as what a device holds: 116 bytes with fixed places for
3 names, 4 action mappings and 2 state mappings, no std::vector or std::string. AlexaInterface and AlexaActions
stay as 1.x names; the five examples compile unchanged and announce the same bytes.
addCapability(row, instance) adds several capabilities of one generic controller. An instanced interface without
an instance or a name, an instance or semantics on an interface that takes none, and a full store are refused
with an ERROR line that says what to change.
PlaybackController and WakeOnLANController announce "properties": {} (AIF_EMPTY_PROPERTIES).
basicLight on a D1 mini: static RAM 30,788 -> 30,672 B, flash 330,509 -> 332,541 B; host tests 61 -> 77.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
8b11cc2394
commit
7afc4346e8
12 changed files with 1084 additions and 253 deletions
|
|
@ -1,202 +1,17 @@
|
|||
// The names of library 1.x for what 2.0 calls AlexaCapability and AlexaAction. They stay through 2.x, so that a
|
||||
// sketch written for 1.x compiles as it is; new sketches use the 2.0 names.
|
||||
#ifndef ALEXA_INTERFACE_H
|
||||
#define ALEXA_INTERFACE_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include "AlexaInterfaces.h"
|
||||
#include "AlexaLog.h"
|
||||
|
||||
enum class AlexaActions {
|
||||
Open,
|
||||
Close,
|
||||
Raise,
|
||||
Lower,
|
||||
SetEcoOn,
|
||||
SetEcoOff
|
||||
};
|
||||
#include "AlexaCapability.h"
|
||||
|
||||
typedef AlexaCapability AlexaInterface;
|
||||
typedef AlexaAction AlexaActions;
|
||||
|
||||
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; // JSON text of the directive's payload object, empty when there is none
|
||||
} 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() const {
|
||||
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.length() > 0) {
|
||||
// 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 {
|
||||
ALEX2ESP_LOGE("action mapping payload is not a JSON object, omitted: %s", directive.payload.c_str());
|
||||
}
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// A capability of a device: a row of AlexaInterfaces and what this device adds to it
|
||||
class AlexaInterface
|
||||
{
|
||||
private:
|
||||
const AlexaInterfaceDesc *row; // in program memory, never nullptr
|
||||
bool retrievable;
|
||||
bool proactivelyReported;
|
||||
std::vector<ActionMapping> actionMappings; // Array of ActionMappings
|
||||
std::vector<FriendlyName> friendlyNames; // Array of FriendlyNames
|
||||
|
||||
|
||||
String instance;
|
||||
bool nameSet = false;
|
||||
public:
|
||||
// Constructor
|
||||
AlexaInterface(const AlexaInterfaceDesc &row, bool retrievable = true, bool proactivelyReported = false)
|
||||
: row(&row), retrievable(retrievable), proactivelyReported(proactivelyReported) {}
|
||||
|
||||
// Getters
|
||||
const AlexaInterfaceDesc &getRow() const { return *row; }
|
||||
AlexaInterfaceType getType() const { return row->interfaceType(); }
|
||||
String getTypeString() const { return String(FPSTR(row->ns)); }
|
||||
String getVersion() const { return String(FPSTR(row->version)); }
|
||||
std::vector<String> getProps() const
|
||||
{
|
||||
std::vector<String> props;
|
||||
for (uint8_t i = 0; i < row->propertyCount(); i++)
|
||||
{
|
||||
props.push_back(String(FPSTR(row->property(i))));
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
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() const
|
||||
{
|
||||
JsonDocument doc;
|
||||
|
||||
// Set base properties
|
||||
doc["interface"] = FPSTR(row->ns);
|
||||
doc["version"] = FPSTR(row->version);
|
||||
doc["type"] = "AlexaInterface";
|
||||
|
||||
// Add properties, unless the interface has none to report (a scene, a doorbell)
|
||||
if (!row->has(AIF_NO_PROPERTIES)) {
|
||||
JsonObject properties = doc["properties"].to<JsonObject>();
|
||||
properties["retrievable"] = retrievable;
|
||||
properties["proactivelyReported"] = proactivelyReported;
|
||||
|
||||
JsonArray supported = properties["supported"].to<JsonArray>();
|
||||
for (uint8_t i = 0; i < row->propertyCount(); i++)
|
||||
{
|
||||
supported.add<JsonObject>()["name"] = FPSTR(row->property(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add action mappings - semantics only exist when there is something to map (as alex2node does)
|
||||
if (!actionMappings.empty()) {
|
||||
JsonObject semantics = doc["semantics"].to<JsonObject>();
|
||||
JsonArray actionMappingArray = semantics["actionMappings"].to<JsonArray>();
|
||||
for (const 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["capabilityResources"].to<JsonObject>();
|
||||
JsonArray friendlyNamesArray = capabilityResources["friendlyNames"].to<JsonArray>();
|
||||
|
||||
for (const FriendlyName& fn : friendlyNames) {
|
||||
JsonObject friendlyNameObj = friendlyNamesArray.add<JsonObject>();
|
||||
friendlyNameObj["@type"] = "text";
|
||||
|
||||
JsonObject valueObj = friendlyNameObj["value"].to<JsonObject>();
|
||||
valueObj["text"] = fn.text;
|
||||
valueObj["locale"] = fn.locale;
|
||||
}
|
||||
}
|
||||
|
||||
doc.shrinkToFit();
|
||||
return doc;
|
||||
}
|
||||
static String toString(AlexaActions action) { return String(FPSTR(alexaActionName(action))); }
|
||||
};
|
||||
|
||||
#endif // ALEXA_INTERFACE_H
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue