discovery: generate capability JSON from the registry
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>
This commit is contained in:
parent
aa0ffd64ea
commit
c492ef74d1
90 changed files with 5539 additions and 1760 deletions
50
test/fixtures/types.ts
vendored
50
test/fixtures/types.ts
vendored
|
|
@ -2,12 +2,12 @@
|
|||
// 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, DeclarationError, DisplayCategory, EndpointHealth, PowerController,
|
||||
registry,
|
||||
ActionMapping, AlexaActions, AlexaInterfaceType, Assets, BrightnessController, DeclarationError, DisplayCategory, EndpointHealth,
|
||||
PowerController, PowerState, TemperatureSensor, asset, registry, text,
|
||||
} from "alex2node";
|
||||
import type {
|
||||
Alex2MQTT, Alex2MQTTOptions, AlexaInterface, AlexaStatusMessage, AssetId, ChangeCause, Device, DisplayCategoryName, Infer,
|
||||
InterfaceDescriptor, Schema, SupportedMode, Temperature, UnitOfMeasure,
|
||||
Alex2MQTT, Alex2MQTTOptions, AlexaInterface, AlexaStatusMessage, AssetId, Capability, CapabilityJson, ChangeCause, Device,
|
||||
DisplayCategoryName, EndpointJson, Infer, InterfaceDescriptor, Label, Schema, SupportedMode, Temperature, UnitOfMeasure,
|
||||
} from "alex2node";
|
||||
|
||||
declare const bridge: Alex2MQTT;
|
||||
|
|
@ -18,9 +18,12 @@ const options: Alex2MQTTOptions = { host: "mqtt://127.0.0.1:1883", mqtt: { recon
|
|||
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
|
||||
// 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);
|
||||
|
|
@ -29,6 +32,10 @@ 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" } }] } };
|
||||
|
|
@ -39,6 +46,34 @@ const sent: Promise<string> = device.getChangeReport(cause).addPowerControllerPr
|
|||
// @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;
|
||||
|
|
@ -59,4 +94,7 @@ 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, version, refused, opening, percent, vacuum, misspelt, notAUnit, measured, rankine };
|
||||
export {
|
||||
options, added, chained, sent, on, namespace, reachable, announced, endpoint, problems, names, version, refused, opening,
|
||||
percent, vacuum, misspelt, notAUnit, measured, rankine,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue