registry: Speaker, StepSpeaker, EqualizerController, PlaybackController, PlaybackStateReporter, InputController, ChannelController

Seven descriptors written from their pages replace seven stubs. All are version 3; 1.5.2 announced Speaker,
StepSpeaker, EqualizerController and PlaybackStateReporter as version 1, and all but ChannelController without
a property. StepSpeaker has no properties object in discovery and PlaybackController one without fields, next
to supportedOperations; a directive for an operation the device did not list is answered with
INVALID_DIRECTIVE. The equalizer declares bands, modes or both under "configurations", a TV its inputs from
the 61 names of the page.

Not described: the lineup of ChannelController, whose discovery example the page folds away. The discovery
examples of InputController spell one key "friendNames"; the fixtures have friendlyNames, as its table does.
62 examples of the six pages are saved as fixtures. 241 tests pass, 215 before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
David 2026-09-28 20:54:53 +00:00
parent c59cf3ff10
commit d7dfc5b122
113 changed files with 3948 additions and 47 deletions

6
dist/esm/index.d.ts vendored
View file

@ -5,11 +5,11 @@ 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, ColorController, ColorTemperatureController, ContactSensor, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerLevelController, RangeController, SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, } 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 { 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, 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, 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, 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 * 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";

4
dist/esm/index.js vendored
View file

@ -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, ColorController, ColorTemperatureController, ContactSensor, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerLevelController, RangeController, SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, } 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 { 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, THERMOSTAT_MODES as ThermostatModes, } 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";
// 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";

View file

@ -1,18 +1,25 @@
import { Alexa } from "./interfaces/Alexa.js";
import { BrightnessController } from "./interfaces/BrightnessController.js";
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 { EndpointHealth } from "./interfaces/EndpointHealth.js";
import { EqualizerController } from "./interfaces/EqualizerController.js";
import { HumiditySensor } from "./interfaces/HumiditySensor.js";
import { InputController } from "./interfaces/InputController.js";
import { LockController } from "./interfaces/LockController.js";
import { ModeController } from "./interfaces/ModeController.js";
import { MotionSensor } from "./interfaces/MotionSensor.js";
import { PercentageController } from "./interfaces/PercentageController.js";
import { PlaybackController } from "./interfaces/PlaybackController.js";
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 { SecurityPanelController } from "./interfaces/SecurityPanelController.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";
@ -26,7 +33,9 @@ export declare const registry: {
/** Every descriptor, ordered by namespace. */
list(): AnyDescriptor[];
};
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerController, PowerLevelController, RangeController, SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, };
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 { INPUTS } from "./interfaces/InputController.js";
export type { InputName } from "./interfaces/InputController.js";
export type { Mode } from "./interfaces/ModeController.js";
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
export type { ThermostatModeName } from "./interfaces/ThermostatController.js";

View file

@ -1,19 +1,26 @@
// The interfaces the library knows, by namespace.
import { Alexa } from "./interfaces/Alexa.js";
import { BrightnessController } from "./interfaces/BrightnessController.js";
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 { EndpointHealth } from "./interfaces/EndpointHealth.js";
import { EqualizerController } from "./interfaces/EqualizerController.js";
import { HumiditySensor } from "./interfaces/HumiditySensor.js";
import { InputController } from "./interfaces/InputController.js";
import { LockController } from "./interfaces/LockController.js";
import { ModeController } from "./interfaces/ModeController.js";
import { MotionSensor } from "./interfaces/MotionSensor.js";
import { PercentageController } from "./interfaces/PercentageController.js";
import { PlaybackController } from "./interfaces/PlaybackController.js";
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 { SecurityPanelController } from "./interfaces/SecurityPanelController.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";
@ -23,19 +30,26 @@ import { DeclarationError } from "./types.js";
const described = [
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,
@ -65,7 +79,8 @@ export const registry = {
return [...descriptors.values()].sort((a, b) => (a.namespace < b.namespace ? -1 : 1));
},
};
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerController, PowerLevelController, RangeController, SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, };
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 { INPUTS } from "./interfaces/InputController.js";
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
export { asset, text } from "./resources.js";
export { semantics, SemanticsBuilder } from "./semantics.js";

View file

@ -0,0 +1,41 @@
/**
* The channel of a TV. ChangeChannel names the channel by one or more of number, callSign, affiliateCallSign and
* uri, or by channelMetadata.name alone. The lineup of a TV provider is not described: the page folds the discovery
* example that has one.
*/
export declare const ChannelController: import("../types.js").InterfaceDescriptor<{
channel: {
name: string;
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
/** "256", "13.1" */
number: import("../schema.js").OptionalSchema<string>;
callSign: import("../schema.js").OptionalSchema<string>;
affiliateCallSign: import("../schema.js").OptionalSchema<string>;
uri: import("../schema.js").OptionalSchema<string>;
}>>;
};
}, {
ChangeChannel: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
channel: import("../schema.js").OptionalSchema<import("../schema.js").InferShape<{
/** "256", "13.1" */
number: import("../schema.js").OptionalSchema<string>;
callSign: import("../schema.js").OptionalSchema<string>;
affiliateCallSign: import("../schema.js").OptionalSchema<string>;
uri: import("../schema.js").OptionalSchema<string>;
}>>;
channelMetadata: import("../schema.js").OptionalSchema<import("../schema.js").InferShape<{
name: import("../schema.js").OptionalSchema<string>;
image: import("../schema.js").OptionalSchema<string>;
}>>;
}>>;
};
SkipChannels: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
channelCount: import("../schema.js").Schema<number>;
}>>;
note: string;
};
}, {}, false>;

View file

@ -0,0 +1,40 @@
import { s } from "../schema.js";
import { defineInterface } from "../types.js";
// None of the fields is required, a channel has one of them at least
const channel = s.object({
/** "256", "13.1" */
number: s.optional(s.string()),
callSign: s.optional(s.string()),
affiliateCallSign: s.optional(s.string()),
uri: s.optional(s.string()),
});
/**
* The channel of a TV. ChangeChannel names the channel by one or more of number, callSign, affiliateCallSign and
* uri, or by channelMetadata.name alone. The lineup of a TV provider is not described: the page folds the discovery
* example that has one.
*/
export const ChannelController = defineInterface({
namespace: "Alexa.ChannelController",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-channelcontroller.html",
kind: "controller",
tier: 2,
instanced: false,
properties: {
channel: { name: "channel", value: channel },
},
directives: {
ChangeChannel: {
name: "ChangeChannel",
payload: s.object({
channel: s.optional(channel),
channelMetadata: s.optional(s.object({ name: s.optional(s.string()), image: s.optional(s.string()) })),
}),
},
SkipChannels: {
name: "SkipChannels",
payload: s.object({ channelCount: s.number({ integer: true }) }),
note: "above 0 up, below 0 down",
},
},
});

View file

@ -0,0 +1,63 @@
/**
* The bands of an equalizer, its modes, or both. Discovery lists bands for a device declared with bands and mode
* for one declared with modes. A band or a mode the device does not have is answered with INVALID_VALUE.
*/
export declare const EqualizerController: import("../types.js").InterfaceDescriptor<{
bands: {
name: string;
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
name: import("../schema.js").EnumSchema<"BASS" | "MIDRANGE" | "TREBLE">;
value: import("../schema.js").Schema<number>;
}>[]>;
};
mode: {
name: string;
value: import("../schema.js").EnumSchema<"TV" | "MOVIE" | "MUSIC" | "NIGHT" | "SPORT">;
};
}, {
SetMode: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
mode: import("../schema.js").EnumSchema<"TV" | "MOVIE" | "MUSIC" | "NIGHT" | "SPORT">;
}>>;
};
SetBands: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
bands: import("../schema.js").Schema<import("../schema.js").InferShape<{
name: import("../schema.js").EnumSchema<"BASS" | "MIDRANGE" | "TREBLE">;
value: import("../schema.js").Schema<number>;
}>[]>;
}>>;
};
AdjustBands: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
bands: import("../schema.js").Schema<import("../schema.js").InferShape<{
name: import("../schema.js").EnumSchema<"BASS" | "MIDRANGE" | "TREBLE">;
levelDelta: import("../schema.js").OptionalSchema<number>;
levelDirection: import("../schema.js").EnumSchema<"UP" | "DOWN">;
}>[]>;
}>>;
note: string;
};
ResetBands: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
bands: import("../schema.js").Schema<import("../schema.js").InferShape<{
name: import("../schema.js").EnumSchema<"BASS" | "MIDRANGE" | "TREBLE">;
}>[]>;
}>>;
};
}, import("../schema.js").InferShape<{
/** The bands of the device and the levels they take. Left out for a device that has modes only. */
bands: import("../schema.js").OptionalSchema<import("../schema.js").InferShape<{
supported: import("../schema.js").Schema<("BASS" | "MIDRANGE" | "TREBLE")[]>;
range: import("../schema.js").Schema<import("../schema.js").InferShape<{
minimum: import("../schema.js").Schema<number>;
maximum: import("../schema.js").Schema<number>;
}>>;
}>>;
/** Left out for a device that has bands only. */
modes: import("../schema.js").OptionalSchema<("TV" | "MOVIE" | "MUSIC" | "NIGHT" | "SPORT")[]>;
}>, false>;

View file

@ -0,0 +1,69 @@
import { s } from "../schema.js";
import { DeclarationError, defineInterface } from "../types.js";
const BANDS = ["BASS", "MIDRANGE", "TREBLE"];
const MODES = ["MOVIE", "MUSIC", "NIGHT", "SPORT", "TV"];
const band = s.enum(...BANDS);
const level = s.number({ integer: true });
/**
* The bands of an equalizer, its modes, or both. Discovery lists bands for a device declared with bands and mode
* for one declared with modes. A band or a mode the device does not have is answered with INVALID_VALUE.
*/
export const EqualizerController = defineInterface({
namespace: "Alexa.EqualizerController",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-equalizercontroller.html",
kind: "controller",
tier: 2,
instanced: false,
properties: {
bands: { name: "bands", value: s.array(s.object({ name: band, value: level })) },
mode: { name: "mode", value: s.enum(...MODES) },
},
directives: {
SetMode: { name: "SetMode", payload: s.object({ mode: s.enum(...MODES) }) },
SetBands: { name: "SetBands", payload: s.object({ bands: s.array(s.object({ name: band, value: level }), { min: 1 }) }) },
AdjustBands: {
name: "AdjustBands",
payload: s.object({
bands: s.array(s.object({ name: band, levelDelta: s.optional(level), levelDirection: s.enum("UP", "DOWN") }), { min: 1 }),
}),
note: "without levelDelta the band moves by the step the device has for it",
},
ResetBands: { name: "ResetBands", payload: s.object({ bands: s.array(s.object({ name: band }), { min: 1 }) }) },
},
options: s.object({
/** The bands of the device and the levels they take. Left out for a device that has modes only. */
bands: s.optional(s.object({
supported: s.array(band, { min: 1 }),
range: s.object({ minimum: level, maximum: level }, { unknownKeys: "reject" }),
}, { unknownKeys: "reject" })),
/** Left out for a device that has bands only. */
modes: s.optional(s.array(s.enum(...MODES), { min: 1 })),
}, { unknownKeys: "reject" }),
// A capability declared the 1.x way has neither; check() says so, discovery lists both properties
discovery({ options: { bands, modes } }) {
if (!bands && !modes)
return {};
return {
supported: [...(bands ? ["bands"] : []), ...(modes ? ["mode"] : [])],
configurations: {
...(bands && { bands: { supported: bands.supported.map((name) => ({ name })), range: bands.range } }),
...(modes && { modes: { supported: modes.map((name) => ({ name })) } }),
},
};
},
validate(capability) {
const { bands, modes = [] } = capability.options;
if (!bands && modes.length === 0)
throw new DeclarationError(capability, "needs bands, modes or both: declare what the equalizer has");
if (bands && bands.range.maximum <= bands.range.minimum) {
throw new DeclarationError(capability, `bands.range.maximum ${bands.range.maximum} is not above bands.range.minimum ${bands.range.minimum}`);
}
const lists = [bands?.supported ?? [], modes];
for (const list of lists) {
const twice = list.find((entry, i) => list.indexOf(entry) !== i);
if (twice)
throw new DeclarationError(capability, `${twice} is listed twice`);
}
},
});

View file

@ -0,0 +1,26 @@
/** alexa-inputcontroller.html, "Input property values". */
export declare const INPUTS: readonly ["AUX 1", "AUX 2", "AUX 3", "AUX 4", "AUX 5", "AUX 6", "AUX 7", "BLURAY", "CABLE", "CD", "COAX 1", "COAX 2", "COMPOSITE 1", "DVD", "GAME", "HD RADIO", "HDMI 1", "HDMI 2", "HDMI 3", "HDMI 4", "HDMI 5", "HDMI 6", "HDMI 7", "HDMI 8", "HDMI 9", "HDMI 10", "HDMI ARC", "INPUT 1", "INPUT 2", "INPUT 3", "INPUT 4", "INPUT 5", "INPUT 6", "INPUT 7", "INPUT 8", "INPUT 9", "INPUT 10", "IPOD", "LINE 1", "LINE 2", "LINE 3", "LINE 4", "LINE 5", "LINE 6", "LINE 7", "MEDIA PLAYER", "OPTICAL 1", "OPTICAL 2", "PHONO", "PLAYSTATION", "PLAYSTATION 3", "PLAYSTATION 4", "SATELLITE", "SMARTCAST", "TUNER", "TV", "USB DAC", "VIDEO 1", "VIDEO 2", "VIDEO 3", "XBOX"];
export type InputName = (typeof INPUTS)[number];
/**
* The input a TV or a receiver shows. The inputs of the device are on the capability itself, as the discovery
* example of the page has them; its table calls them a configuration object.
*/
export declare const InputController: import("../types.js").InterfaceDescriptor<{
input: {
name: string;
value: import("../schema.js").EnumSchema<"TV" | "AUX 1" | "AUX 2" | "AUX 3" | "AUX 4" | "AUX 5" | "AUX 6" | "AUX 7" | "BLURAY" | "CABLE" | "CD" | "COAX 1" | "COAX 2" | "COMPOSITE 1" | "DVD" | "GAME" | "HD RADIO" | "HDMI 1" | "HDMI 2" | "HDMI 3" | "HDMI 4" | "HDMI 5" | "HDMI 6" | "HDMI 7" | "HDMI 8" | "HDMI 9" | "HDMI 10" | "HDMI ARC" | "INPUT 1" | "INPUT 2" | "INPUT 3" | "INPUT 4" | "INPUT 5" | "INPUT 6" | "INPUT 7" | "INPUT 8" | "INPUT 9" | "INPUT 10" | "IPOD" | "LINE 1" | "LINE 2" | "LINE 3" | "LINE 4" | "LINE 5" | "LINE 6" | "LINE 7" | "MEDIA PLAYER" | "OPTICAL 1" | "OPTICAL 2" | "PHONO" | "PLAYSTATION" | "PLAYSTATION 3" | "PLAYSTATION 4" | "SATELLITE" | "SMARTCAST" | "TUNER" | "USB DAC" | "VIDEO 1" | "VIDEO 2" | "VIDEO 3" | "XBOX">;
};
}, {
SelectInput: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
input: import("../schema.js").EnumSchema<"TV" | "AUX 1" | "AUX 2" | "AUX 3" | "AUX 4" | "AUX 5" | "AUX 6" | "AUX 7" | "BLURAY" | "CABLE" | "CD" | "COAX 1" | "COAX 2" | "COMPOSITE 1" | "DVD" | "GAME" | "HD RADIO" | "HDMI 1" | "HDMI 2" | "HDMI 3" | "HDMI 4" | "HDMI 5" | "HDMI 6" | "HDMI 7" | "HDMI 8" | "HDMI 9" | "HDMI 10" | "HDMI ARC" | "INPUT 1" | "INPUT 2" | "INPUT 3" | "INPUT 4" | "INPUT 5" | "INPUT 6" | "INPUT 7" | "INPUT 8" | "INPUT 9" | "INPUT 10" | "IPOD" | "LINE 1" | "LINE 2" | "LINE 3" | "LINE 4" | "LINE 5" | "LINE 6" | "LINE 7" | "MEDIA PLAYER" | "OPTICAL 1" | "OPTICAL 2" | "PHONO" | "PLAYSTATION" | "PLAYSTATION 3" | "PLAYSTATION 4" | "SATELLITE" | "SMARTCAST" | "TUNER" | "USB DAC" | "VIDEO 1" | "VIDEO 2" | "VIDEO 3" | "XBOX">;
}>>;
};
}, import("../schema.js").InferShape<{
/** friendlyNames: what the user calls the input, "Cable" for HDMI 2. */
inputs: import("../schema.js").Schema<import("../schema.js").InferShape<{
name: import("../schema.js").EnumSchema<"TV" | "AUX 1" | "AUX 2" | "AUX 3" | "AUX 4" | "AUX 5" | "AUX 6" | "AUX 7" | "BLURAY" | "CABLE" | "CD" | "COAX 1" | "COAX 2" | "COMPOSITE 1" | "DVD" | "GAME" | "HD RADIO" | "HDMI 1" | "HDMI 2" | "HDMI 3" | "HDMI 4" | "HDMI 5" | "HDMI 6" | "HDMI 7" | "HDMI 8" | "HDMI 9" | "HDMI 10" | "HDMI ARC" | "INPUT 1" | "INPUT 2" | "INPUT 3" | "INPUT 4" | "INPUT 5" | "INPUT 6" | "INPUT 7" | "INPUT 8" | "INPUT 9" | "INPUT 10" | "IPOD" | "LINE 1" | "LINE 2" | "LINE 3" | "LINE 4" | "LINE 5" | "LINE 6" | "LINE 7" | "MEDIA PLAYER" | "OPTICAL 1" | "OPTICAL 2" | "PHONO" | "PLAYSTATION" | "PLAYSTATION 3" | "PLAYSTATION 4" | "SATELLITE" | "SMARTCAST" | "TUNER" | "USB DAC" | "VIDEO 1" | "VIDEO 2" | "VIDEO 3" | "XBOX">;
friendlyNames: import("../schema.js").OptionalSchema<string[]>;
}>[]>;
}>, false>;

View file

@ -0,0 +1,46 @@
import { s } from "../schema.js";
import { DeclarationError, defineInterface } from "../types.js";
/** alexa-inputcontroller.html, "Input property values". */
export const INPUTS = [
"AUX 1", "AUX 2", "AUX 3", "AUX 4", "AUX 5", "AUX 6", "AUX 7", "BLURAY", "CABLE", "CD", "COAX 1", "COAX 2",
"COMPOSITE 1", "DVD", "GAME", "HD RADIO", "HDMI 1", "HDMI 2", "HDMI 3", "HDMI 4", "HDMI 5", "HDMI 6", "HDMI 7",
"HDMI 8", "HDMI 9", "HDMI 10", "HDMI ARC", "INPUT 1", "INPUT 2", "INPUT 3", "INPUT 4", "INPUT 5", "INPUT 6",
"INPUT 7", "INPUT 8", "INPUT 9", "INPUT 10", "IPOD", "LINE 1", "LINE 2", "LINE 3", "LINE 4", "LINE 5", "LINE 6",
"LINE 7", "MEDIA PLAYER", "OPTICAL 1", "OPTICAL 2", "PHONO", "PLAYSTATION", "PLAYSTATION 3", "PLAYSTATION 4",
"SATELLITE", "SMARTCAST", "TUNER", "TV", "USB DAC", "VIDEO 1", "VIDEO 2", "VIDEO 3", "XBOX",
];
const input = s.oneOf(INPUTS, "an input like HDMI 1");
/**
* The input a TV or a receiver shows. The inputs of the device are on the capability itself, as the discovery
* example of the page has them; its table calls them a configuration object.
*/
export const InputController = defineInterface({
namespace: "Alexa.InputController",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-inputcontroller.html",
kind: "controller",
tier: 2,
instanced: false,
properties: {
input: { name: "input", value: input },
},
directives: {
SelectInput: { name: "SelectInput", payload: s.object({ input }) },
},
options: s.object({
/** friendlyNames: what the user calls the input, "Cable" for HDMI 2. */
inputs: s.array(s.object({ name: input, friendlyNames: s.optional(s.array(s.string({ min: 1 }))) }, { unknownKeys: "reject" }), { min: 1 }),
}, { unknownKeys: "reject" }),
// A capability declared the 1.x way has no inputs; check() says so
discovery: ({ options: { inputs } }) => (inputs ? { topLevel: { inputs } } : {}),
validate(capability) {
const names = capability.options.inputs.map(({ name }) => name);
const called = capability.options.inputs.flatMap(({ friendlyNames = [] }) => friendlyNames);
const lists = [names, called];
for (const list of lists) {
const twice = list.find((entry, i) => list.indexOf(entry) !== i);
if (twice)
throw new DeclarationError(capability, `${twice} is listed twice, a name stands for one input`);
}
},
});

View file

@ -0,0 +1,57 @@
import type { Declared } from "../types.js";
declare const OPERATIONS: readonly ["Play", "Pause", "Stop", "Next", "Previous", "FastForward", "Rewind", "StartOver"];
type Operation = (typeof OPERATIONS)[number];
interface Options {
supportedOperations?: Operation[];
}
/**
* Play, pause and the other buttons of a player. The interface reports nothing: what plays is the playbackState of
* Alexa.PlaybackStateReporter, which a Response to these directives has in its context. Errors of a video device go
* under Alexa.Video, the others under Alexa.
*/
export declare const PlaybackController: import("../types.js").InterfaceDescriptor<{}, {
Play: {
name: "Play";
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
when: ({ options }: Declared<Options>) => boolean;
};
Pause: {
name: "Pause";
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
when: ({ options }: Declared<Options>) => boolean;
};
Stop: {
name: "Stop";
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
when: ({ options }: Declared<Options>) => boolean;
};
Next: {
name: "Next";
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
when: ({ options }: Declared<Options>) => boolean;
};
Previous: {
name: "Previous";
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
when: ({ options }: Declared<Options>) => boolean;
};
FastForward: {
name: "FastForward";
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
when: ({ options }: Declared<Options>) => boolean;
};
Rewind: {
name: "Rewind";
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
when: ({ options }: Declared<Options>) => boolean;
};
StartOver: {
name: "StartOver";
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{}>>;
when: ({ options }: Declared<Options>) => boolean;
};
}, import("../schema.js").InferShape<{
/** Default: all eight. */
supportedOperations: import("../schema.js").OptionalSchema<("Play" | "Pause" | "Stop" | "Next" | "Previous" | "FastForward" | "Rewind" | "StartOver")[]>;
}>, false>;
export {};

View file

@ -0,0 +1,43 @@
import { s } from "../schema.js";
import { defineInterface } from "../types.js";
// In the order of the discovery example
const OPERATIONS = ["Play", "Pause", "Stop", "Next", "Previous", "FastForward", "Rewind", "StartOver"];
// Alexa sends the operations the capability lists
const operation = (name) => ({
name,
payload: s.object({}),
when: ({ options }) => (options.supportedOperations ?? OPERATIONS).includes(name),
});
/**
* Play, pause and the other buttons of a player. The interface reports nothing: what plays is the playbackState of
* Alexa.PlaybackStateReporter, which a Response to these directives has in its context. Errors of a video device go
* under Alexa.Video, the others under Alexa.
*/
export const PlaybackController = defineInterface({
namespace: "Alexa.PlaybackController",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-playbackcontroller.html",
kind: "controller",
tier: 2,
instanced: false,
properties: {},
directives: {
Play: operation("Play"),
Pause: operation("Pause"),
Stop: operation("Stop"),
Next: operation("Next"),
Previous: operation("Previous"),
FastForward: operation("FastForward"),
Rewind: operation("Rewind"),
StartOver: operation("StartOver"),
},
options: s.object({
/** Default: all eight. */
supportedOperations: s.optional(s.array(s.enum(...OPERATIONS), { min: 1 })),
}, { unknownKeys: "reject" }),
// The example has a properties object without fields, which topLevel puts where properties: false left none
discovery: ({ options: { supportedOperations = [...OPERATIONS] } }) => ({
properties: false,
topLevel: { properties: {}, supportedOperations },
}),
});

View file

@ -0,0 +1,9 @@
/** What a player is doing, for an endpoint with Alexa.PlaybackController. Documented on the page of that interface. */
export declare const PlaybackStateReporter: import("../types.js").InterfaceDescriptor<{
playbackState: {
name: string;
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
state: import("../schema.js").EnumSchema<"PLAYING" | "PAUSED" | "STOPPED">;
}>>;
};
}, {}, {}, false>;

View file

@ -0,0 +1,15 @@
import { s } from "../schema.js";
import { defineInterface } from "../types.js";
/** What a player is doing, for an endpoint with Alexa.PlaybackController. Documented on the page of that interface. */
export const PlaybackStateReporter = defineInterface({
namespace: "Alexa.PlaybackStateReporter",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-playbackcontroller.html",
kind: "sensor",
tier: 2,
instanced: false,
properties: {
playbackState: { name: "playbackState", value: s.object({ state: s.enum("PLAYING", "PAUSED", "STOPPED") }) },
},
directives: {},
});

View file

@ -0,0 +1,32 @@
/** A speaker that can be set to a volume from 0 to 100. One that only takes steps is an Alexa.StepSpeaker. */
export declare const Speaker: import("../types.js").InterfaceDescriptor<{
volume: {
name: string;
value: import("../schema.js").Schema<number>;
};
muted: {
name: string;
value: import("../schema.js").Schema<boolean>;
};
}, {
SetVolume: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
volume: import("../schema.js").Schema<number>;
}>>;
};
AdjustVolume: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
volume: import("../schema.js").Schema<number>;
volumeDefault: import("../schema.js").Schema<boolean>;
}>>;
note: string;
};
SetMute: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
mute: import("../schema.js").Schema<boolean>;
}>>;
};
}, {}, false>;

24
dist/esm/registry/interfaces/Speaker.js vendored Normal file
View file

@ -0,0 +1,24 @@
import { s } from "../schema.js";
import { defineInterface } from "../types.js";
/** A speaker that can be set to a volume from 0 to 100. One that only takes steps is an Alexa.StepSpeaker. */
export const Speaker = defineInterface({
namespace: "Alexa.Speaker",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-speaker.html",
kind: "controller",
tier: 2,
instanced: false,
properties: {
volume: { name: "volume", value: s.number({ min: 0, max: 100, integer: true }) },
muted: { name: "muted", value: s.boolean() },
},
directives: {
SetVolume: { name: "SetVolume", payload: s.object({ volume: s.number({ min: 0, max: 100, integer: true }) }) },
AdjustVolume: {
name: "AdjustVolume",
payload: s.object({ volume: s.number({ min: -100, max: 100, integer: true }), volumeDefault: s.boolean() }),
note: "volume is the change. volumeDefault is true when the user named no amount",
},
SetMute: { name: "SetMute", payload: s.object({ mute: s.boolean() }) },
},
});

View file

@ -0,0 +1,19 @@
/**
* A speaker that is turned up and down by steps and does not know its volume. It reports nothing: the capability
* has no properties object in discovery.
*/
export declare const StepSpeaker: import("../types.js").InterfaceDescriptor<{}, {
AdjustVolume: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
volumeSteps: import("../schema.js").Schema<number>;
}>>;
note: string;
};
SetMute: {
name: string;
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
mute: import("../schema.js").Schema<boolean>;
}>>;
};
}, {}, false>;

View file

@ -0,0 +1,24 @@
import { s } from "../schema.js";
import { defineInterface } from "../types.js";
/**
* A speaker that is turned up and down by steps and does not know its volume. It reports nothing: the capability
* has no properties object in discovery.
*/
export const StepSpeaker = defineInterface({
namespace: "Alexa.StepSpeaker",
version: "3",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-stepspeaker.html",
kind: "controller",
tier: 2,
instanced: false,
properties: {},
directives: {
AdjustVolume: {
name: "AdjustVolume",
payload: s.object({ volumeSteps: s.number({ min: -100, max: 100, integer: true }) }),
note: "above 0 louder, below 0 quieter",
},
SetMute: { name: "SetMute", payload: s.object({ mute: s.boolean() }) },
},
discovery: () => ({ properties: false }),
});

View file

@ -1,7 +1,7 @@
// Interfaces without a descriptor of their own. A stub names the interface with the version and the properties
// alex2node 1.5.2 put in discovery, so an endpoint that declares one is announced as before. Nothing about it is
// checked, and the version is the one 1.5.2 sent, which is not always the one on the page of the interface
// (Alexa.Speaker is "3" there). A row leaves this table when a descriptor is written from that page.
// (Alexa.Cooking is "3" there). A row leaves this table when a descriptor is written from that page.
import { s } from "../schema.js";
const DEVICE_APIS = "https://developer.amazon.com/docs/alexaplus/device-apis";
const LIST_OF_INTERFACES = `${DEVICE_APIS}/list-of-interfaces.html`;
@ -17,7 +17,6 @@ const TABLE = [
// 1.5.2 announced the version "UNKNOWN"; 1.7 is what the list of interfaces gives
["Alexa.Camera.LiveViewController", "1.7", [], LIST_OF_INTERFACES],
["Alexa.CameraStreamController", "3", [], "alexa-camerastreamcontroller.html"],
["Alexa.ChannelController", "3", ["channel"], "alexa-channelcontroller.html"],
["Alexa.Commissionable", "1", [], "alexa-commissionable.html"],
["Alexa.ConsentManagement.ConsentRequiredReporter", "1", [], "alexa-consentrequiredreporter.html"],
["Alexa.Cooking", "1", [], "alexa-cooking.html"],
@ -31,8 +30,6 @@ const TABLE = [
["Alexa.DeviceUsage.Estimation", "1", [], "alexa-deviceusage-estimation.html"],
["Alexa.DeviceUsage.Meter", "1", [], "alexa-deviceusage-meter.html"],
["Alexa.DoorbellEventSource", "3", [], "alexa-doorbelleventsource.html"],
["Alexa.EqualizerController", "1", [], "alexa-equalizercontroller.html"],
["Alexa.InputController", "3", [], "alexa-inputcontroller.html"],
["Alexa.InventoryLevelSensor", "1", [], "alexa-inventorylevelsensor.html"],
["Alexa.InventoryLevelUsageSensor", "1", [], "alexa-inventorylevelusagesensor.html"],
["Alexa.InventoryUsageSensor", "1", [], "alexa-inventoryusagesensor.html"],
@ -41,8 +38,6 @@ const TABLE = [
["Alexa.Media.PlayQueue", "1", [], "alexa-media-playqueue.html"],
["Alexa.Media.Playback", "1", [], "alexa-media-playback.html"],
["Alexa.Media.Search", "1", [], "alexa-media-search.html"],
["Alexa.PlaybackController", "3", [], "alexa-playbackcontroller.html"],
["Alexa.PlaybackStateReporter", "1", [], "alexa-playbackcontroller.html"],
["Alexa.ProactiveNotificationSource", "1", [], "alexa-proactivenotificationsource.html"],
["Alexa.RTCSessionController", "1", [], "alexa-rtcsessioncontroller.html"],
["Alexa.RecordController", "3", [], "alexa-recordcontroller.html"],
@ -53,8 +48,6 @@ const TABLE = [
["Alexa.SimpleEventSource", "1", [], "alexa-simpleeventsource.html"],
["Alexa.SmartVision.ObjectDetectionSensor", "1", [], "alexa-smartvision-objectdetectionsensor.html"],
["Alexa.SmartVision.SnapshotProvider", "1", [], "alexa-smartvision-snapshotprovider.html"],
["Alexa.Speaker", "1", [], "alexa-speaker.html"],
["Alexa.StepSpeaker", "1", [], "alexa-stepspeaker.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"