Report helpers for every tier-1 property, asset ids, AlexaCapability::isValid()

AlexaStatusMessage gets a helper for color, percentage, power level, range, mode, lock state, contact,
motion, humidity and the thermostat (target, lower and upper setpoint, mode), and addProperty(row, place,
value) for a property without one. Each is a function of its own on one shared builder; a value outside of
the range of its property is reported as the nearest of the range with an error.
AlexaResources.h holds the 103 asset ids of resources-and-assets.html (AlexaAssets, AlexaUnits); AlexaState
has the seven states that were missing; addStateMapping() takes a number of any type.
A setter that refuses its argument makes isValid() false, and the capability is left out of discovery with
an error instead of being announced without what was refused.
basicLight: static RAM 30,520 B (was 30,540), flash 333,389 B (was 334,981); 130 host tests (was 112).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
David 2026-09-28 21:00:01 +00:00
parent 3215457f91
commit 77156ffbe5
10 changed files with 995 additions and 100 deletions

View file

@ -9,6 +9,7 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#include <initializer_list>
#include <type_traits>
#include "AlexaInterfaces.h"
#include "AlexaLimits.h"
@ -23,11 +24,19 @@ enum class AlexaAction : uint8_t
SetEcoOff
};
// What Alexa says about the device: "the blind is open" (alexa-discovery-objects.html#state-mapping)
// What Alexa says about the device: "the blind is open" (alexa-discovery-objects.html#state-mapping). EcoOn and
// EcoOff are for a ToggleController; Low, Empty, Full, Done and Stuck are what Alexa announces a device for.
enum class AlexaState : uint8_t
{
Open,
Closed
Closed,
EcoOn,
EcoOff,
Low,
Empty,
Full,
Done,
Stuck
};
// "Alexa.Actions.Open", in program memory
@ -74,7 +83,9 @@ struct AlexaCapabilityName
// 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.
// What a capability refuses is printed at ERROR with what to change, and leaves the capability as it was. The
// chain goes on after a call that was refused; isValid() is false from then on, and the device leaves the
// capability out of discovery, where Alexa would otherwise learn half of it.
class AlexaCapability
{
public:
@ -97,6 +108,9 @@ public:
bool isProactivelyReported() const { return proactivelyReported; }
bool isNonControllable() const { return nonControllable; }
// false when a setter has refused what it was given. The reason was printed when it happened.
bool isValid() const { return valid; }
// 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);
@ -104,7 +118,8 @@ public:
// 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.
// A name from the catalog of Alexa, which Alexa translates: AlexaAssets::Setting_Opening of AlexaResources.h,
// or PSTR("Alexa.Setting.Opening"). Not copied.
AlexaCapability &addFriendlyAsset(PGM_P assetId);
AlexaCapability &setRetrievable(bool value);
@ -124,13 +139,21 @@ public:
AlexaCapability &addStateMapping(std::initializer_list<AlexaState> states, float minimum, float maximum);
AlexaCapability &addStateMapping(std::initializer_list<AlexaState> states, const char *value);
// A number of another type, addStateMapping({AlexaState::Closed}, 0): without this a 0 is as much a text
// that is missing as it is a number, and the call does not compile
template <typename Number, typename = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
AlexaCapability &addStateMapping(std::initializer_list<AlexaState> states, Number value)
{
return addStateMapping(states, static_cast<float>(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.
// prints what is missing and returns false. So does one that is not valid.
bool toJson(JsonArray capabilities, const char *endpointId) const;
private:
@ -149,8 +172,8 @@ private:
struct StateMapping
{
uint8_t states; // one bit per AlexaState
uint8_t kind; // StateMappingKind
uint16_t states; // one bit per AlexaState
uint8_t kind; // StateMappingKind
union
{
float value; // the value, or the lowest of a range
@ -160,6 +183,7 @@ private:
};
StateMapping *newStateMapping(std::initializer_list<AlexaState> states, StateMappingKind kind);
AlexaCapability &refused();
void writeSemantics(JsonObject capability) const;
void writeNames(JsonObject capability) const;
@ -176,6 +200,7 @@ private:
bool retrievable : 1;
bool proactivelyReported : 1;
bool nonControllable : 1;
bool valid : 1;
};
#endif // ALEXA_CAPABILITY_H