A capability is a descriptor plus what the endpoint declares (device/Capability.ts), and its discovery object is
generated from the two. The new API is bridge.addDevice({ endpointId, name, categories, ... }) and
device.add(PowerController, options): both throw a DeclarationError that names the endpoint, the interface and
the instance (device/validate.ts), and leave the bridge and the device as they were. AlexaInterface is the same
Capability with the 1.x methods on it; it, ActionMapping and the enums moved to src/compat/, Device to src/device/.
What a 1.x caller can observe:
- every endpoint ends with { type: "AlexaInterface", interface: "Alexa", version: "3" } (alexa-interface.html);
new Alex2MQTT(..., { alexaInterface: false }) leaves it out
- the fields of a capability object come in the order of Amazon's examples; their content is unchanged
- addCapability() with a name that is not an interface throws (1.5.2 announced it with the version "UNKNOWN")
- ActionMapping takes the payload as an object; a JSON string is parsed (1.5.2 sent the string), any other throws
- what Alexa would reject in a 1.x declaration is not refused: device.check() lists it and the bridge logs each
line once, as "warning: ..." through the log hook, when it answers a discovery
- a device whose JSON cannot be built is left out of the answer and reported as an error event
- PowerController and EndpointHealth are the descriptors and keep ON/OFF and OK/UNREACHABLE; PowerState is new
Tests: six zoo devices declared the 1.x way give the JSON that Alexa accepted from 1.5.2 on 2026-09-28, plus the
Alexa capability. npm test: 85 pass (was 57) in 10-12 s, also on Node 18.20.8 and 20.20.2.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
100 lines
5.3 KiB
TypeScript
100 lines
5.3 KiB
TypeScript
// Compiled, never run (test/typings.test.js, npm run check): a CommonJS TypeScript consumer of the declarations in
|
|
// dist/types, resolved through the package's "exports". A "@ts-expect-error" line fails the compile when the
|
|
// declaration stops rejecting what follows it.
|
|
import {
|
|
ActionMapping, AlexaActions, AlexaInterfaceType, Assets, BrightnessController, DeclarationError, DisplayCategory, EndpointHealth,
|
|
PowerController, PowerState, TemperatureSensor, asset, registry, text,
|
|
} from "alex2node";
|
|
import type {
|
|
Alex2MQTT, Alex2MQTTOptions, AlexaInterface, AlexaStatusMessage, AssetId, Capability, CapabilityJson, ChangeCause, Device,
|
|
DisplayCategoryName, EndpointJson, Infer, InterfaceDescriptor, Label, Schema, SupportedMode, Temperature, UnitOfMeasure,
|
|
} from "alex2node";
|
|
|
|
declare const bridge: Alex2MQTT;
|
|
declare const message: AlexaStatusMessage;
|
|
declare const capability: AlexaInterface;
|
|
|
|
const options: Alex2MQTTOptions = { host: "mqtt://127.0.0.1:1883", mqtt: { reconnectPeriod: 1000 }, log: (line) => line.length };
|
|
const device: Device = bridge.registerDevice("Lamp", "lamp-1", [DisplayCategory.LIGHT, DisplayCategory.SWITCH]);
|
|
const added: AlexaInterface = device.addCapability(AlexaInterfaceType.POWER_CONTROLLER, { proactivelyReported: true });
|
|
|
|
// ActionMapping: the payload is optional, and an object
|
|
const close = new ActionMapping([AlexaActions.Close], "TurnOn");
|
|
capability.addActionMapping(close);
|
|
capability.addActionMapping(new ActionMapping([AlexaActions.Open], "SetRangeValue", { rangeValue: 100 }));
|
|
// @ts-expect-error a payload is an object or, as in 1.x, a string
|
|
capability.addActionMapping(new ActionMapping([AlexaActions.Open], "SetRangeValue", 100));
|
|
|
|
// addHealthProp: the enum or its string values, chainable
|
|
const chained: AlexaStatusMessage = message.addHealthProp(EndpointHealth.OK).addHealthProp("UNREACHABLE", 50);
|
|
// @ts-expect-error not a connectivity value
|
|
message.addHealthProp("ASLEEP");
|
|
// @ts-expect-error a power state is ON or OFF
|
|
message.addPowerControllerProp("ON ");
|
|
message.addPowerControllerProp(PowerController.ON);
|
|
// PowerController is the descriptor of the interface as well; the enum alone is PowerState
|
|
const on: PowerController = PowerState.ON;
|
|
const namespace: string = PowerController.namespace;
|
|
const reachable: EndpointHealth = EndpointHealth.OK;
|
|
|
|
// addSupportedModes: Mode objects, plain strings still accepted
|
|
const mode: SupportedMode = { value: "Fan.Auto", modeResources: { friendlyNames: [{ "@type": "text", value: { text: "Auto", locale: "en-US" } }] } };
|
|
capability.addSupportedModes([mode, "Fan.On"]);
|
|
|
|
const cause: ChangeCause = "PHYSICAL_INTERACTION";
|
|
const sent: Promise<string> = device.getChangeReport(cause).addPowerControllerProp(PowerController.OFF).send();
|
|
// @ts-expect-error not a cause Alexa knows
|
|
device.getChangeReport("BUTTON");
|
|
|
|
// bridge.addDevice() and device.add(): the options of the interface and the ones every capability has
|
|
const blinds: Device = bridge.addDevice({
|
|
endpointId: "bedroom-blinds",
|
|
name: "Bedroom Blinds",
|
|
categories: ["INTERIOR_BLIND", DisplayCategory.OTHER],
|
|
cookie: { room: "bedroom" },
|
|
endpointHealth: false,
|
|
});
|
|
// @ts-expect-error an endpoint has categories
|
|
bridge.addDevice({ endpointId: "lamp-2", name: "Lamp" });
|
|
// @ts-expect-error not a display category
|
|
bridge.addDevice({ endpointId: "lamp-2", name: "Lamp", categories: ["LAMP"] });
|
|
const power: Capability = blinds.add(PowerController, { proactivelyReported: true, verificationsRequired: ["TurnOff"] });
|
|
blinds.add(BrightnessController);
|
|
blinds.add(TemperatureSensor, { retrievable: true, nonControllable: true });
|
|
// @ts-expect-error not a directive of the interface
|
|
blinds.add(PowerController, { verificationsRequired: ["Toggle"] });
|
|
// @ts-expect-error not an option of the interface
|
|
blinds.add(BrightnessController, { range: { min: 0, max: 100 } });
|
|
const announced: CapabilityJson = power.toJSON();
|
|
const endpoint: EndpointJson = blinds.getJSON();
|
|
const problems: string[] = blinds.check();
|
|
|
|
// Friendly names
|
|
const names: Label[] = [asset("Alexa.Setting.Opening"), text("Position"), text("Posición", "es-MX")];
|
|
// @ts-expect-error not in the global Alexa catalog
|
|
asset("Alexa.Setting.Openning");
|
|
|
|
// The registry: a descriptor by its namespace or by the 1.x enum member
|
|
const described: InterfaceDescriptor = registry.get("Alexa.PowerController");
|
|
const version: string = registry.get(AlexaInterfaceType.ENDPOINT_HEALTH).version;
|
|
const refused: Error = new DeclarationError({ endpointId: "lamp-1", namespace: described.namespace }, "declared twice");
|
|
|
|
// The vocabularies are unions of what the pages list
|
|
const opening: AssetId = Assets[0];
|
|
const percent: UnitOfMeasure = "Alexa.Unit.Percent";
|
|
const vacuum: DisplayCategoryName = "VACUUM";
|
|
// @ts-expect-error not in the global Alexa catalog
|
|
const misspelt: AssetId = "Alexa.Setting.Openning";
|
|
// @ts-expect-error an asset, but not a unit of measure
|
|
const notAUnit: UnitOfMeasure = "Alexa.Setting.Opening";
|
|
|
|
// A schema types what it parses
|
|
declare const temperature: Schema<Temperature>;
|
|
const measured: Infer<typeof temperature> = { value: 20, scale: "CELSIUS" };
|
|
// @ts-expect-error not a temperature scale
|
|
const rankine: Infer<typeof temperature> = { value: 20, scale: "RANKINE" };
|
|
|
|
export {
|
|
options, added, chained, sent, on, namespace, reachable, announced, endpoint, problems, names, version, refused, opening,
|
|
percent, vacuum, misspelt, notAUnit, measured, rankine,
|
|
};
|