1.5.1: broker errors as events that never crash the host (error only when listened to; connect/offline/reconnect/close; connected flag), configurable broker host + mqtt options + log hook, Alexa.ChangeReport via Device.getChangeReport (<root>/changeReport), SceneController discovery + sendSceneResponse, addCapability options (proactivelyReported), unregisterDevice/clearDevices/getDevices/disconnect, discover/directive events, promise-returning quiet send(); tests on an in-process broker (aedes); readme

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
David 2026-09-22 19:29:38 +00:00
parent e96850bdd6
commit 717af632de
16 changed files with 2080 additions and 225 deletions

View file

@ -2,7 +2,8 @@ import { AlexaInterface, AlexaInterfaceType } from "./AlexaInterface";
import { DisplayCategory } from "./DisplayCategory";
import { EventEmitter } from "events";
import { MqttClient } from "mqtt";
import { AlexaStatusMessage } from "./AlexaStatusMessage";
import { AlexaStatusMessage, ChangeCause } from "./AlexaStatusMessage";
import { v4 as uuidv4 } from "uuid";
import { AlexaErrorResponse } from "./AlexaErrorResponse";
class Device extends EventEmitter {
@ -72,6 +73,35 @@ class Device extends EventEmitter {
isDeferred
);
}
/**
* A proactive Alexa.ChangeReport (1.5.1): add the changed properties (the default target), optionally
* .unchanged() then the others, and .send() - it goes to <root>/changeReport, which Alex2MQTT forwards to the Alexa
* event gateway with the user's token. Alexa only accepts it for capabilities registered with proactivelyReported.
*/
getChangeReport(cause: ChangeCause = "PHYSICAL_INTERACTION"): AlexaStatusMessage {
return new AlexaStatusMessage("", this.rootTopic, this.endpointId, this.mqttClient, false, false, cause);
}
/**
* Alexa.SceneController: answer an Activate / Deactivate directive with ActivationStarted / DeactivationStarted
* (1.5.1). Resolves with the topic published to.
*/
sendSceneResponse(correlationToken: string, activated: boolean, cause: ChangeCause = "VOICE_INTERACTION", sendAsync = false): Promise<string> {
const payload = {
context: {},
event: {
header: { namespace: "Alexa.SceneController", name: activated ? "ActivationStarted" : "DeactivationStarted", messageId: uuidv4(), correlationToken, payloadVersion: "3" },
endpoint: { endpointId: this.endpointId },
payload: { cause: { type: cause }, timestamp: new Date().toISOString() },
},
};
const topic = `${this.rootTopic}/${this.endpointId}/${sendAsync ? "deferredResponse" : "alexaResponce"}`;
return new Promise((resolve, reject) => {
this.mqttClient.publish(topic, JSON.stringify(payload), (err) => (err ? reject(err) : resolve(topic)));
});
}
getCapabilities(): AlexaInterface[] {
return this.capabilities.slice();
}
setManufacturerName(name: string): void {
this.manufacturerName = name;
}
@ -100,15 +130,9 @@ class Device extends EventEmitter {
return this.softwareVersion;
}
addCapability(type: AlexaInterfaceType): AlexaInterface {
// const existing = this.capabilities.find(
// (iface) => iface.getType() === type
// );
// if (existing) {
// return existing;
// }
const newCapability = new AlexaInterface(type);
/** Add a capability. 1.5.1: options.retrievable / proactivelyReported / instance (a change report needs proactivelyReported). */
addCapability(type: AlexaInterfaceType, options: { retrievable?: boolean; proactivelyReported?: boolean; instance?: string } = {}): AlexaInterface {
const newCapability = new AlexaInterface(type, options.retrievable ?? true, options.proactivelyReported ?? false, options.instance ?? "");
this.capabilities.push(newCapability);
// console.log(