src/dispatcher.ts routes a directive to capability.on(name | "*"), device.onDirective() or device.onReportState(), with the payload checked by the descriptor of the interface. The DirectiveContext answers with respond/report/defer/error; respond() and report() start from device.state(). A handler or 1.x listener that throws or rejects is answered with INTERNAL_ERROR (an AlexaError with itself) and reported through "error", never as an unhandled rejection. Undeclared interface, unknown directive, AdjustMode on an unordered mode and a missing handler get INVALID_DIRECTIVE, a bad payload INVALID_VALUE; a device with an "Event" or "ReportState" listener keeps the answer to itself. Every answer passes the dispatcher: the first one per correlationToken is published, a second is refused. No answer within answerWithinMs (6500, 0 = off, unref'd timer) sends INTERNAL_ERROR and emits "unanswered". A messageId that arrived in the last 60 s is dropped. New: "unknownEndpoint", options answerUnknownEndpoints, publisher, timers, and bridge.receive() to run a bridge without a broker. 168 tests pass (18 new). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
111 lines
5.9 KiB
TypeScript
111 lines
5.9 KiB
TypeScript
import type { IClientOptions } from "mqtt";
|
|
import Device from "./device/Device.js";
|
|
import type { EndpointDefinition } from "./device/Device.js";
|
|
import { EventEmitter } from "events";
|
|
import { DisplayCategory } from "./compat/enums.js";
|
|
import type { Timers } from "./dispatcher.js";
|
|
import type { Publisher } from "./transport.js";
|
|
/** Optional settings for the bridge (1.5.1). Everything has the 1.4.0 behaviour as its default. */
|
|
export interface Alex2MQTTOptions {
|
|
/** The broker URL. Default: the public Alex2MQTT broker, mqtt://Alex2MQTT.stormysdream.club:1883. */
|
|
host?: string;
|
|
/** Extra mqtt.js client options (reconnectPeriod, connectTimeout, clientId, ...). Merged over the defaults. */
|
|
mqtt?: IClientOptions;
|
|
/** Where log lines go. Default: console (only when debugLogging is on). */
|
|
log?: (message: string, detail?: unknown) => void;
|
|
/**
|
|
* false: discovery does not list the Alexa interface on the endpoints. Default: every endpoint ends with
|
|
* { type: "AlexaInterface", interface: "Alexa", version: "3" }, which Amazon requires and 1.x left out.
|
|
*/
|
|
alexaInterface?: boolean;
|
|
/**
|
|
* How long a directive may wait for its answer, in milliseconds. After that the bridge answers INTERNAL_ERROR and
|
|
* emits "unanswered". Default: 6500, before Alex2MQTT gives up at 7000. 0: the bridge never answers for a handler.
|
|
*/
|
|
answerWithinMs?: number;
|
|
/**
|
|
* true: a directive for an endpoint the bridge does not have is answered with NO_SUCH_ENDPOINT. Default: it is
|
|
* left alone, because the endpoint may belong to another bridge on the same root topic.
|
|
*/
|
|
answerUnknownEndpoints?: boolean;
|
|
/** Publish here and not to the broker: a MemoryPublisher and receive() run a bridge that never connects. */
|
|
publisher?: Publisher;
|
|
/** The timer of answerWithinMs, for a test that does not wait. Default: setTimeout, unref'd. */
|
|
timers?: Timers;
|
|
}
|
|
export declare const DEFAULT_HOST = "mqtt://Alex2MQTT.stormysdream.club:1883";
|
|
/**
|
|
* The Alexa-to-MQTT bridge: one broker connection for a user's root topic, a set of registered devices, discovery and
|
|
* directive dispatch.
|
|
*
|
|
* Events (all optional to listen to - since 1.5.1 a broker outage never throws out of the library):
|
|
* "connect" connected (or reconnected) and subscribed to <root>/discover and <root>/+/alexaDirective
|
|
* "offline" the connection dropped; mqtt.js reconnects on its own (reconnectPeriod, default 1 s)
|
|
* "reconnect" a reconnect attempt starts
|
|
* "close" the connection closed
|
|
* "error" (err) a connection or publish error. Emitted ONLY when a listener is attached (1.4.0 emitted it
|
|
* unconditionally, and Node kills a process that has an unhandled "error" event)
|
|
* "discover" (n) a discovery request was answered with n devices
|
|
* "directive" (info) a directive was dispatched to a device: { endpointId, namespace, name }
|
|
* "unknownEndpoint" (info) a directive came for an endpoint that is not registered: { endpointId, namespace, name }
|
|
* "unanswered" (info) nothing answered a directive in time and the bridge answered INTERNAL_ERROR:
|
|
* { endpointId, namespace, name, correlationToken }
|
|
*/
|
|
declare class Alex2MQTT extends EventEmitter {
|
|
private username;
|
|
private password;
|
|
private rootTopic;
|
|
private debugLogging;
|
|
private client;
|
|
private readonly publisher;
|
|
private readonly dispatcher;
|
|
private devices;
|
|
private MqttHost;
|
|
private options;
|
|
private logged;
|
|
/** true while the broker connection is up. */
|
|
connected: boolean;
|
|
/** ISO time of the last discovery request answered, null before the first. */
|
|
lastDiscoveryAt: string | null;
|
|
constructor(username: string, password: string, rootTopic: string, debugLogging?: boolean, options?: Alex2MQTTOptions);
|
|
private log;
|
|
/** Emit "error" only when somebody listens: an unhandled "error" event would crash the host process. */
|
|
private fail;
|
|
connect(): void;
|
|
/**
|
|
* What the bridge does with a message of the broker: it answers a discovery request, and it hands a directive to
|
|
* the handlers of its device. Resolves when they are done and never rejects. With the publisher option a test
|
|
* calls it in place of the broker:
|
|
*
|
|
* await bridge.receive("root/lamp-1/alexaDirective", JSON.stringify(directive));
|
|
*/
|
|
receive(topic: string, payload: Buffer | string): Promise<void>;
|
|
private answerDiscovery;
|
|
private describeDevices;
|
|
/** Close the broker connection (resolves once closed). The devices stay registered; connect() again reuses them. */
|
|
disconnect(): Promise<void>;
|
|
/**
|
|
* Declare a device:
|
|
*
|
|
* const blinds = bridge.addDevice({ endpointId: "bedroom-blinds", name: "Bedroom Blinds",
|
|
* categories: ["INTERIOR_BLIND"], manufacturerName: "Acme", description: "Roller blind by Acme" });
|
|
*
|
|
* Throws a DeclarationError when Alexa would reject the endpoint (an endpointId with a slash, a name with
|
|
* punctuation) or when the endpointId is registered already. Discovery lists Alexa.EndpointHealth for the device
|
|
* unless endpointHealth is false. A device can be declared before connect(): what it sends before the bridge has
|
|
* a connection is not published, and the "error" event says so.
|
|
*/
|
|
addDevice(definition: EndpointDefinition): Device;
|
|
registerDevice(name: string, endpointId: string, displayCategory?: DisplayCategory | DisplayCategory[] | null): Device;
|
|
private register;
|
|
/** Forget a device (its listeners with it). Returns false when there was none. */
|
|
unregisterDevice(endpointId: string): boolean;
|
|
/** Forget every device. */
|
|
clearDevices(): void;
|
|
private release;
|
|
getDevices(): Device[];
|
|
getDevice(endpointId: string): Device | undefined;
|
|
getRootTopic(): string;
|
|
getHost(): string;
|
|
}
|
|
export default Alex2MQTT;
|