AlexaErrors has a helper for each of the 73 types of the table on alexa-errorresponse.html, named after the type in camel case: 63 take the message only, 10 take the payload fields their page documents. A field the page requires (reason, currentDeviceMode, currentChargeState, resourceType) is a required argument, so leaving it out does not compile; from JavaScript the helper throws a TypeError that lists the values. validRange is optional, as the page says, and has both ends when given. temperatureOutOfRange and setpointsTooClose stay as shorter names. The helpers are compared with the 15 examples of the four error pages. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
189 lines
10 KiB
TypeScript
189 lines
10 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,
|
|
ModeController, PowerController, PowerState, RangeController, TemperatureSensor, ToggleController, asset, registry, semantics, text,
|
|
AlexaErrors, StateBuilder, messages,
|
|
} 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 generic controllers: an instance name, friendly names and the options of the interface
|
|
const lift = blinds.add(RangeController, {
|
|
instance: "Blind.Lift",
|
|
friendlyNames: names,
|
|
range: { min: 0, max: 100, precision: 1 },
|
|
unit: "Alexa.Unit.Percent",
|
|
presets: [{ value: 100, friendlyNames: [asset("Alexa.Value.Maximum")] }],
|
|
semantics: semantics()
|
|
.action("Close", "SetRangeValue", { rangeValue: 0 })
|
|
.action(["Open", AlexaActions.Raise], "SetRangeValue", { rangeValue: 100 })
|
|
.state("Closed", 0)
|
|
.stateRange("Open", 1, 100),
|
|
retrievable: true,
|
|
proactivelyReported: true,
|
|
});
|
|
blinds.add(ModeController, {
|
|
instance: "Blind.Position",
|
|
friendlyNames: [text("Position")],
|
|
supportedModes: [{ value: "Position.Up", friendlyNames: [asset("Alexa.Value.Open")] }, { value: "Position.Down", friendlyNames: [asset("Alexa.Value.Close")] }],
|
|
ordered: false,
|
|
});
|
|
blinds.add(ToggleController, { instance: "Blind.Tilt", friendlyNames: [text("Tilt")], nonControllable: true });
|
|
// @ts-expect-error a range controller has a range
|
|
blinds.add(RangeController, { instance: "Blind.Tilt", friendlyNames: [text("Tilt")] });
|
|
// @ts-expect-error a generic controller has an instance name and friendly names
|
|
blinds.add(ToggleController, { proactivelyReported: true });
|
|
// @ts-expect-error and so it cannot be declared without options
|
|
blinds.add(ToggleController);
|
|
// @ts-expect-error not a unit of measure
|
|
blinds.add(RangeController, { instance: "Blind.Tilt", friendlyNames: [text("Tilt")], range: { min: 0, max: 90, precision: 1 }, unit: "Alexa.Unit.Percentage" });
|
|
// @ts-expect-error not a phrase Alexa has
|
|
semantics().action("Shut", "TurnOff");
|
|
// What a directive carries and what a property holds are typed by the descriptor
|
|
const lower: Infer<typeof RangeController.directives.AdjustRangeValue.payload> = { rangeValueDelta: -10, rangeValueDeltaDefault: false };
|
|
const position: Schema<number> = lift.descriptor.properties.rangeValue.value;
|
|
// @ts-expect-error a mode is a string, or null when none is set
|
|
const current: Infer<typeof ModeController.properties.mode.value> = 3;
|
|
|
|
// 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, lower, position, current, version,
|
|
refused, opening, percent, vacuum, misspelt, notAUnit, measured, rankine,
|
|
};
|
|
|
|
// StateBuilder.set(): the properties of the interface and the type of each value
|
|
const state = new StateBuilder()
|
|
.set(PowerController, "powerState", "ON")
|
|
.set(TemperatureSensor, "temperature", { value: 70, scale: "FAHRENHEIT" })
|
|
.set(RangeController, "rangeValue", 3, { instance: "Blind.Lift", timeOfSample: new Date() })
|
|
.health("UNREACHABLE", "WIFI_AP_NOT_FOUND");
|
|
// @ts-expect-error a power state is ON or OFF
|
|
state.set(PowerController, "powerState", "on");
|
|
// @ts-expect-error not a property of the interface
|
|
state.set(PowerController, "power", "ON");
|
|
|
|
// The builders take what the builder of the state collected; a message is typed down to its payload
|
|
const report = messages.changeReport({ endpointId: "lamp-1", cause: "APP_INTERACTION", changed: state.change, context: state.context });
|
|
const changedNames: string[] = report.event.payload.change.properties.map((property) => property.name);
|
|
// @ts-expect-error a DeferredResponse has no context
|
|
messages.deferredResponse({ endpointId: "lamp-1", correlationToken: "ct" }).context;
|
|
// @ts-expect-error the range is two numbers
|
|
AlexaErrors.valueOutOfRange("too high", { minimumValue: 1 });
|
|
// @ts-expect-error a temperature has a scale
|
|
AlexaErrors.temperatureValueOutOfRange("too cold", { minimumValue: 15, maximumValue: 30 });
|
|
// @ts-expect-error the reason is required
|
|
AlexaErrors.endpointControlUnavailable("no connectivity package");
|
|
// @ts-expect-error the mode is one of the page's
|
|
AlexaErrors.notSupportedInCurrentMode("the lamp shows a color", "PARTY");
|
|
// @ts-expect-error the state of the battery is required
|
|
AlexaErrors.notSupportedWithCurrentBatteryChargeState("charging");
|
|
// @ts-expect-error the resource is required
|
|
AlexaErrors.insufficientResource("the tank is empty");
|
|
// @ts-expect-error a type without fields takes the message only
|
|
AlexaErrors.thermostatIsOff("the thermostat is off", { validRange: { minimumValue: 1, maximumValue: 2 } });
|
|
// @ts-expect-error not a type of the table
|
|
AlexaErrors.thermostatIsOn("the thermostat is on");
|
|
const plainErrors: string[] = [
|
|
AlexaErrors.endpointUnreachable("the lamp does not answer"), AlexaErrors.obstacleDetected("something is in the way"),
|
|
AlexaErrors.valueOutOfRange("too high"), AlexaErrors.maintenanceRequired("the bin is full", "EMPTY_BIN"),
|
|
].map((error) => error.type);
|
|
const errorNamespace: string = AlexaErrors.setpointsTooClose("too close", { value: 2, scale: "CELSIUS" }).namespace;
|
|
|
|
// Handlers: the directives of the interface by name, the payload of each typed by its descriptor
|
|
lift.on("SetRangeValue", (ctx) => {
|
|
const target: number = ctx.payload.rangeValue;
|
|
return ctx.respond((s) => s.set(lift, "rangeValue", target));
|
|
});
|
|
lift.on("*", (ctx) => ctx.error("INVALID_DIRECTIVE", `${ctx.name} is not supported`));
|
|
// @ts-expect-error not a directive of the interface
|
|
lift.on("SetRange", (ctx) => ctx.respond());
|
|
// @ts-expect-error the payload of SetRangeValue has no brightness
|
|
lift.on("SetRangeValue", (ctx) => ctx.payload.brightness);
|
|
device.state((s) => s.health("OK")).onReportState((ctx) => ctx.report());
|