registry: SceneController events, DoorbellEventSource, SimpleEventSource, TimeHoldController, InventoryLevelSensor, WakeOnLANController
Six descriptors written from their pages replace the last stubs of tiers 1 and 2. A scene answers Activate and Deactivate through ctx.respond() with ActivationStarted and DeactivationStarted, the time and the cause filled in. device.raise(descriptor, name, payload) publishes DoorbellPress and the Event of a button on <root>/event with the endpoint and a new messageId; it throws a MessageError for an interface or instance the device did not declare, an event that answers a directive, a payload that does not fit and a message over 16000 bytes. TurnOn of a device with WakeOnLANController is deferred without the warning. On the wire: a doorbell has no properties object and proactivelyReported on the capability; SimpleEventSource is version 1.0, InventoryLevelSensor and WakeOnLANController version 3 (1.5.2: 1). A scene declared without options is announced as before. Alex2MQTT has no topic yet for the WakeUp event. 24 examples of the six pages are saved as fixtures. 267 tests pass, 241 before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
d7dfc5b122
commit
3a07c861b1
116 changed files with 3136 additions and 166 deletions
19
dist/esm/device/Device.d.ts
vendored
19
dist/esm/device/Device.d.ts
vendored
|
|
@ -8,7 +8,7 @@ import type { ChangeCause } from "../messages/types.js";
|
|||
import type { DirectiveHandler, Fill } from "../dispatcher.js";
|
||||
import type { DisplayCategoryName } from "../registry/catalog.js";
|
||||
import type { Directives, InterfaceDescriptor, Properties } from "../registry/types.js";
|
||||
import type { Publisher } from "../transport.js";
|
||||
import type { Publisher, PublishResult } from "../transport.js";
|
||||
import { Capability } from "./Capability.js";
|
||||
import type { AnyCapability, CapabilityJson, Declaration } from "./Capability.js";
|
||||
import type { EndpointFields } from "./validate.js";
|
||||
|
|
@ -102,6 +102,23 @@ declare class Device extends EventEmitter {
|
|||
* (1.5.1). Resolves with the topic published to, or "" when the publish failed (never rejects, 1.5.2).
|
||||
*/
|
||||
sendSceneResponse(correlationToken: string, activated: boolean, cause?: ChangeCause, sendAsync?: boolean): Promise<string>;
|
||||
/**
|
||||
* Say that something happened on the device, which nobody asked for:
|
||||
*
|
||||
* door.raise(DoorbellEventSource, "DoorbellPress");
|
||||
* remote.raise(SimpleEventSource, "Event", { id: "Button.SinglePush.1" }, { instance: topButton.instance });
|
||||
*
|
||||
* The event goes to <root>/event, which Alex2MQTT posts to Alexa: up to 30 events a minute for a root. The
|
||||
* payload is checked by the descriptor, which also sets the time of the event to now and its cause to the usual
|
||||
* one when the payload has none. Resolves with what became of the publish and does not reject.
|
||||
*
|
||||
* Throws a MessageError for an event that would not arrive: of an interface or an instance the device did not
|
||||
* declare, not an event of the interface, with a payload that does not fit, or too long for Alex2MQTT.
|
||||
*/
|
||||
raise(descriptor: InterfaceDescriptor<any, any, any, boolean>, name: string, payload?: Record<string, unknown>, options?: {
|
||||
instance?: string;
|
||||
messageId?: string;
|
||||
}): Promise<PublishResult>;
|
||||
/**
|
||||
* How the device reports its state, every retrievable property of it:
|
||||
*
|
||||
|
|
|
|||
68
dist/esm/device/Device.js
vendored
68
dist/esm/device/Device.js
vendored
|
|
@ -3,9 +3,10 @@ import { AlexaErrorResponse } from "../compat/AlexaErrorResponse.js";
|
|||
import { AlexaInterface } from "../compat/AlexaInterface.js";
|
||||
import { AlexaStatusMessage } from "../compat/AlexaStatusMessage.js";
|
||||
import { DisplayCategory } from "../compat/enums.js";
|
||||
import { sceneEvent } from "../messages/build.js";
|
||||
import { MessageError, proactiveEvent, sceneEvent } from "../messages/build.js";
|
||||
import { Alexa } from "../registry/interfaces/Alexa.js";
|
||||
import { EndpointHealth } from "../registry/interfaces/EndpointHealth.js";
|
||||
import { SceneController } from "../registry/interfaces/SceneController.js";
|
||||
import { SchemaError } from "../registry/schema.js";
|
||||
import { DeclarationError } from "../registry/types.js";
|
||||
import * as topics from "../topics.js";
|
||||
|
|
@ -97,6 +98,69 @@ class Device extends EventEmitter {
|
|||
const build = () => sceneEvent({ endpointId: this.endpointId, correlationToken, activated, cause });
|
||||
return send(this.publisher, answer(this.rootTopic, this.endpointId), build, this.onPublishError);
|
||||
}
|
||||
/**
|
||||
* Say that something happened on the device, which nobody asked for:
|
||||
*
|
||||
* door.raise(DoorbellEventSource, "DoorbellPress");
|
||||
* remote.raise(SimpleEventSource, "Event", { id: "Button.SinglePush.1" }, { instance: topButton.instance });
|
||||
*
|
||||
* The event goes to <root>/event, which Alex2MQTT posts to Alexa: up to 30 events a minute for a root. The
|
||||
* payload is checked by the descriptor, which also sets the time of the event to now and its cause to the usual
|
||||
* one when the payload has none. Resolves with what became of the publish and does not reject.
|
||||
*
|
||||
* Throws a MessageError for an event that would not arrive: of an interface or an instance the device did not
|
||||
* declare, not an event of the interface, with a payload that does not fit, or too long for Alex2MQTT.
|
||||
*/
|
||||
raise(descriptor, name, payload = {}, options = {}) {
|
||||
const { endpointId } = this;
|
||||
const { namespace } = descriptor;
|
||||
const { instance = "", messageId } = options;
|
||||
const refuse = (problem) => {
|
||||
throw new MessageError(endpointId, `${namespace}.${name} was not raised: ${problem}`);
|
||||
};
|
||||
const capability = this.capability(namespace, instance);
|
||||
if (!capability) {
|
||||
const declared = instance ? `the instance ${JSON.stringify(instance)} of ${namespace}` : namespace;
|
||||
return refuse(`the device did not declare ${declared}. Declare it with add() first${descriptor.instanced && !instance ? ", and pass the instance that raises the event" : ""}`);
|
||||
}
|
||||
const events = capability.descriptor.events ?? {};
|
||||
const event = events[name];
|
||||
if (!event)
|
||||
return refuse(`it is not an event of the interface, which has ${Object.keys(events).join(", ") || "none"}`);
|
||||
if (event.topic !== "proactive")
|
||||
return refuse("it answers a directive. Send it with respond() in the handler of the directive");
|
||||
let checked;
|
||||
try {
|
||||
checked = event.payload.parse(payload, "payload");
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof SchemaError)
|
||||
return refuse(err.message);
|
||||
throw err;
|
||||
}
|
||||
const message = proactiveEvent({
|
||||
endpointId,
|
||||
messageId,
|
||||
namespace: event.namespace ?? namespace,
|
||||
name,
|
||||
instance,
|
||||
payloadVersion: event.payloadVersion ?? capability.descriptor.version,
|
||||
payload: checked,
|
||||
});
|
||||
const bytes = Buffer.byteLength(JSON.stringify(message));
|
||||
if (bytes > topics.EVENT_BYTES)
|
||||
return refuse(`it is ${bytes} bytes as JSON, Alex2MQTT takes ${topics.EVENT_BYTES}`);
|
||||
const topic = topics.event(this.rootTopic);
|
||||
const unpublished = new Error(`nothing was published to ${topic}: the device is on no bridge, register it with addDevice() or registerDevice()`);
|
||||
const published = this.publisher
|
||||
? this.publisher.publish(topic, message)
|
||||
: Promise.resolve({ ok: false, topic, error: unpublished });
|
||||
return published.then((result) => {
|
||||
if (!result.ok)
|
||||
this.onPublishError?.(result.error);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* How the device reports its state, every retrievable property of it:
|
||||
*
|
||||
|
|
@ -264,7 +328,7 @@ class Device extends EventEmitter {
|
|||
const has = (namespace) => declared.some((capability) => capability.namespace === namespace);
|
||||
const added = [];
|
||||
// alexa-scenecontroller.html, "Discovery": a scene is not a physical device and has no Alexa.EndpointHealth
|
||||
if (this.endpointHealth && !has(EndpointHealth.namespace) && !has("Alexa.SceneController")) {
|
||||
if (this.endpointHealth && !has(EndpointHealth.namespace) && !has(SceneController.namespace)) {
|
||||
added.push(new Capability(EndpointHealth, { options: {} }));
|
||||
}
|
||||
if (this.alexaInterface && !has(Alexa.namespace))
|
||||
|
|
|
|||
7
dist/esm/dispatcher.js
vendored
7
dist/esm/dispatcher.js
vendored
|
|
@ -57,7 +57,7 @@ class Context {
|
|||
}
|
||||
defer(estimatedDeferralInSeconds) {
|
||||
const { endpointId, correlationToken } = this;
|
||||
if (this.capability && !this.capability.descriptor.deferrable)
|
||||
if (this.capability && !this.capability.descriptor.deferrable && !this.deferredByAnother())
|
||||
this.dispatcher.noteDeferral(this.namespace);
|
||||
// Always where the first answer goes: a second DeferredResponse is refused there
|
||||
const topic = topics.response(this.dispatcher.rootTopic, endpointId);
|
||||
|
|
@ -75,6 +75,11 @@ class Context {
|
|||
const { type, alexaMessage, namespace } = error;
|
||||
return this.answer(errorResponse({ endpointId, correlationToken, type, message: alexaMessage, extra: error.extra, namespace }));
|
||||
}
|
||||
// An interface of the device documents a DeferredResponse for this directive of another one
|
||||
deferredByAnother() {
|
||||
const { namespace, name } = this;
|
||||
return this.device.getCapabilities().some(({ descriptor }) => descriptor.defers?.some((directive) => directive.namespace === namespace && directive.name === name));
|
||||
}
|
||||
// The state of the device, and over it what the handler says about this directive
|
||||
state(fill) {
|
||||
const whole = new StateBuilder();
|
||||
|
|
|
|||
8
dist/esm/index.d.ts
vendored
8
dist/esm/index.d.ts
vendored
|
|
@ -5,14 +5,14 @@ export type { EndpointDefinition, EndpointJson } from "./device/Device.js";
|
|||
export { Capability } from "./device/Capability.js";
|
||||
export type { CapabilityJson, CommonOptions, Declaration } from "./device/Capability.js";
|
||||
export { registry, DeclarationError, SchemaError } from "./registry/index.js";
|
||||
export { Alexa, BrightnessController, ChannelController, ColorController, ColorTemperatureController, ContactSensor, EqualizerController, HumiditySensor, InputController, LockController, ModeController, MotionSensor, PercentageController, PlaybackController, PlaybackStateReporter, PowerLevelController, RangeController, SecurityPanelController, Speaker, StepSpeaker, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, } from "./registry/index.js";
|
||||
export { Alexa, BrightnessController, ChannelController, ColorController, ColorTemperatureController, ContactSensor, DoorbellEventSource, EqualizerController, HumiditySensor, InputController, InventoryLevelSensor, LockController, ModeController, MotionSensor, PercentageController, PlaybackController, PlaybackStateReporter, PowerLevelController, RangeController, SceneController, SecurityPanelController, SimpleEventSource, Speaker, StepSpeaker, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, TimeHoldController, ToggleController, WakeOnLANController, } from "./registry/index.js";
|
||||
export { PowerController, EndpointHealth } from "./compat/enums.js";
|
||||
export { asset, text, semantics, SemanticsBuilder } from "./registry/index.js";
|
||||
export { ASSETS as Assets, UNITS_OF_MEASURE as Units, ACTIONS as Actions, STATES as States, DISPLAY_CATEGORIES as DisplayCategories, INPUTS as Inputs, THERMOSTAT_MODES as ThermostatModes, } from "./registry/index.js";
|
||||
export type { ActionId, AssetId, DisplayCategoryName, StateId, UnitOfMeasure, ActionsToDirective, AnyDescriptor, CapabilityExtras, Declared, DirectiveDescriptor, EndpointView, EventDescriptor, InterfaceDescriptor, Label, PropertyDescriptor, Semantics, StatesToRange, StatesToValue, ActionName, StateName, InputName, Mode, ThermostatModeName, Infer, Schema, Temperature, TimeInterval, } from "./registry/index.js";
|
||||
export { ASSETS as Assets, UNITS_OF_MEASURE as Units, ACTIONS as Actions, STATES as States, CAUSES as Causes, DISPLAY_CATEGORIES as DisplayCategories, INPUTS as Inputs, THERMOSTAT_MODES as ThermostatModes, } from "./registry/index.js";
|
||||
export type { ActionId, AssetId, DisplayCategoryName, StateId, UnitOfMeasure, ActionsToDirective, AnyDescriptor, CapabilityExtras, Declared, DirectiveDescriptor, EndpointView, EventDescriptor, InterfaceDescriptor, Label, PropertyDescriptor, Semantics, StatesToRange, StatesToValue, ActionName, StateName, Cause, InputName, InventoryLevel, Mode, ThermostatModeName, Infer, Schema, Temperature, TimeInterval, } from "./registry/index.js";
|
||||
export * as messages from "./messages/index.js";
|
||||
export { AlexaError, AlexaErrors, MessageError, StateBuilder, property } from "./messages/index.js";
|
||||
export type { ChangeCause, ChangeReportMessage, DeferredResponseMessage, ErrorResponseMessage, Header, ProactiveEventMessage, Property, PropertyOptions, ResponseMessage, SceneEventMessage, } from "./messages/index.js";
|
||||
export type { ChangeCause, ChangeReportMessage, DeferredResponseMessage, ErrorResponseMessage, Header, ProactiveEventFields, ProactiveEventMessage, Property, PropertyOptions, ResponseMessage, SceneEventMessage, } from "./messages/index.js";
|
||||
export * as topics from "./topics.js";
|
||||
export { MemoryPublisher } from "./transport.js";
|
||||
export type { Publisher, PublishResult } from "./transport.js";
|
||||
|
|
|
|||
4
dist/esm/index.js
vendored
4
dist/esm/index.js
vendored
|
|
@ -4,11 +4,11 @@ export { default as Device } from "./device/Device.js";
|
|||
export { Capability } from "./device/Capability.js";
|
||||
// The interfaces, for device.add(), and the registry that holds them by name
|
||||
export { registry, DeclarationError, SchemaError } from "./registry/index.js";
|
||||
export { Alexa, BrightnessController, ChannelController, ColorController, ColorTemperatureController, ContactSensor, EqualizerController, HumiditySensor, InputController, LockController, ModeController, MotionSensor, PercentageController, PlaybackController, PlaybackStateReporter, PowerLevelController, RangeController, SecurityPanelController, Speaker, StepSpeaker, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, } from "./registry/index.js";
|
||||
export { Alexa, BrightnessController, ChannelController, ColorController, ColorTemperatureController, ContactSensor, DoorbellEventSource, EqualizerController, HumiditySensor, InputController, InventoryLevelSensor, LockController, ModeController, MotionSensor, PercentageController, PlaybackController, PlaybackStateReporter, PowerLevelController, RangeController, SceneController, SecurityPanelController, SimpleEventSource, Speaker, StepSpeaker, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, TimeHoldController, ToggleController, WakeOnLANController, } from "./registry/index.js";
|
||||
export { PowerController, EndpointHealth } from "./compat/enums.js";
|
||||
export { asset, text, semantics, SemanticsBuilder } from "./registry/index.js";
|
||||
// The vocabularies of the Smart Home API
|
||||
export { ASSETS as Assets, UNITS_OF_MEASURE as Units, ACTIONS as Actions, STATES as States, DISPLAY_CATEGORIES as DisplayCategories, INPUTS as Inputs, THERMOSTAT_MODES as ThermostatModes, } from "./registry/index.js";
|
||||
export { ASSETS as Assets, UNITS_OF_MEASURE as Units, ACTIONS as Actions, STATES as States, CAUSES as Causes, DISPLAY_CATEGORIES as DisplayCategories, INPUTS as Inputs, THERMOSTAT_MODES as ThermostatModes, } from "./registry/index.js";
|
||||
// The messages: what the bridge publishes, built from plain values
|
||||
export * as messages from "./messages/index.js";
|
||||
export { AlexaError, AlexaErrors, MessageError, StateBuilder, property } from "./messages/index.js";
|
||||
|
|
|
|||
13
dist/esm/messages/build.d.ts
vendored
13
dist/esm/messages/build.d.ts
vendored
|
|
@ -78,6 +78,19 @@ export interface SceneEventFields extends Answer {
|
|||
}
|
||||
/** The answer to Activate and Deactivate of a scene (alexa-scenecontroller.html). */
|
||||
export declare function sceneEvent(fields: SceneEventFields): SceneEventMessage;
|
||||
export interface ProactiveEventFields extends Envelope {
|
||||
/** "Alexa.DoorbellEventSource" */
|
||||
namespace: string;
|
||||
/** "DoorbellPress" */
|
||||
name: string;
|
||||
/** The instance of a generic interface that raises the event. */
|
||||
instance?: string;
|
||||
/** Default: "3". */
|
||||
payloadVersion?: string;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
/** An event a device raises by itself, of any interface. The header has no correlationToken: nobody asked. */
|
||||
export declare function proactiveEvent(fields: ProactiveEventFields): ProactiveEventMessage;
|
||||
export interface DoorbellPressFields extends Envelope {
|
||||
/** Default: "PHYSICAL_INTERACTION", somebody pressed the button. */
|
||||
cause?: ChangeCause;
|
||||
|
|
|
|||
29
dist/esm/messages/build.js
vendored
29
dist/esm/messages/build.js
vendored
|
|
@ -99,27 +99,26 @@ export function sceneEvent(fields) {
|
|||
context: {},
|
||||
};
|
||||
}
|
||||
/** Somebody rang (alexa-doorbelleventsource.html). */
|
||||
export function doorbellPress(fields) {
|
||||
/** An event a device raises by itself, of any interface. The header has no correlationToken: nobody asked. */
|
||||
export function proactiveEvent(fields) {
|
||||
const { namespace, name, instance, messageId, payloadVersion, payload } = fields;
|
||||
return {
|
||||
event: {
|
||||
header: header("Alexa.DoorbellEventSource", "DoorbellPress", { messageId: fields.messageId }),
|
||||
header: header(namespace, name, { instance, messageId, payloadVersion }),
|
||||
endpoint: { endpointId: fields.endpointId },
|
||||
payload: { cause: { type: fields.cause ?? "PHYSICAL_INTERACTION" }, timestamp: isoTime(fields.timestamp) },
|
||||
payload,
|
||||
},
|
||||
};
|
||||
}
|
||||
/** Somebody rang (alexa-doorbelleventsource.html). */
|
||||
export function doorbellPress(fields) {
|
||||
const { endpointId, messageId } = fields;
|
||||
const payload = { cause: { type: fields.cause ?? "PHYSICAL_INTERACTION" }, timestamp: isoTime(fields.timestamp) };
|
||||
return proactiveEvent({ endpointId, messageId, namespace: "Alexa.DoorbellEventSource", name: "DoorbellPress", payload });
|
||||
}
|
||||
/** An event of a button or a sensor that routines start on (alexa-simpleeventsource.html). */
|
||||
export function simpleEvent(fields) {
|
||||
return {
|
||||
event: {
|
||||
header: header("Alexa.SimpleEventSource", "Event", {
|
||||
instance: fields.instance,
|
||||
messageId: fields.messageId,
|
||||
payloadVersion: "1.0",
|
||||
}),
|
||||
endpoint: { endpointId: fields.endpointId },
|
||||
payload: { id: fields.id, timestamp: isoTime(fields.timestamp) },
|
||||
},
|
||||
};
|
||||
const { endpointId, messageId, instance } = fields;
|
||||
const payload = { id: fields.id, timestamp: isoTime(fields.timestamp) };
|
||||
return proactiveEvent({ endpointId, messageId, instance, namespace: "Alexa.SimpleEventSource", name: "Event", payloadVersion: "1.0", payload });
|
||||
}
|
||||
|
|
|
|||
4
dist/esm/messages/index.d.ts
vendored
4
dist/esm/messages/index.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
export { changeReport, deferredResponse, doorbellPress, errorResponse, response, sceneEvent, simpleEvent, stateReport, MessageError, } from "./build.js";
|
||||
export type { ChangeReportFields, DoorbellPressFields, ErrorResponseFields, ResponseFields, SceneEventFields, SimpleEventFields, } from "./build.js";
|
||||
export { changeReport, deferredResponse, doorbellPress, errorResponse, proactiveEvent, response, sceneEvent, simpleEvent, stateReport, MessageError, } from "./build.js";
|
||||
export type { ChangeReportFields, DoorbellPressFields, ErrorResponseFields, ProactiveEventFields, ResponseFields, SceneEventFields, SimpleEventFields, } from "./build.js";
|
||||
export { AlexaError, AlexaErrors, errorNamespace } from "./errors.js";
|
||||
export type { ChargeState, ControlUnavailableReason, CurrentDeviceMode } from "./errors.js";
|
||||
export { property } from "./property.js";
|
||||
|
|
|
|||
2
dist/esm/messages/index.js
vendored
2
dist/esm/messages/index.js
vendored
|
|
@ -1,5 +1,5 @@
|
|||
// The messages of the bridge without the bridge: builders, the state collector and the errors.
|
||||
export { changeReport, deferredResponse, doorbellPress, errorResponse, response, sceneEvent, simpleEvent, stateReport, MessageError, } from "./build.js";
|
||||
export { changeReport, deferredResponse, doorbellPress, errorResponse, proactiveEvent, response, sceneEvent, simpleEvent, stateReport, MessageError, } from "./build.js";
|
||||
export { AlexaError, AlexaErrors, errorNamespace } from "./errors.js";
|
||||
export { property } from "./property.js";
|
||||
export { StateBuilder } from "./StateBuilder.js";
|
||||
|
|
|
|||
8
dist/esm/messages/types.d.ts
vendored
8
dist/esm/messages/types.d.ts
vendored
|
|
@ -1,8 +1,6 @@
|
|||
/**
|
||||
* Why a property changed or an event was raised (message-guide.html, "Cause object"). RULE_TRIGGER is not in that
|
||||
* table; the ChangeReport examples of alexa-securitypanelcontroller.html and alexa-thermostatcontroller.html use it.
|
||||
*/
|
||||
export type ChangeCause = "APP_INTERACTION" | "PERIODIC_POLL" | "PHYSICAL_INTERACTION" | "RULE_TRIGGER" | "VOICE_INTERACTION";
|
||||
import type { Cause } from "../registry/catalog.js";
|
||||
/** Why a property changed or an event was raised: one of CAUSES. */
|
||||
export type ChangeCause = Cause;
|
||||
export interface Header {
|
||||
namespace: string;
|
||||
name: string;
|
||||
|
|
|
|||
6
dist/esm/registry/catalog.d.ts
vendored
6
dist/esm/registry/catalog.d.ts
vendored
|
|
@ -21,6 +21,12 @@ export declare const DISPLAY_CATEGORIES: readonly ["ACTIVITY_TRIGGER", "AIR_COND
|
|||
export type DisplayCategoryName = (typeof DISPLAY_CATEGORIES)[number];
|
||||
/** Not to be used as a friendly name (resources-and-assets.html, "Reserved words"). */
|
||||
export declare const RESERVED_WORDS: readonly ["alarm", "alarms", "all alarms", "away mode", "bass", "camera", "date", "date today", "day", "do not disturb", "drop in", "music", "night light", "notification", "playing", "sleep sounds", "time", "timer", "today in music", "treble", "volume", "way f. m."];
|
||||
/**
|
||||
* Why a property changed or an event was raised (message-guide.html, "Cause object"). RULE_TRIGGER is not in that
|
||||
* table; the ChangeReport examples of alexa-securitypanelcontroller.html and alexa-thermostatcontroller.html use it.
|
||||
*/
|
||||
export declare const CAUSES: readonly ["APP_INTERACTION", "PERIODIC_POLL", "PHYSICAL_INTERACTION", "RULE_TRIGGER", "VOICE_INTERACTION"];
|
||||
export type Cause = (typeof CAUSES)[number];
|
||||
/**
|
||||
* What a discovery answer may hold (alexa-discovery.html, "Interface limits"; alexa-discovery-objects.html,
|
||||
* "Endpoint object details" and "AdditionalAttributes object details"; alexa-scenecontroller.html, "Discovery").
|
||||
|
|
|
|||
5
dist/esm/registry/catalog.js
vendored
5
dist/esm/registry/catalog.js
vendored
|
|
@ -67,6 +67,11 @@ export const RESERVED_WORDS = [
|
|||
"drop in", "music", "night light", "notification", "playing", "sleep sounds", "time", "timer", "today in music",
|
||||
"treble", "volume", "way f. m.",
|
||||
];
|
||||
/**
|
||||
* Why a property changed or an event was raised (message-guide.html, "Cause object"). RULE_TRIGGER is not in that
|
||||
* table; the ChangeReport examples of alexa-securitypanelcontroller.html and alexa-thermostatcontroller.html use it.
|
||||
*/
|
||||
export const CAUSES = ["APP_INTERACTION", "PERIODIC_POLL", "PHYSICAL_INTERACTION", "RULE_TRIGGER", "VOICE_INTERACTION"];
|
||||
/**
|
||||
* What a discovery answer may hold (alexa-discovery.html, "Interface limits"; alexa-discovery-objects.html,
|
||||
* "Endpoint object details" and "AdditionalAttributes object details"; alexa-scenecontroller.html, "Discovery").
|
||||
|
|
|
|||
13
dist/esm/registry/events.d.ts
vendored
Normal file
13
dist/esm/registry/events.d.ts
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import type { Cause } from "./catalog.js";
|
||||
/** The time of an event: now, unless the payload says when. */
|
||||
export declare const timestamp: import("./schema.js").Schema<string>;
|
||||
/**
|
||||
* The payload of an event that happened at a time and for a reason: ActivationStarted, DoorbellPress. Both are
|
||||
* required by Alexa. The cause is the usual one of the event unless the payload names another.
|
||||
*/
|
||||
export declare const happened: (usually: Cause) => import("./schema.js").Schema<import("./schema.js").InferShape<{
|
||||
cause: import("./schema.js").Schema<{
|
||||
type: "APP_INTERACTION" | "PERIODIC_POLL" | "PHYSICAL_INTERACTION" | "RULE_TRIGGER" | "VOICE_INTERACTION";
|
||||
}>;
|
||||
timestamp: import("./schema.js").Schema<string>;
|
||||
}>>;
|
||||
13
dist/esm/registry/events.js
vendored
Normal file
13
dist/esm/registry/events.js
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// What the events of several interfaces have in common.
|
||||
import { CAUSES } from "./catalog.js";
|
||||
import { s } from "./schema.js";
|
||||
/** The time of an event: now, unless the payload says when. */
|
||||
export const timestamp = s.defaulted(s.dateTime(), () => new Date().toISOString());
|
||||
/**
|
||||
* The payload of an event that happened at a time and for a reason: ActivationStarted, DoorbellPress. Both are
|
||||
* required by Alexa. The cause is the usual one of the event unless the payload names another.
|
||||
*/
|
||||
export const happened = (usually) => s.object({
|
||||
cause: s.defaulted(s.object({ type: s.enum(...CAUSES) }), () => ({ type: usually })),
|
||||
timestamp,
|
||||
});
|
||||
9
dist/esm/registry/index.d.ts
vendored
9
dist/esm/registry/index.d.ts
vendored
|
|
@ -4,10 +4,12 @@ import { ChannelController } from "./interfaces/ChannelController.js";
|
|||
import { ColorController } from "./interfaces/ColorController.js";
|
||||
import { ColorTemperatureController } from "./interfaces/ColorTemperatureController.js";
|
||||
import { ContactSensor } from "./interfaces/ContactSensor.js";
|
||||
import { DoorbellEventSource } from "./interfaces/DoorbellEventSource.js";
|
||||
import { EndpointHealth } from "./interfaces/EndpointHealth.js";
|
||||
import { EqualizerController } from "./interfaces/EqualizerController.js";
|
||||
import { HumiditySensor } from "./interfaces/HumiditySensor.js";
|
||||
import { InputController } from "./interfaces/InputController.js";
|
||||
import { InventoryLevelSensor } from "./interfaces/InventoryLevelSensor.js";
|
||||
import { LockController } from "./interfaces/LockController.js";
|
||||
import { ModeController } from "./interfaces/ModeController.js";
|
||||
import { MotionSensor } from "./interfaces/MotionSensor.js";
|
||||
|
|
@ -17,13 +19,17 @@ import { PlaybackStateReporter } from "./interfaces/PlaybackStateReporter.js";
|
|||
import { PowerController } from "./interfaces/PowerController.js";
|
||||
import { PowerLevelController } from "./interfaces/PowerLevelController.js";
|
||||
import { RangeController } from "./interfaces/RangeController.js";
|
||||
import { SceneController } from "./interfaces/SceneController.js";
|
||||
import { SecurityPanelController } from "./interfaces/SecurityPanelController.js";
|
||||
import { SimpleEventSource } from "./interfaces/SimpleEventSource.js";
|
||||
import { Speaker } from "./interfaces/Speaker.js";
|
||||
import { StepSpeaker } from "./interfaces/StepSpeaker.js";
|
||||
import { TemperatureSensor } from "./interfaces/TemperatureSensor.js";
|
||||
import { ThermostatController } from "./interfaces/ThermostatController.js";
|
||||
import { ThermostatControllerSchedule } from "./interfaces/ThermostatControllerSchedule.js";
|
||||
import { TimeHoldController } from "./interfaces/TimeHoldController.js";
|
||||
import { ToggleController } from "./interfaces/ToggleController.js";
|
||||
import { WakeOnLANController } from "./interfaces/WakeOnLANController.js";
|
||||
import type { AnyDescriptor } from "./types.js";
|
||||
export declare const registry: {
|
||||
/** Whether an interface of this name is known. */
|
||||
|
|
@ -33,9 +39,10 @@ export declare const registry: {
|
|||
/** Every descriptor, ordered by namespace. */
|
||||
list(): AnyDescriptor[];
|
||||
};
|
||||
export { Alexa, BrightnessController, ChannelController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, EqualizerController, HumiditySensor, InputController, LockController, ModeController, MotionSensor, PercentageController, PlaybackController, PlaybackStateReporter, PowerController, PowerLevelController, RangeController, SecurityPanelController, Speaker, StepSpeaker, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, };
|
||||
export { Alexa, BrightnessController, ChannelController, ColorController, ColorTemperatureController, ContactSensor, DoorbellEventSource, EndpointHealth, EqualizerController, HumiditySensor, InputController, InventoryLevelSensor, LockController, ModeController, MotionSensor, PercentageController, PlaybackController, PlaybackStateReporter, PowerController, PowerLevelController, RangeController, SceneController, SecurityPanelController, SimpleEventSource, Speaker, StepSpeaker, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, TimeHoldController, ToggleController, WakeOnLANController, };
|
||||
export { INPUTS } from "./interfaces/InputController.js";
|
||||
export type { InputName } from "./interfaces/InputController.js";
|
||||
export type { InventoryLevel } from "./interfaces/InventoryLevelSensor.js";
|
||||
export type { Mode } from "./interfaces/ModeController.js";
|
||||
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
|
||||
export type { ThermostatModeName } from "./interfaces/ThermostatController.js";
|
||||
|
|
|
|||
14
dist/esm/registry/index.js
vendored
14
dist/esm/registry/index.js
vendored
|
|
@ -5,10 +5,12 @@ import { ChannelController } from "./interfaces/ChannelController.js";
|
|||
import { ColorController } from "./interfaces/ColorController.js";
|
||||
import { ColorTemperatureController } from "./interfaces/ColorTemperatureController.js";
|
||||
import { ContactSensor } from "./interfaces/ContactSensor.js";
|
||||
import { DoorbellEventSource } from "./interfaces/DoorbellEventSource.js";
|
||||
import { EndpointHealth } from "./interfaces/EndpointHealth.js";
|
||||
import { EqualizerController } from "./interfaces/EqualizerController.js";
|
||||
import { HumiditySensor } from "./interfaces/HumiditySensor.js";
|
||||
import { InputController } from "./interfaces/InputController.js";
|
||||
import { InventoryLevelSensor } from "./interfaces/InventoryLevelSensor.js";
|
||||
import { LockController } from "./interfaces/LockController.js";
|
||||
import { ModeController } from "./interfaces/ModeController.js";
|
||||
import { MotionSensor } from "./interfaces/MotionSensor.js";
|
||||
|
|
@ -18,13 +20,17 @@ import { PlaybackStateReporter } from "./interfaces/PlaybackStateReporter.js";
|
|||
import { PowerController } from "./interfaces/PowerController.js";
|
||||
import { PowerLevelController } from "./interfaces/PowerLevelController.js";
|
||||
import { RangeController } from "./interfaces/RangeController.js";
|
||||
import { SceneController } from "./interfaces/SceneController.js";
|
||||
import { SecurityPanelController } from "./interfaces/SecurityPanelController.js";
|
||||
import { SimpleEventSource } from "./interfaces/SimpleEventSource.js";
|
||||
import { Speaker } from "./interfaces/Speaker.js";
|
||||
import { StepSpeaker } from "./interfaces/StepSpeaker.js";
|
||||
import { TemperatureSensor } from "./interfaces/TemperatureSensor.js";
|
||||
import { ThermostatController } from "./interfaces/ThermostatController.js";
|
||||
import { ThermostatControllerSchedule } from "./interfaces/ThermostatControllerSchedule.js";
|
||||
import { TimeHoldController } from "./interfaces/TimeHoldController.js";
|
||||
import { ToggleController } from "./interfaces/ToggleController.js";
|
||||
import { WakeOnLANController } from "./interfaces/WakeOnLANController.js";
|
||||
import { STUBS } from "./interfaces/stubs.js";
|
||||
import { DeclarationError } from "./types.js";
|
||||
const described = [
|
||||
|
|
@ -34,10 +40,12 @@ const described = [
|
|||
ColorController,
|
||||
ColorTemperatureController,
|
||||
ContactSensor,
|
||||
DoorbellEventSource,
|
||||
EndpointHealth,
|
||||
EqualizerController,
|
||||
HumiditySensor,
|
||||
InputController,
|
||||
InventoryLevelSensor,
|
||||
LockController,
|
||||
ModeController,
|
||||
MotionSensor,
|
||||
|
|
@ -47,13 +55,17 @@ const described = [
|
|||
PowerController,
|
||||
PowerLevelController,
|
||||
RangeController,
|
||||
SceneController,
|
||||
SecurityPanelController,
|
||||
SimpleEventSource,
|
||||
Speaker,
|
||||
StepSpeaker,
|
||||
TemperatureSensor,
|
||||
ThermostatController,
|
||||
ThermostatControllerSchedule,
|
||||
TimeHoldController,
|
||||
ToggleController,
|
||||
WakeOnLANController,
|
||||
];
|
||||
const descriptors = new Map();
|
||||
for (const descriptor of [...described, ...STUBS]) {
|
||||
|
|
@ -79,7 +91,7 @@ export const registry = {
|
|||
return [...descriptors.values()].sort((a, b) => (a.namespace < b.namespace ? -1 : 1));
|
||||
},
|
||||
};
|
||||
export { Alexa, BrightnessController, ChannelController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, EqualizerController, HumiditySensor, InputController, LockController, ModeController, MotionSensor, PercentageController, PlaybackController, PlaybackStateReporter, PowerController, PowerLevelController, RangeController, SecurityPanelController, Speaker, StepSpeaker, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, };
|
||||
export { Alexa, BrightnessController, ChannelController, ColorController, ColorTemperatureController, ContactSensor, DoorbellEventSource, EndpointHealth, EqualizerController, HumiditySensor, InputController, InventoryLevelSensor, LockController, ModeController, MotionSensor, PercentageController, PlaybackController, PlaybackStateReporter, PowerController, PowerLevelController, RangeController, SceneController, SecurityPanelController, SimpleEventSource, Speaker, StepSpeaker, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, TimeHoldController, ToggleController, WakeOnLANController, };
|
||||
export { INPUTS } from "./interfaces/InputController.js";
|
||||
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
|
||||
export { asset, text } from "./resources.js";
|
||||
|
|
|
|||
6
dist/esm/registry/interfaces/DoorbellEventSource.d.ts
vendored
Normal file
6
dist/esm/registry/interfaces/DoorbellEventSource.d.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* A doorbell. It takes no directive and reports nothing; device.raise(DoorbellEventSource, "DoorbellPress") says
|
||||
* that somebody rang. Alexa wants 30 seconds between two presses of one doorbell, and announces a press when the
|
||||
* user switched the announcements on for the doorbell in the Alexa app.
|
||||
*/
|
||||
export declare const DoorbellEventSource: import("../types.js").InterfaceDescriptor<{}, {}, {}, false>;
|
||||
31
dist/esm/registry/interfaces/DoorbellEventSource.js
vendored
Normal file
31
dist/esm/registry/interfaces/DoorbellEventSource.js
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { happened } from "../events.js";
|
||||
import { DeclarationError, defineInterface } from "../types.js";
|
||||
/**
|
||||
* A doorbell. It takes no directive and reports nothing; device.raise(DoorbellEventSource, "DoorbellPress") says
|
||||
* that somebody rang. Alexa wants 30 seconds between two presses of one doorbell, and announces a press when the
|
||||
* user switched the announcements on for the doorbell in the Alexa app.
|
||||
*/
|
||||
export const DoorbellEventSource = defineInterface({
|
||||
namespace: "Alexa.DoorbellEventSource",
|
||||
version: "3",
|
||||
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-doorbelleventsource.html",
|
||||
kind: "eventSource",
|
||||
tier: 2,
|
||||
instanced: false,
|
||||
properties: {},
|
||||
directives: {},
|
||||
events: {
|
||||
DoorbellPress: { name: "DoorbellPress", payload: happened("PHYSICAL_INTERACTION"), topic: "proactive" },
|
||||
},
|
||||
// The capability is the announcement that the endpoint raises the event: proactivelyReported whatever was declared
|
||||
discovery: () => ({ properties: false, topLevel: { proactivelyReported: true } }),
|
||||
// alexa-doorbelleventsource.html, "Discovery"
|
||||
validate(capability, { displayCategories }) {
|
||||
const doorbell = displayCategories.indexOf("DOORBELL");
|
||||
if (doorbell < 0)
|
||||
throw new DeclarationError(capability, "a doorbell has the display category DOORBELL");
|
||||
if (displayCategories.indexOf("CAMERA") > doorbell) {
|
||||
throw new DeclarationError(capability, "a video doorbell lists the display category CAMERA before DOORBELL");
|
||||
}
|
||||
},
|
||||
});
|
||||
34
dist/esm/registry/interfaces/InventoryLevelSensor.d.ts
vendored
Normal file
34
dist/esm/registry/interfaces/InventoryLevelSensor.d.ts
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import type { Infer, Schema } from "../schema.js";
|
||||
declare const level: Schema<import("../schema.js").InferShape<{
|
||||
"@type": import("../schema.js").EnumSchema<"Count" | "Percentage" | "Volume" | "Weight">;
|
||||
value: Schema<number>;
|
||||
unit: import("../schema.js").OptionalSchema<string>;
|
||||
}>>;
|
||||
export type InventoryLevel = Infer<typeof level>;
|
||||
/**
|
||||
* How much is left of something a device uses up: ink, paper, detergent. Each instance is one sensor, and Amazon
|
||||
* orders a refill by its Dash replenishment id. The level is reported, there is nothing to ask for by voice.
|
||||
*/
|
||||
export declare const InventoryLevelSensor: import("../types.js").InterfaceDescriptor<{
|
||||
level: {
|
||||
name: string;
|
||||
value: Schema<import("../schema.js").InferShape<{
|
||||
"@type": import("../schema.js").EnumSchema<"Count" | "Percentage" | "Volume" | "Weight">;
|
||||
value: Schema<number>;
|
||||
unit: import("../schema.js").OptionalSchema<string>;
|
||||
}>>;
|
||||
note: string;
|
||||
};
|
||||
}, {}, import("../schema.js").InferShape<{
|
||||
/** How the level is measured: { "@type": "Volume", unit: "MILLILITER" }, { "@type": "Count" }. */
|
||||
measurement: Schema<import("../schema.js").InferShape<{
|
||||
"@type": import("../schema.js").EnumSchema<"Count" | "Percentage" | "Volume" | "Weight">;
|
||||
unit: import("../schema.js").OptionalSchema<string>;
|
||||
}>>;
|
||||
/** The id the Dash console gave for the product. */
|
||||
replenishment: Schema<import("../schema.js").InferShape<{
|
||||
"@type": import("../schema.js").EnumSchema<"DashReplenishmentId">;
|
||||
value: Schema<string>;
|
||||
}>>;
|
||||
}>, true>;
|
||||
export {};
|
||||
51
dist/esm/registry/interfaces/InventoryLevelSensor.js
vendored
Normal file
51
dist/esm/registry/interfaces/InventoryLevelSensor.js
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { at, mismatch, s } from "../schema.js";
|
||||
import { defineInterface } from "../types.js";
|
||||
// As the examples of the page spell them; its table has them in lower case
|
||||
const KINDS = ["Count", "Percentage", "Volume", "Weight"];
|
||||
// A volume and a weight have a unit: MILLILITER, GRAM (alexa-property-schemas.html, "Volume unit values" and
|
||||
// "Weight unit values")
|
||||
function withUnit(fields) {
|
||||
return {
|
||||
expects: fields.expects,
|
||||
parse(input, path = "") {
|
||||
const measured = fields.parse(input, path);
|
||||
const what = measured["@type"].toLowerCase();
|
||||
const needed = what === "volume" || what === "weight";
|
||||
if (needed && measured.unit === undefined)
|
||||
throw mismatch(at(path, "unit"), `the unit of the ${what}, like MILLILITER or GRAM`, undefined);
|
||||
if (!needed && measured.unit !== undefined)
|
||||
throw mismatch(at(path, "unit"), `no unit for a ${what}`, measured.unit);
|
||||
return measured;
|
||||
},
|
||||
};
|
||||
}
|
||||
const kind = s.enum(...KINDS);
|
||||
const measurement = withUnit(s.object({ "@type": kind, unit: s.optional(s.string({ min: 1 })) }, { unknownKeys: "reject" }));
|
||||
const level = withUnit(s.object({ "@type": kind, value: s.number({ min: 0 }), unit: s.optional(s.string({ min: 1 })) }));
|
||||
/**
|
||||
* How much is left of something a device uses up: ink, paper, detergent. Each instance is one sensor, and Amazon
|
||||
* orders a refill by its Dash replenishment id. The level is reported, there is nothing to ask for by voice.
|
||||
*/
|
||||
export const InventoryLevelSensor = defineInterface({
|
||||
namespace: "Alexa.InventoryLevelSensor",
|
||||
version: "3",
|
||||
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-inventorylevelsensor.html",
|
||||
kind: "sensor",
|
||||
tier: 2,
|
||||
instanced: true,
|
||||
properties: {
|
||||
level: { name: "level", value: level, note: "measured as the capability declares it" },
|
||||
},
|
||||
directives: {},
|
||||
options: s.object({
|
||||
/** How the level is measured: { "@type": "Volume", unit: "MILLILITER" }, { "@type": "Count" }. */
|
||||
measurement,
|
||||
/** The id the Dash console gave for the product. */
|
||||
replenishment: s.object({ "@type": s.enum("DashReplenishmentId"), value: s.string({ min: 1 }) }, { unknownKeys: "reject" }),
|
||||
}, { unknownKeys: "reject" }),
|
||||
// A capability declared the 1.x way has neither; check() says so
|
||||
discovery({ options: { measurement: measured, replenishment } }) {
|
||||
const configuration = { ...(measured && { measurement: measured }), ...(replenishment && { replenishment }) };
|
||||
return Object.keys(configuration).length > 0 ? { configuration } : {};
|
||||
},
|
||||
});
|
||||
21
dist/esm/registry/interfaces/SceneController.d.ts
vendored
Normal file
21
dist/esm/registry/interfaces/SceneController.d.ts
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* A scene: an endpoint that is not a device, but several devices set to a state each. Activate is answered with
|
||||
* ActivationStarted and Deactivate with DeactivationStarted; ctx.respond() sends them, with the time and
|
||||
* VOICE_INTERACTION as the cause. A scene reports nothing and has no Alexa.EndpointHealth.
|
||||
*
|
||||
* Alexa takes no scene with a lock, a garage door, a camera, a cooking appliance or a security device in it.
|
||||
*/
|
||||
export declare const SceneController: import("../types.js").InterfaceDescriptor<{}, {
|
||||
Activate: {
|
||||
name: string;
|
||||
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
|
||||
};
|
||||
Deactivate: {
|
||||
name: string;
|
||||
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
|
||||
when: ({ options }: import("../types.js").Declared<any>) => boolean;
|
||||
};
|
||||
}, import("../schema.js").InferShape<{
|
||||
/** The scene can be switched off again. */
|
||||
supportsDeactivation: import("../schema.js").OptionalSchema<boolean>;
|
||||
}>, false>;
|
||||
64
dist/esm/registry/interfaces/SceneController.js
vendored
Normal file
64
dist/esm/registry/interfaces/SceneController.js
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { LIMITS } from "../catalog.js";
|
||||
import { happened } from "../events.js";
|
||||
import { s } from "../schema.js";
|
||||
import { DeclarationError, defineInterface } from "../types.js";
|
||||
const started = (name) => ({ name, payload: happened("VOICE_INTERACTION"), topic: "response" });
|
||||
const events = {
|
||||
ActivationStarted: started("ActivationStarted"),
|
||||
DeactivationStarted: started("DeactivationStarted"),
|
||||
};
|
||||
/**
|
||||
* A scene: an endpoint that is not a device, but several devices set to a state each. Activate is answered with
|
||||
* ActivationStarted and Deactivate with DeactivationStarted; ctx.respond() sends them, with the time and
|
||||
* VOICE_INTERACTION as the cause. A scene reports nothing and has no Alexa.EndpointHealth.
|
||||
*
|
||||
* Alexa takes no scene with a lock, a garage door, a camera, a cooking appliance or a security device in it.
|
||||
*/
|
||||
export const SceneController = defineInterface({
|
||||
namespace: "Alexa.SceneController",
|
||||
version: "3",
|
||||
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-scenecontroller.html",
|
||||
kind: "controller",
|
||||
tier: 1,
|
||||
instanced: false,
|
||||
properties: {},
|
||||
directives: {
|
||||
Activate: { name: "Activate", payload: s.object({}) },
|
||||
Deactivate: {
|
||||
name: "Deactivate",
|
||||
payload: s.object({}),
|
||||
when: ({ options }) => options.supportsDeactivation !== false,
|
||||
},
|
||||
},
|
||||
events,
|
||||
options: s.object({
|
||||
/** The scene can be switched off again. */
|
||||
supportsDeactivation: s.optional(s.boolean()),
|
||||
}, { unknownKeys: "reject" }),
|
||||
// A scene declared without the option is announced as 1.5.2 announced every scene, which Alexa accepted: it can
|
||||
// be deactivated, and proactivelyReported is on the capability. The example of the page has supportsDeactivation
|
||||
// alone.
|
||||
discovery({ proactivelyReported, options: { supportsDeactivation } }) {
|
||||
const topLevel = supportsDeactivation === undefined ? { supportsDeactivation: true, proactivelyReported } : { supportsDeactivation };
|
||||
return { properties: false, topLevel };
|
||||
},
|
||||
// alexa-scenecontroller.html, "Discovery"
|
||||
validate(capability, endpoint) {
|
||||
const { friendlyName, description, displayCategories } = endpoint;
|
||||
if (!displayCategories.includes("SCENE_TRIGGER") && !displayCategories.includes("ACTIVITY_TRIGGER")) {
|
||||
throw new DeclarationError(capability, "a scene has the display category SCENE_TRIGGER, or ACTIVITY_TRIGGER when the order of its steps matters");
|
||||
}
|
||||
if (!/scene/i.test(description)) {
|
||||
throw new DeclarationError(capability, 'the description of a scene has the word "scene" in it, like "Party scene connected by Alex2Node"');
|
||||
}
|
||||
if (friendlyName.length > LIMITS.sceneFriendlyNameLength) {
|
||||
throw new DeclarationError(capability, `the name of a scene takes up to ${LIMITS.sceneFriendlyNameLength} characters, this one has ${friendlyName.length}`);
|
||||
}
|
||||
},
|
||||
responseFor(directive) {
|
||||
if (directive !== "Activate" && directive !== "Deactivate")
|
||||
return undefined;
|
||||
const { name, payload } = directive === "Activate" ? events.ActivationStarted : events.DeactivationStarted;
|
||||
return { namespace: "Alexa.SceneController", name, payload };
|
||||
},
|
||||
});
|
||||
15
dist/esm/registry/interfaces/SimpleEventSource.d.ts
vendored
Normal file
15
dist/esm/registry/interfaces/SimpleEventSource.d.ts
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* A button of a remote, or anything else that routines start on. Each instance is one button with the events it
|
||||
* has, a single and a double push; device.raise(SimpleEventSource, "Event", { id }, { instance }) says that one
|
||||
* happened. Alexa wants the instance to be unique among all endpoints, "preferably a version 4 UUID".
|
||||
*
|
||||
* The page folds its discovery example away. The capability is announced with the fields of its table and without a
|
||||
* properties object, as the interface has no property.
|
||||
*/
|
||||
export declare const SimpleEventSource: import("../types.js").InterfaceDescriptor<{}, {}, import("../schema.js").InferShape<{
|
||||
/** For a REMOTE the names are assets of the catalog: Alexa.Button.SinglePush, Alexa.Gesture.Tap. */
|
||||
supportedEvents: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||
id: import("../schema.js").Schema<string>;
|
||||
friendlyNames: import("../schema.js").Schema<import("../types.js").Label[]>;
|
||||
}>[]>;
|
||||
}>, true>;
|
||||
50
dist/esm/registry/interfaces/SimpleEventSource.js
vendored
Normal file
50
dist/esm/registry/interfaces/SimpleEventSource.js
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { timestamp } from "../events.js";
|
||||
import { labels, shownAs } from "../resources.js";
|
||||
import { s } from "../schema.js";
|
||||
import { DeclarationError, defineInterface } from "../types.js";
|
||||
/**
|
||||
* A button of a remote, or anything else that routines start on. Each instance is one button with the events it
|
||||
* has, a single and a double push; device.raise(SimpleEventSource, "Event", { id }, { instance }) says that one
|
||||
* happened. Alexa wants the instance to be unique among all endpoints, "preferably a version 4 UUID".
|
||||
*
|
||||
* The page folds its discovery example away. The capability is announced with the fields of its table and without a
|
||||
* properties object, as the interface has no property.
|
||||
*/
|
||||
export const SimpleEventSource = defineInterface({
|
||||
namespace: "Alexa.SimpleEventSource",
|
||||
version: "1.0",
|
||||
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-simpleeventsource.html",
|
||||
kind: "eventSource",
|
||||
tier: 2,
|
||||
instanced: true,
|
||||
properties: {},
|
||||
directives: {},
|
||||
events: {
|
||||
Event: {
|
||||
name: "Event",
|
||||
/** id: the id of one of the supportedEvents of the instance. */
|
||||
payload: s.object({ id: s.string({ min: 1 }), timestamp }),
|
||||
topic: "proactive",
|
||||
},
|
||||
},
|
||||
options: s.object({
|
||||
/** For a REMOTE the names are assets of the catalog: Alexa.Button.SinglePush, Alexa.Gesture.Tap. */
|
||||
supportedEvents: s.array(s.object({ id: s.string({ min: 1 }), friendlyNames: labels }, { unknownKeys: "reject" }), { min: 1 }),
|
||||
}, { unknownKeys: "reject" }),
|
||||
// A capability declared the 1.x way has no events; check() says so
|
||||
discovery: ({ options: { supportedEvents } }) => ({
|
||||
properties: false,
|
||||
...(supportedEvents && { configuration: { supportedEvents } }),
|
||||
}),
|
||||
validate(capability) {
|
||||
const { supportedEvents } = capability.options;
|
||||
const ids = supportedEvents.map(({ id }) => id);
|
||||
// "The first friendly name in the array must be unique per instance of the capability."
|
||||
const names = supportedEvents.map(({ friendlyNames: [first] }) => shownAs(first));
|
||||
const twice = (list) => list.find((entry, i) => list.indexOf(entry) !== i);
|
||||
if (twice(ids))
|
||||
throw new DeclarationError(capability, `the event ${twice(ids)} is listed twice`);
|
||||
if (twice(names))
|
||||
throw new DeclarationError(capability, `two events have ${twice(names)} as their first friendly name, the Alexa app shows an event by it`);
|
||||
},
|
||||
});
|
||||
27
dist/esm/registry/interfaces/TimeHoldController.d.ts
vendored
Normal file
27
dist/esm/registry/interfaces/TimeHoldController.d.ts
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Pausing what a device does, a microwave that heats, and going on with it. Amazon pairs the interface with
|
||||
* Alexa.Cooking. Alexa sends Resume to a device declared with allowRemoteResume only; for another one it asks the
|
||||
* user to press start on the device.
|
||||
*/
|
||||
export declare const TimeHoldController: import("../types.js").InterfaceDescriptor<{
|
||||
holdStartTime: {
|
||||
name: string;
|
||||
value: import("../schema.js").Schema<string>;
|
||||
};
|
||||
holdEndTime: {
|
||||
name: string;
|
||||
value: import("../schema.js").Schema<string>;
|
||||
};
|
||||
}, {
|
||||
Hold: {
|
||||
name: string;
|
||||
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
|
||||
};
|
||||
Resume: {
|
||||
name: string;
|
||||
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
|
||||
when: ({ options }: import("../types.js").Declared<any>) => boolean;
|
||||
};
|
||||
}, import("../schema.js").InferShape<{
|
||||
allowRemoteResume: import("../schema.js").Schema<boolean>;
|
||||
}>, false>;
|
||||
28
dist/esm/registry/interfaces/TimeHoldController.js
vendored
Normal file
28
dist/esm/registry/interfaces/TimeHoldController.js
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { s } from "../schema.js";
|
||||
import { defineInterface } from "../types.js";
|
||||
/**
|
||||
* Pausing what a device does, a microwave that heats, and going on with it. Amazon pairs the interface with
|
||||
* Alexa.Cooking. Alexa sends Resume to a device declared with allowRemoteResume only; for another one it asks the
|
||||
* user to press start on the device.
|
||||
*/
|
||||
export const TimeHoldController = defineInterface({
|
||||
namespace: "Alexa.TimeHoldController",
|
||||
version: "3",
|
||||
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-timeholdcontroller.html",
|
||||
kind: "controller",
|
||||
tier: 2,
|
||||
instanced: false,
|
||||
properties: {
|
||||
holdStartTime: { name: "holdStartTime", value: s.dateTime() },
|
||||
holdEndTime: { name: "holdEndTime", value: s.dateTime() },
|
||||
},
|
||||
directives: {
|
||||
Hold: { name: "Hold", payload: s.object({}) },
|
||||
Resume: { name: "Resume", payload: s.object({}), when: ({ options }) => options.allowRemoteResume !== false },
|
||||
},
|
||||
options: s.object({
|
||||
allowRemoteResume: s.boolean(),
|
||||
}, { unknownKeys: "reject" }),
|
||||
// A capability declared the 1.x way has no allowRemoteResume; check() says so
|
||||
discovery: ({ options: { allowRemoteResume } }) => (allowRemoteResume === undefined ? {} : { configuration: { allowRemoteResume } }),
|
||||
});
|
||||
10
dist/esm/registry/interfaces/WakeOnLANController.d.ts
vendored
Normal file
10
dist/esm/registry/interfaces/WakeOnLANController.d.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* A device that is switched on by a Wake-on-LAN message, which an Echo of the user sends to the MAC addresses of
|
||||
* the capability. The interface has no directive and no property: the device gets TurnOn of Alexa.PowerController,
|
||||
* answers with a DeferredResponse, sends the WakeUp event and, when it is up, the Response.
|
||||
*
|
||||
* Alex2MQTT has no topic yet that takes the WakeUp event to Alexa.
|
||||
*/
|
||||
export declare const WakeOnLANController: import("../types.js").InterfaceDescriptor<{}, {}, import("../schema.js").InferShape<{
|
||||
macAddresses: import("../schema.js").Schema<string[]>;
|
||||
}>, false>;
|
||||
42
dist/esm/registry/interfaces/WakeOnLANController.js
vendored
Normal file
42
dist/esm/registry/interfaces/WakeOnLANController.js
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { s } from "../schema.js";
|
||||
import { DeclarationError, defineInterface } from "../types.js";
|
||||
// 00-14-22-01-23-45 in the example of the page; the colon is the other common way to write one
|
||||
const MAC_ADDRESS = /^[0-9A-Fa-f]{2}([-:][0-9A-Fa-f]{2}){5}$/;
|
||||
/**
|
||||
* A device that is switched on by a Wake-on-LAN message, which an Echo of the user sends to the MAC addresses of
|
||||
* the capability. The interface has no directive and no property: the device gets TurnOn of Alexa.PowerController,
|
||||
* answers with a DeferredResponse, sends the WakeUp event and, when it is up, the Response.
|
||||
*
|
||||
* Alex2MQTT has no topic yet that takes the WakeUp event to Alexa.
|
||||
*/
|
||||
export const WakeOnLANController = defineInterface({
|
||||
namespace: "Alexa.WakeOnLANController",
|
||||
version: "3",
|
||||
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-wakeonlancontroller.html",
|
||||
kind: "controller",
|
||||
tier: 2,
|
||||
instanced: false,
|
||||
properties: {},
|
||||
directives: {},
|
||||
events: {
|
||||
WakeUp: { name: "WakeUp", payload: s.object({}), topic: "response" },
|
||||
},
|
||||
options: s.object({
|
||||
macAddresses: s.array(s.string({ pattern: MAC_ADDRESS, expects: "a MAC address like 00-14-22-01-23-45" }), { min: 1 }),
|
||||
}, { unknownKeys: "reject" }),
|
||||
// The example has a properties object without fields, which topLevel puts where properties: false left none. A
|
||||
// capability declared the 1.x way has no address; check() says so.
|
||||
discovery: ({ options: { macAddresses } }) => ({
|
||||
properties: false,
|
||||
topLevel: { properties: {} },
|
||||
...(macAddresses && { configuration: { MACAddresses: macAddresses } }),
|
||||
}),
|
||||
validate(capability) {
|
||||
const addresses = capability.options.macAddresses.map((address) => address.toUpperCase().replace(/:/g, "-"));
|
||||
const twice = addresses.find((address, i) => addresses.indexOf(address) !== i);
|
||||
if (twice)
|
||||
throw new DeclarationError(capability, `${twice} is listed twice`);
|
||||
},
|
||||
deferrable: true,
|
||||
defers: [{ namespace: "Alexa.PowerController", name: "TurnOn" }],
|
||||
});
|
||||
16
dist/esm/registry/interfaces/stubs.js
vendored
16
dist/esm/registry/interfaces/stubs.js
vendored
|
|
@ -29,8 +29,6 @@ const TABLE = [
|
|||
["Alexa.DataController", "1", [], "alexa-datacontroller.html"],
|
||||
["Alexa.DeviceUsage.Estimation", "1", [], "alexa-deviceusage-estimation.html"],
|
||||
["Alexa.DeviceUsage.Meter", "1", [], "alexa-deviceusage-meter.html"],
|
||||
["Alexa.DoorbellEventSource", "3", [], "alexa-doorbelleventsource.html"],
|
||||
["Alexa.InventoryLevelSensor", "1", [], "alexa-inventorylevelsensor.html"],
|
||||
["Alexa.InventoryLevelUsageSensor", "1", [], "alexa-inventorylevelusagesensor.html"],
|
||||
["Alexa.InventoryUsageSensor", "1", [], "alexa-inventoryusagesensor.html"],
|
||||
["Alexa.KeypadController", "1", [], "alexa-keypadcontroller.html"],
|
||||
|
|
@ -42,29 +40,16 @@ const TABLE = [
|
|||
["Alexa.RTCSessionController", "1", [], "alexa-rtcsessioncontroller.html"],
|
||||
["Alexa.RecordController", "3", [], "alexa-recordcontroller.html"],
|
||||
["Alexa.RemoteVideoPlayer", "1", [], "alexa-remotevideoplayer.html"],
|
||||
["Alexa.SceneController", "3", [], "alexa-scenecontroller.html"],
|
||||
["Alexa.SecurityPanelController.Alert", "1", [], "alexa-securitypanelcontroller-alert.html"],
|
||||
["Alexa.SeekController", "3", [], "alexa-seekcontroller.html"],
|
||||
["Alexa.SimpleEventSource", "1", [], "alexa-simpleeventsource.html"],
|
||||
["Alexa.SmartVision.ObjectDetectionSensor", "1", [], "alexa-smartvision-objectdetectionsensor.html"],
|
||||
["Alexa.SmartVision.SnapshotProvider", "1", [], "alexa-smartvision-snapshotprovider.html"],
|
||||
["Alexa.ThermostatController.Configuration", "1", [], "alexa-thermostatcontroller-configuration.html"],
|
||||
["Alexa.ThermostatController.HVAC.Components", "1", [], "alexa-thermostatcontroller-hvac-components.html"],
|
||||
// "UNKNOWN" in 1.5.2 as well; the page is titled "Interface 3"
|
||||
["Alexa.TimeHoldController", "3", [], "alexa-timeholdcontroller.html"],
|
||||
["Alexa.UIController", "1", [], "alexa-uicontroller.html"],
|
||||
["Alexa.UserPreference", "1", [], "alexa-userpreference.html"],
|
||||
["Alexa.VideoRecorder", "1", [], "alexa-videorecorder.html"],
|
||||
["Alexa.WakeOnLANController", "1", [], "alexa-wakeonlancontroller.html"],
|
||||
];
|
||||
// What 1.5.2 added to the capability object of one of them
|
||||
const EXTRAS = {
|
||||
// No properties object; supportsDeactivation and proactivelyReported on the capability itself
|
||||
"Alexa.SceneController": ({ proactivelyReported }) => ({
|
||||
properties: false,
|
||||
topLevel: { supportsDeactivation: true, proactivelyReported },
|
||||
}),
|
||||
};
|
||||
function kindOf(namespace) {
|
||||
if (namespace.endsWith("Sensor"))
|
||||
return "sensor";
|
||||
|
|
@ -82,7 +67,6 @@ function stub([namespace, version, properties, page]) {
|
|||
instanced: false,
|
||||
properties: Object.fromEntries(properties.map((name) => [name, { name, value: s.unknown() }])),
|
||||
directives: {},
|
||||
discovery: EXTRAS[namespace],
|
||||
};
|
||||
}
|
||||
export const STUBS = TABLE.map(stub);
|
||||
|
|
|
|||
3
dist/esm/registry/schema.d.ts
vendored
3
dist/esm/registry/schema.d.ts
vendored
|
|
@ -62,6 +62,8 @@ declare function enumeration<V extends readonly string[]>(...values: V): EnumSch
|
|||
declare function oneOf<V extends string>(values: readonly V[], expects: string): EnumSchema<V>;
|
||||
declare function unknown(): Schema<unknown>;
|
||||
declare function optional<T>(inner: Schema<T>): OptionalSchema<T>;
|
||||
/** A value that is made when none is given: the time of an event, which is now unless the caller says when. */
|
||||
declare function defaulted<T>(inner: Schema<T>, make: () => T): Schema<T>;
|
||||
declare function nullable<T>(inner: Schema<T>): Schema<T | null>;
|
||||
declare function array<T>(item: Schema<T>, rules?: {
|
||||
min?: number;
|
||||
|
|
@ -87,6 +89,7 @@ export declare const s: {
|
|||
oneOf: typeof oneOf;
|
||||
unknown: typeof unknown;
|
||||
optional: typeof optional;
|
||||
defaulted: typeof defaulted;
|
||||
nullable: typeof nullable;
|
||||
array: typeof array;
|
||||
object: typeof object;
|
||||
|
|
|
|||
8
dist/esm/registry/schema.js
vendored
8
dist/esm/registry/schema.js
vendored
|
|
@ -83,6 +83,13 @@ function optional(inner) {
|
|||
parse: (input, path = "") => (input === undefined ? undefined : inner.parse(input, path)),
|
||||
};
|
||||
}
|
||||
/** A value that is made when none is given: the time of an event, which is now unless the caller says when. */
|
||||
function defaulted(inner, make) {
|
||||
return {
|
||||
expects: inner.expects,
|
||||
parse: (input, path = "") => (input === undefined ? make() : inner.parse(input, path)),
|
||||
};
|
||||
}
|
||||
function nullable(inner) {
|
||||
const expects = `${inner.expects} or null`;
|
||||
return {
|
||||
|
|
@ -183,6 +190,7 @@ export const s = {
|
|||
oneOf,
|
||||
unknown,
|
||||
optional,
|
||||
defaulted,
|
||||
nullable,
|
||||
array,
|
||||
object,
|
||||
|
|
|
|||
8
dist/esm/registry/types.d.ts
vendored
8
dist/esm/registry/types.d.ts
vendored
|
|
@ -138,6 +138,14 @@ export interface InterfaceDescriptor<P extends Properties = Properties, D extend
|
|||
errorTypes?: readonly string[];
|
||||
/** Amazon documents a DeferredResponse for the interface. */
|
||||
deferrable?: boolean;
|
||||
/**
|
||||
* The directives of other interfaces that Amazon documents a DeferredResponse for on an endpoint with this one:
|
||||
* TurnOn of Alexa.PowerController, for a device that is woken over the LAN.
|
||||
*/
|
||||
defers?: ReadonlyArray<{
|
||||
namespace: string;
|
||||
name: string;
|
||||
}>;
|
||||
/** Rules a schema cannot state, for options that passed the schema. Throws DeclarationError. */
|
||||
validate?: (capability: Declared<O>, endpoint: EndpointView) => void;
|
||||
}
|
||||
|
|
|
|||
7
dist/esm/topics.d.ts
vendored
7
dist/esm/topics.d.ts
vendored
|
|
@ -19,3 +19,10 @@ export declare const deferred: (root: string, endpointId: string) => string;
|
|||
export declare const DIRECTIVE_BUDGET_MS = 7000;
|
||||
/** Where every ChangeReport of the root goes: the backend adds the user's token and posts it to Alexa. */
|
||||
export declare const changeReport: (root: string) => string;
|
||||
/**
|
||||
* Where the events go that a device raises by itself, a DoorbellPress: the backend adds the user's token and posts
|
||||
* them to Alexa. It takes 30 events a minute from a root.
|
||||
*/
|
||||
export declare const event: (root: string) => string;
|
||||
/** The backend drops an event that is longer, as JSON. */
|
||||
export declare const EVENT_BYTES = 16000;
|
||||
|
|
|
|||
7
dist/esm/topics.js
vendored
7
dist/esm/topics.js
vendored
|
|
@ -24,3 +24,10 @@ export const deferred = (root, endpointId) => `${root}/${endpointId}/deferredRes
|
|||
export const DIRECTIVE_BUDGET_MS = 7000;
|
||||
/** Where every ChangeReport of the root goes: the backend adds the user's token and posts it to Alexa. */
|
||||
export const changeReport = (root) => `${root}/changeReport`;
|
||||
/**
|
||||
* Where the events go that a device raises by itself, a DoorbellPress: the backend adds the user's token and posts
|
||||
* them to Alexa. It takes 30 events a minute from a root.
|
||||
*/
|
||||
export const event = (root) => `${root}/event`;
|
||||
/** The backend drops an event that is longer, as JSON. */
|
||||
export const EVENT_BYTES = 16000;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue