AlexaDevice: capability array, linked list, a directive handler with the device in hand
onDirective(handler) registers void handler(AlexaDirective&): the directive comes with its device, the capability it is for, namespace, name, instance, correlationToken and payload, and response() and stateReport() build the answer, so one function serves several devices. registerEvent() is kept; the five examples compile unchanged and announce the same discovery objects. Devices are a linked list on the bridge and a device holds ALEX2ESP_MAX_CAPABILITIES (8) capability pointers in place of two std::deque; one capability more is refused with an error. A device without a handler, and a handler that sends nothing for a capability the device lacks, are answered with the ErrorResponse INVALID_DIRECTIVE instead of a timeout. A directive whose endpointId is not a device of the sketch is ignored at DEBUG. basicLight on a D1 mini: static RAM 30,672 -> 30,600 B, flash 332,541 -> 333,437 B, 0 warnings in the five examples; 88 host tests (11 new, test/test_dispatch). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
7afc4346e8
commit
d3e7ee7f2a
10 changed files with 767 additions and 67 deletions
|
|
@ -7,7 +7,7 @@
|
|||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include "AlexaLimits.h"
|
||||
#include "AlexaStatusMessage.h"
|
||||
#include "AlexaTransport.h"
|
||||
#include "AlexaVersion.h"
|
||||
|
|
@ -22,20 +22,53 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class AlexaDevice;
|
||||
|
||||
// A directive as the handler of its device gets it. The texts belong to the directive: they are valid until the
|
||||
// handler returns, so a handler that answers later keeps a copy of the correlationToken.
|
||||
struct AlexaDirective {
|
||||
AlexaDevice* device;
|
||||
AlexaCapability* capability; // nullptr for ReportState, and when the device has no capability for the directive
|
||||
AlexaInterfaceType type; // of the capability, UNKNOWN without one
|
||||
const char* ns; // "Alexa.PowerController"
|
||||
const char* name; // "TurnOn"
|
||||
const char* instance; // "Blind.Lift", "" for an interface without instances
|
||||
const char* correlationToken;
|
||||
JsonObjectConst payload;
|
||||
|
||||
bool is(const char* directiveName) const { return strcmp(name, directiveName) == 0; }
|
||||
bool isReportState() const { return strcmp(ns, "Alexa") == 0 && is("ReportState"); }
|
||||
|
||||
class AlexaDevice {
|
||||
// The answer to a directive that changed something, and the answer to ReportState: add the properties of the
|
||||
// device and send()
|
||||
AlexaStatusMessage response() const;
|
||||
AlexaStatusMessage stateReport() const;
|
||||
};
|
||||
|
||||
// One function can serve several devices: the directive says which one it is for
|
||||
typedef void (*AlexaDirectiveHandler)(AlexaDirective& directive);
|
||||
|
||||
// A device is the transport of its own messages: it passes them on to the bridge and so knows whether a handler
|
||||
// answered the directive it was given.
|
||||
class AlexaDevice final : private AlexaTransport {
|
||||
public:
|
||||
// Devices are created by Alex2ESP::getDevice(), which passes the bridge as the transport that publishes the
|
||||
// device's reports. A device built without one can describe itself, but its reports cannot be sent.
|
||||
AlexaDevice(const String& name, const String& rootTopic, const String& endpointId, AlexaTransport* transport = nullptr);
|
||||
~AlexaDevice();
|
||||
|
||||
// It owns its capabilities, and the pointers to it that the sketch holds have to stay valid
|
||||
AlexaDevice(const AlexaDevice&) = delete;
|
||||
AlexaDevice& operator=(const AlexaDevice&) = delete;
|
||||
|
||||
void setName(const String& name);
|
||||
String getName() const;
|
||||
|
||||
String getEndpointId() const;
|
||||
|
||||
// The device that was created after this one, nullptr for the last
|
||||
AlexaDevice* nextDevice() const { return next; }
|
||||
|
||||
// Compares the endpoint id with `length` characters at `id` (not NUL-terminated: a part of an MQTT topic)
|
||||
bool hasEndpointId(const char* id, size_t length) const;
|
||||
|
||||
|
|
@ -61,8 +94,8 @@ public:
|
|||
// Returns the capability of this device for the interface, creating it on first use. An interface that a
|
||||
// device may have several of (RangeController, ModeController, ToggleController) takes the instance that tells
|
||||
// them apart, "Blind.Lift"; without one, and with an instance for any other interface, nothing is added, the
|
||||
// reason is printed and nullptr returned. 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.
|
||||
// reason is printed and nullptr returned. A device holds ALEX2ESP_MAX_CAPABILITIES capabilities; one more is
|
||||
// refused in the same way. The pointer stays valid for the lifetime of the device.
|
||||
AlexaCapability* addCapability(const AlexaInterfaceDesc& row, const char* instance = nullptr);
|
||||
|
||||
// The same by the type of the interface, as 1.x sketches write it: they call setInstance() on what they get,
|
||||
|
|
@ -78,17 +111,38 @@ public:
|
|||
// UNKNOWN when the device has none
|
||||
AlexaInterfaceType getInterfaceType(const char* interfaceName) const;
|
||||
|
||||
// The capability a directive with this namespace and instance is for, nullptr when the device has none
|
||||
AlexaCapability* findCapability(const char* ns, const char* instance) const;
|
||||
|
||||
// The handler of every directive for this device, ReportState included. With one, the "ReportState" and
|
||||
// "Event" handlers of registerEvent() are not called.
|
||||
void onDirective(AlexaDirectiveHandler handler);
|
||||
|
||||
// The handlers of 1.x: "ReportState", "Event" for every other directive, and "DirectiveReceived", which is
|
||||
// called before either
|
||||
void registerEvent(const char* eventName, void (*callback)(const JsonDocument&, const AlexaInterfaceType&));
|
||||
|
||||
// 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;
|
||||
// Returns false when no handler has this name. warnIfMissing=false keeps that quiet.
|
||||
bool triggerEvent(const char* eventName, const JsonDocument& directive, const AlexaInterfaceType& type, bool warnIfMissing = true) const;
|
||||
|
||||
// Hands a directive, {"header": ..., "endpoint": ..., "payload": ...}, to the handler of the device. The
|
||||
// device answers with the ErrorResponse INVALID_DIRECTIVE itself when it has no handler, and when the
|
||||
// directive is for a capability it does not have and the handler sent nothing. A handler that sends nothing
|
||||
// for a capability of the device leaves the directive unanswered; that is printed as an error.
|
||||
void handleDirective(const JsonDocument& message);
|
||||
|
||||
// A device without a transport gets none for its messages: send() says what is wrong
|
||||
AlexaStatusMessage buildStatusMessage(const String& correlationToken,const bool isResponse=false) {
|
||||
return AlexaStatusMessage(correlationToken,rootTopic,endpointId,isResponse,transport);
|
||||
return AlexaStatusMessage(correlationToken,rootTopic,endpointId,isResponse,transport != nullptr ? this : nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AlexaDeviceList;
|
||||
|
||||
// AlexaTransport, for the messages of this device: notes the answer and passes it on to the bridge
|
||||
AlexaSendResult publish(const char* topic, JsonDocument& doc) override;
|
||||
void timestamp(char* buffer, size_t size) override;
|
||||
|
||||
// The capability with this row and, when one is given, this instance; added when the device has none
|
||||
AlexaCapability* capabilityOf(const AlexaInterfaceDesc& row, const char* instance);
|
||||
AlexaCapability* refuseCapability(AlexaInterfaceType type);
|
||||
|
|
@ -97,6 +151,9 @@ private:
|
|||
String endpointId;
|
||||
String rootTopic;
|
||||
AlexaTransport* transport;
|
||||
AlexaDevice* next = nullptr; // in the list of the bridge
|
||||
AlexaDirectiveHandler directiveHandler = nullptr;
|
||||
bool answered = false; // a message has been sent since the handler of the directive was called
|
||||
const char* eventNames[MAX_EVENTS];
|
||||
void (*eventCallbacks[MAX_EVENTS])(const JsonDocument&, const AlexaInterfaceType&);
|
||||
|
||||
|
|
@ -107,8 +164,41 @@ private:
|
|||
String model = "Alexa2MQTT";
|
||||
const String softwareVersion = ALEX2ESP_VERSION;
|
||||
|
||||
std::deque<AlexaCapability> capabilities; // deque: pointers handed out by addCapability stay valid
|
||||
AlexaCapability* capabilities[ALEX2ESP_MAX_CAPABILITIES]; // on the heap, owned
|
||||
uint8_t capabilityCount = 0;
|
||||
};
|
||||
|
||||
inline AlexaStatusMessage AlexaDirective::response() const {
|
||||
return device->buildStatusMessage(correlationToken, true);
|
||||
}
|
||||
|
||||
inline AlexaStatusMessage AlexaDirective::stateReport() const {
|
||||
return device->buildStatusMessage(correlationToken, false);
|
||||
}
|
||||
|
||||
// The devices of a bridge in the order they were created, as a linked list: a device is on the heap from the
|
||||
// moment the sketch asks for it, and a sketch without devices pays for none.
|
||||
class AlexaDeviceList {
|
||||
public:
|
||||
AlexaDeviceList() {}
|
||||
~AlexaDeviceList();
|
||||
|
||||
AlexaDeviceList(const AlexaDeviceList&) = delete;
|
||||
AlexaDeviceList& operator=(const AlexaDeviceList&) = delete;
|
||||
|
||||
// Creates a device at the end of the list. nullptr when the heap has no room for it.
|
||||
AlexaDevice* add(const String& name, const String& rootTopic, const String& endpointId, AlexaTransport* transport);
|
||||
|
||||
// The device with this endpoint id, `length` characters at `id`; nullptr when the list has none
|
||||
AlexaDevice* find(const char* id, size_t length) const;
|
||||
|
||||
AlexaDevice* first() const { return head; }
|
||||
size_t size() const { return count; }
|
||||
|
||||
private:
|
||||
AlexaDevice* head = nullptr;
|
||||
AlexaDevice* tail = nullptr;
|
||||
size_t count = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue