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
181
src/AlexaCapability.h
Normal file
181
src/AlexaCapability.h
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// One capability of one device: a row of AlexaInterfaces and what this device adds to it. For most interfaces that
|
||||
// is nothing. A generic controller (RangeController, ModeController, ToggleController) has an instance and the
|
||||
// names Alexa calls it by, a configuration, and the words Alexa maps to its directives and states.
|
||||
// alexa-discovery-objects.html, generic-controllers.html under
|
||||
// https://developer.amazon.com/en-US/docs/alexa/device-apis/
|
||||
#ifndef ALEXA_CAPABILITY_H
|
||||
#define ALEXA_CAPABILITY_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <initializer_list>
|
||||
#include "AlexaInterfaces.h"
|
||||
#include "AlexaLimits.h"
|
||||
|
||||
// What a user says: "open", "close", "raise", "lower" (alexa-discovery-objects.html#action-mapping)
|
||||
enum class AlexaAction : uint8_t
|
||||
{
|
||||
Open,
|
||||
Close,
|
||||
Raise,
|
||||
Lower,
|
||||
SetEcoOn,
|
||||
SetEcoOff
|
||||
};
|
||||
|
||||
// What Alexa says about the device: "the blind is open" (alexa-discovery-objects.html#state-mapping)
|
||||
enum class AlexaState : uint8_t
|
||||
{
|
||||
Open,
|
||||
Closed
|
||||
};
|
||||
|
||||
// "Alexa.Actions.Open", in program memory
|
||||
PGM_P alexaActionName(AlexaAction action);
|
||||
|
||||
// "Alexa.States.Open", in program memory
|
||||
PGM_P alexaStateName(AlexaState state);
|
||||
|
||||
// The directive Alexa sends for one or more actions. The name of the directive and its payload, the JSON text of
|
||||
// an object, are kept as pointers and not copied: pass literals. Actions are announced in the order of AlexaAction.
|
||||
class ActionMapping
|
||||
{
|
||||
public:
|
||||
ActionMapping() {}
|
||||
|
||||
ActionMapping(std::initializer_list<AlexaAction> actions, const char *directiveName,
|
||||
const char *directivePayload = nullptr)
|
||||
: directiveName(directiveName), directivePayload(directivePayload)
|
||||
{
|
||||
for (AlexaAction action : actions)
|
||||
{
|
||||
this->actions |= static_cast<uint8_t>(1u << static_cast<uint8_t>(action));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t actions = 0; // one bit per AlexaAction
|
||||
const char *directiveName = nullptr;
|
||||
const char *directivePayload = nullptr; // nullptr or "" for a directive without payload
|
||||
};
|
||||
|
||||
// Writes the "configuration" object of a capability when the device is announced; context is what the sketch
|
||||
// passed to setConfiguration()
|
||||
typedef void (*AlexaConfigurationFiller)(JsonObject configuration, void *context);
|
||||
|
||||
// "Alexa.RangeController Blind.Lift" for a log line. The namespace is in program memory, where the %s of the log
|
||||
// cannot read it.
|
||||
struct AlexaCapabilityName
|
||||
{
|
||||
AlexaCapabilityName(const AlexaInterfaceDesc &row, const char *instance);
|
||||
|
||||
char text[72];
|
||||
};
|
||||
|
||||
// 116 bytes on the heap of an ESP8266, whatever the capability holds. Every setter returns the capability:
|
||||
// device->addCapability(AlexaInterfaces::RangeController, "Blind.Lift")
|
||||
// ->addFriendlyName("Lift", "en-US").setConfiguration(fillLift).addStateMapping({AlexaState::Closed}, 0.0f);
|
||||
// What a capability refuses is printed at ERROR with what to change, and leaves the capability as it was.
|
||||
class AlexaCapability
|
||||
{
|
||||
public:
|
||||
explicit AlexaCapability(const AlexaInterfaceDesc &row, const char *instance = nullptr);
|
||||
~AlexaCapability();
|
||||
|
||||
// It owns copies of its instance and its names
|
||||
AlexaCapability(const AlexaCapability &) = delete;
|
||||
AlexaCapability &operator=(const AlexaCapability &) = delete;
|
||||
|
||||
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)); }
|
||||
|
||||
// "" when the capability has none
|
||||
const char *getInstance() const { return instance != nullptr ? instance : ""; }
|
||||
|
||||
bool isRetrievable() const { return retrievable; }
|
||||
bool isProactivelyReported() const { return proactivelyReported; }
|
||||
bool isNonControllable() const { return nonControllable; }
|
||||
|
||||
// The instance tells the capabilities of one interface on a device apart ("Blind.Lift", "Blind.Tilt"). It is
|
||||
// copied. addCapability(row, instance) sets it; this is for a capability that was added by its type.
|
||||
AlexaCapability &setInstance(const char *instance);
|
||||
|
||||
// A name of the capability in words. The text is copied, the locale ("en-US") is not: pass a literal.
|
||||
AlexaCapability &addFriendlyName(const char *text, const char *locale);
|
||||
|
||||
// A name from the catalog of Alexa, which Alexa translates: PSTR("Alexa.Setting.Opening"). Not copied.
|
||||
AlexaCapability &addFriendlyAsset(PGM_P assetId);
|
||||
|
||||
AlexaCapability &setRetrievable(bool value);
|
||||
AlexaCapability &setProactivelyReported(bool value);
|
||||
|
||||
// true: Alexa reports the state and sends no directive that changes it
|
||||
AlexaCapability &setNonControllable(bool value);
|
||||
|
||||
AlexaCapability &setConfiguration(AlexaConfigurationFiller fill, void *context = nullptr);
|
||||
|
||||
// Only for an interface that takes semantics: RangeController, ModeController, ToggleController
|
||||
AlexaCapability &addActionMapping(const ActionMapping &mapping);
|
||||
|
||||
// The states a value stands for: a number or a range of a RangeController, the text of a mode or "ON"/"OFF"
|
||||
// of a ToggleController. A text is not copied: pass a literal.
|
||||
AlexaCapability &addStateMapping(std::initializer_list<AlexaState> states, float value);
|
||||
AlexaCapability &addStateMapping(std::initializer_list<AlexaState> states, float minimum, float maximum);
|
||||
AlexaCapability &addStateMapping(std::initializer_list<AlexaState> states, const char *value);
|
||||
|
||||
// Whether a directive with this namespace and instance is for this capability. The instance counts only
|
||||
// for an interface that has instances.
|
||||
bool matches(const char *ns, const char *directiveInstance) const;
|
||||
|
||||
// Adds the discovery object of the capability to the capabilities of its endpoint. A capability that Alexa
|
||||
// would reject the endpoint for, a generic controller without an instance or without a name, adds nothing,
|
||||
// prints what is missing and returns false.
|
||||
bool toJson(JsonArray capabilities, const char *endpointId) const;
|
||||
|
||||
private:
|
||||
struct Name
|
||||
{
|
||||
const char *value; // the text, a copy on the heap, or the asset id in program memory
|
||||
const char *locale; // nullptr for an asset
|
||||
};
|
||||
|
||||
enum StateMappingKind : uint8_t
|
||||
{
|
||||
STATES_TO_VALUE,
|
||||
STATES_TO_RANGE,
|
||||
STATES_TO_TEXT
|
||||
};
|
||||
|
||||
struct StateMapping
|
||||
{
|
||||
uint8_t states; // one bit per AlexaState
|
||||
uint8_t kind; // StateMappingKind
|
||||
union
|
||||
{
|
||||
float value; // the value, or the lowest of a range
|
||||
const char *text;
|
||||
};
|
||||
float maximum;
|
||||
};
|
||||
|
||||
StateMapping *newStateMapping(std::initializer_list<AlexaState> states, StateMappingKind kind);
|
||||
void writeSemantics(JsonObject capability) const;
|
||||
void writeNames(JsonObject capability) const;
|
||||
|
||||
const AlexaInterfaceDesc *row; // in program memory, never nullptr
|
||||
char *instance = nullptr; // a copy on the heap
|
||||
AlexaConfigurationFiller fillConfiguration = nullptr;
|
||||
void *configurationContext = nullptr;
|
||||
Name names[ALEX2ESP_MAX_FRIENDLY_NAMES];
|
||||
ActionMapping actionMappings[ALEX2ESP_MAX_ACTION_MAPPINGS];
|
||||
StateMapping stateMappings[ALEX2ESP_MAX_STATE_MAPPINGS];
|
||||
uint8_t nameCount = 0;
|
||||
uint8_t actionCount = 0;
|
||||
uint8_t stateCount = 0;
|
||||
bool retrievable : 1;
|
||||
bool proactivelyReported : 1;
|
||||
bool nonControllable : 1;
|
||||
};
|
||||
|
||||
#endif // ALEXA_CAPABILITY_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue