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>
317 lines
15 KiB
JavaScript
317 lines
15 KiB
JavaScript
"use strict";
|
|
// One entry per interface that is described in full: the page its examples were saved from (test/helpers/fixtures.js)
|
|
// and declarations that give capability objects of Amazon's discovery examples.
|
|
//
|
|
// namespace the interface
|
|
// page the page of the interface; test/fixtures/alexa-docs/<page>/ has its examples
|
|
// declared [{ options, example, page, n }]: device.add(descriptor, options) gives the n-th capability object of
|
|
// the interface in that discovery example. n counts from 0 and defaults to it, page defaults to the
|
|
// page of the interface. The pages of RangeController and ModeController fold their discovery
|
|
// examples away, so they were not saved: the range is taken from an example of the ToggleController
|
|
// page, and ModeController has none.
|
|
// refused [[directive, payload, message]]: a payload the directive does not take, and the message of the
|
|
// INVALID_VALUE that answers it
|
|
// unfit [[property, value]]: a value the property does not take
|
|
// chosen true: a declaration lists the properties the device has, an example some of those of the interface
|
|
// folded the directives whose example the page folds away, so that it was not saved
|
|
//
|
|
// A directive is answered with a Response, or with the event responseFor() of its descriptor names.
|
|
//
|
|
// test/registry/descriptors.test.js, test/device/discovery.test.js and test/dispatch/interfaces.test.js run the
|
|
// entries. The first one fails for a descriptor of tier 1 or 2 that has no entry.
|
|
const { asset, semantics, text } = require("alex2node");
|
|
|
|
const reported = { retrievable: true, proactivelyReported: true };
|
|
const declared = [{ options: reported, example: "discovery" }];
|
|
const inThreeLanguages = (english, spanish, french) => [text(english, "en-US"), text(spanish, "es-MX"), text(french, "fr-CA")];
|
|
|
|
module.exports = [
|
|
{ namespace: "Alexa", page: "alexa-interface", declared: [{ options: {}, example: "discovery" }] },
|
|
{
|
|
namespace: "Alexa.ChannelController",
|
|
page: "alexa-channelcontroller",
|
|
// The page folds its two discovery examples away; the one of its AddOrUpdateReport has the same endpoint
|
|
declared: [{ options: reported, example: "discovery-addOrUpdateReport" }],
|
|
refused: [
|
|
["ChangeChannel", { channel: { number: 9 } }, "payload.channel.number: expected a string, got 9"],
|
|
["ChangeChannel", { channel: "KCTS" }, 'payload.channel: expected an object with number, callSign, affiliateCallSign, uri, got "KCTS"'],
|
|
["SkipChannels", { channelCount: 1.5 }, "payload.channelCount: expected an integer, got 1.5"],
|
|
],
|
|
unfit: [["channel", "9"], ["channel", { number: 9 }]],
|
|
},
|
|
{ namespace: "Alexa.BrightnessController", page: "alexa-brightnesscontroller", declared: [{ options: reported, example: "discovery" }] },
|
|
{
|
|
namespace: "Alexa.ColorController",
|
|
page: "alexa-colorcontroller",
|
|
declared,
|
|
refused: [
|
|
["SetColor", { color: { hue: 361, saturation: 1, brightness: 1 } }, "payload.color.hue: expected a number from 0 to 360, got 361"],
|
|
["SetColor", { color: "red" }, 'payload.color: expected an object with hue, saturation, brightness, got "red"'],
|
|
],
|
|
unfit: [["color", { hue: 120, saturation: 50, brightness: 1 }], ["color", { hue: 120, saturation: 1 }]],
|
|
},
|
|
{
|
|
namespace: "Alexa.ColorTemperatureController",
|
|
page: "alexa-colortemperaturecontroller",
|
|
declared,
|
|
refused: [
|
|
["SetColorTemperature", { colorTemperatureInKelvin: 900 }, "payload.colorTemperatureInKelvin: expected an integer from 1000 to 10000, got 900"],
|
|
["IncreaseColorTemperature", "a lot", 'payload: expected an object, got "a lot"'],
|
|
],
|
|
unfit: [["colorTemperatureInKelvin", 10001], ["colorTemperatureInKelvin", "warm"]],
|
|
},
|
|
{ namespace: "Alexa.ContactSensor", page: "alexa-contactsensor", declared, unfit: [["detectionState", "OPEN"], ["detectionState", true]] },
|
|
{ namespace: "Alexa.EndpointHealth", page: "alexa-endpointhealth", declared: [{ options: reported, example: "discovery" }] },
|
|
{
|
|
namespace: "Alexa.EqualizerController",
|
|
page: "alexa-equalizercontroller",
|
|
declared: [{
|
|
example: "discovery",
|
|
options: {
|
|
...reported,
|
|
bands: { supported: ["BASS", "MIDRANGE", "TREBLE"], range: { minimum: -6, maximum: 6 } },
|
|
modes: ["MOVIE", "MUSIC", "SPORT"],
|
|
},
|
|
}],
|
|
refused: [
|
|
["SetMode", { mode: "OPERA" }, 'payload.mode: expected MOVIE | MUSIC | NIGHT | SPORT | TV, got "OPERA"'],
|
|
["SetBands", { bands: [{ name: "BASS", value: 1.5 }] }, "payload.bands[0].value: expected an integer, got 1.5"],
|
|
["AdjustBands", { bands: [{ name: "BASS", levelDelta: 3 }] }, "payload.bands[0].levelDirection: expected UP | DOWN, got nothing"],
|
|
["ResetBands", { bands: [] }, "payload.bands: expected a list with 1 or more entries, got []"],
|
|
],
|
|
unfit: [["mode", "OPERA"], ["bands", { name: "BASS", value: 0 }], ["bands", [{ name: "SUB", value: 0 }]]],
|
|
},
|
|
{ namespace: "Alexa.HumiditySensor", page: "alexa-humiditysensor", declared, unfit: [["relativeHumidity", 45], ["relativeHumidity", { value: 101 }]] },
|
|
{
|
|
namespace: "Alexa.InputController",
|
|
page: "alexa-inputcontroller",
|
|
// The two discovery examples of the page spell the friendlyNames of HDMI 1 "friendNames". The fixtures have the
|
|
// name of the table of the page.
|
|
declared: [{
|
|
example: "discovery",
|
|
options: {
|
|
...reported,
|
|
inputs: [{ name: "HDMI 1", friendlyNames: ["AnyCompany TV"] }, { name: "HDMI 2", friendlyNames: ["Cable", "AnyCompany Cable"] }],
|
|
},
|
|
}],
|
|
refused: [
|
|
["SelectInput", { input: "HDMI 11" }, 'payload.input: expected an input like HDMI 1, got "HDMI 11"'],
|
|
["SelectInput", {}, "payload.input: expected an input like HDMI 1, got nothing"],
|
|
],
|
|
unfit: [["input", "hdmi 1"], ["input", 1]],
|
|
},
|
|
{
|
|
namespace: "Alexa.LockController",
|
|
page: "alexa-lockcontroller",
|
|
declared,
|
|
refused: [["Lock", "now", 'payload: expected an object, got "now"']],
|
|
unfit: [["lockState", "OPEN"], ["lockState", "locked"]],
|
|
},
|
|
{ namespace: "Alexa.ModeController", page: "alexa-modecontroller", declared: [] },
|
|
{ namespace: "Alexa.MotionSensor", page: "alexa-motionsensor", declared, unfit: [["detectionState", "MOTION"], ["detectionState", 1]] },
|
|
{
|
|
namespace: "Alexa.PercentageController",
|
|
page: "alexa-percentagecontroller",
|
|
declared,
|
|
refused: [
|
|
["SetPercentage", { percentage: 101 }, "payload.percentage: expected an integer from 0 to 100, got 101"],
|
|
["AdjustPercentage", { percentageDelta: 0.5 }, "payload.percentageDelta: expected an integer from -100 to 100, got 0.5"],
|
|
],
|
|
unfit: [["percentage", -1], ["percentage", 50.5]],
|
|
},
|
|
{
|
|
namespace: "Alexa.PlaybackController",
|
|
page: "alexa-playbackcontroller",
|
|
declared: [{
|
|
example: "discovery",
|
|
options: { supportedOperations: ["Play", "Pause", "Stop", "Next", "Previous", "FastForward", "Rewind", "StartOver"] },
|
|
}],
|
|
refused: [["Play", "now", 'payload: expected an object, got "now"']],
|
|
},
|
|
{
|
|
namespace: "Alexa.PlaybackStateReporter",
|
|
page: "alexa-playbackcontroller",
|
|
declared,
|
|
unfit: [["playbackState", "PLAYING"], ["playbackState", { state: "REWINDING" }]],
|
|
},
|
|
{ namespace: "Alexa.PowerController", page: "alexa-powercontroller", declared: [{ options: reported, example: "discovery" }] },
|
|
{
|
|
namespace: "Alexa.PowerLevelController",
|
|
page: "alexa-powerlevelcontroller",
|
|
declared,
|
|
refused: [
|
|
["SetPowerLevel", { powerLevel: "half" }, 'payload.powerLevel: expected an integer from 0 to 100, got "half"'],
|
|
["AdjustPowerLevel", {}, "payload.powerLevelDelta: expected an integer from -100 to 100, got nothing"],
|
|
],
|
|
unfit: [["powerLevel", 101], ["powerLevel", null]],
|
|
},
|
|
{
|
|
namespace: "Alexa.RangeController",
|
|
page: "alexa-rangecontroller",
|
|
declared: [{
|
|
page: "alexa-togglecontroller",
|
|
example: "discovery-range-and-toggle",
|
|
options: {
|
|
...reported,
|
|
instance: "SampleManufacturer.Fan.Speed",
|
|
friendlyNames: [asset("Alexa.Setting.FanSpeed"), ...inThreeLanguages("Speed", "Velocidad", "Vitesse")],
|
|
range: { min: 1, max: 10, precision: 1 },
|
|
presets: [
|
|
{
|
|
value: 10,
|
|
friendlyNames: [
|
|
asset("Alexa.Value.Maximum"), asset("Alexa.Value.High"), text("Highest"), text("Fast"), text("Alta", "es-MX"),
|
|
text("Élevée", "fr-CA"),
|
|
],
|
|
},
|
|
{
|
|
value: 1,
|
|
friendlyNames: [
|
|
asset("Alexa.Value.Minimum"), asset("Alexa.Value.Low"), text("Lowest"), text("Slow"), text("Baja", "es-MX"),
|
|
text("Faible", "fr-CA"),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
}],
|
|
},
|
|
{
|
|
namespace: "Alexa.SecurityPanelController",
|
|
page: "alexa-securitypanelcontroller",
|
|
chosen: true,
|
|
declared: [{
|
|
example: "discovery",
|
|
options: {
|
|
...reported,
|
|
supportedArmStates: ["ARMED_AWAY", "ARMED_STAY", "DISARMED"],
|
|
supportedAuthorizationTypes: ["FOUR_DIGIT_PIN"],
|
|
alarms: ["burglaryAlarm", "fireAlarm"],
|
|
},
|
|
}],
|
|
refused: [
|
|
["Arm", { armState: "ARMED" }, 'payload.armState: expected ARMED_AWAY | ARMED_STAY | ARMED_NIGHT | DISARMED, got "ARMED"'],
|
|
["Arm", { armState: "ARMED_STAY", bypassType: "BYPASS_SOME" }, 'payload.bypassType: expected BYPASS_ALL, got "BYPASS_SOME"'],
|
|
["Disarm", { authorization: { type: "FOUR_DIGIT_PIN", value: "12345" } }, 'payload.authorization.value: expected a PIN of four digits, got "12345"'],
|
|
["Disarm", { authorization: { type: "VOICE", value: "1234" } }, 'payload.authorization.type: expected FOUR_DIGIT_PIN, got "VOICE"'],
|
|
],
|
|
unfit: [["armState", "ARMED"], ["burglaryAlarm", "ALARM"], ["fireAlarm", { value: "FIRE" }]],
|
|
},
|
|
{
|
|
namespace: "Alexa.Speaker",
|
|
page: "alexa-speaker",
|
|
declared,
|
|
refused: [
|
|
["SetVolume", { volume: 101 }, "payload.volume: expected an integer from 0 to 100, got 101"],
|
|
["AdjustVolume", { volume: -20 }, "payload.volumeDefault: expected true or false, got nothing"],
|
|
["SetMute", { mute: "yes" }, 'payload.mute: expected true or false, got "yes"'],
|
|
],
|
|
unfit: [["volume", 50.5], ["volume", -1], ["muted", "no"]],
|
|
},
|
|
{
|
|
namespace: "Alexa.StepSpeaker",
|
|
page: "alexa-stepspeaker",
|
|
declared: [{ options: {}, example: "discovery" }],
|
|
refused: [
|
|
["AdjustVolume", { volumeSteps: 101 }, "payload.volumeSteps: expected an integer from -100 to 100, got 101"],
|
|
["SetMute", {}, "payload.mute: expected true or false, got nothing"],
|
|
],
|
|
},
|
|
{ namespace: "Alexa.TemperatureSensor", page: "alexa-temperaturesensor", declared: [{ options: reported, example: "discovery" }] },
|
|
{
|
|
namespace: "Alexa.ThermostatController",
|
|
page: "alexa-thermostatcontroller",
|
|
chosen: true,
|
|
declared: [
|
|
{
|
|
example: "discovery",
|
|
options: {
|
|
...reported,
|
|
supportedModes: ["HEAT", "COOL", "AUTO", "EM_HEAT"],
|
|
supportsScheduling: true,
|
|
properties: ["lowerSetpoint", "upperSetpoint", "thermostatMode", "adaptiveRecoveryStatus"],
|
|
},
|
|
},
|
|
{
|
|
page: "alexa-thermostatcontroller-schedule",
|
|
example: "discovery",
|
|
options: { ...reported, supportedModes: ["HEAT", "COOL", "AUTO", "OFF"], supportsScheduling: true, properties: ["thermostatMode"] },
|
|
},
|
|
],
|
|
refused: [
|
|
["SetTargetTemperature", { targetSetpoint: { value: 20, scale: "RANKINE" } }, 'payload.targetSetpoint.scale: expected CELSIUS | FAHRENHEIT | KELVIN, got "RANKINE"'],
|
|
[
|
|
"SetTargetTemperature",
|
|
{ holdUntil: { duration: "PT30M" } },
|
|
'payload: expected one or more of targetSetpoint, lowerSetpoint, upperSetpoint, got {"holdUntil":{"duration":"PT30M"}}',
|
|
],
|
|
[
|
|
"SetTargetTemperature",
|
|
{ targetSetpoint: { value: 20, scale: "CELSIUS" }, holdUntil: { duration: "30 minutes" } },
|
|
'payload.holdUntil.duration: expected a duration like PT3M15S, got "30 minutes"',
|
|
],
|
|
["AdjustTargetTemperature", { targetSetpointDelta: 2 }, "payload.targetSetpointDelta: expected an object with value, scale, got 2"],
|
|
["SetThermostatMode", { thermostatMode: { value: "WARM" } }, 'payload.thermostatMode.value: expected AUTO | COOL | ECO | EM_HEAT | HEAT | OFF, got "WARM"'],
|
|
["SetThermostatMode", { thermostatMode: "HEAT" }, 'payload.thermostatMode: expected an object with value, got "HEAT"'],
|
|
],
|
|
unfit: [["thermostatMode", "EMERGENCY"], ["targetSetpoint", 20], ["upperSetpoint", { value: 20 }], ["adaptiveRecoveryStatus", "ACTIVE"]],
|
|
},
|
|
{
|
|
namespace: "Alexa.ThermostatController.Schedule",
|
|
page: "alexa-thermostatcontroller-schedule",
|
|
folded: ["SetWeeklySchedule"],
|
|
declared: [{
|
|
example: "discovery",
|
|
options: { ...reported, supportedFanModes: ["ON", "AUTO"], supportsAdaptiveRecovery: true, maxEntryPerDay: 4 },
|
|
}],
|
|
refused: [
|
|
["SetScheduleState", { scheduleEnabled: "yes" }, 'payload.scheduleEnabled: expected true or false, got "yes"'],
|
|
["SetAdaptiveRecovery", {}, "payload.adaptiveRecoveryEnabled: expected true or false, got nothing"],
|
|
["SetWeeklySchedule", { weeklySchedule: { temperatureScale: "CELSIUS" } }, "payload.weeklySchedule.Monday: expected a list, got nothing"],
|
|
],
|
|
unfit: [["scheduleEnabled", "ON"], ["adaptiveRecoveryEnabled", 1]],
|
|
},
|
|
{
|
|
namespace: "Alexa.ToggleController",
|
|
page: "alexa-togglecontroller",
|
|
declared: [
|
|
{
|
|
example: "discovery-oven",
|
|
options: {
|
|
...reported,
|
|
nonControllable: false,
|
|
instance: "Oven.OvenLight",
|
|
friendlyNames: inThreeLanguages("Oven light", "Luz del horno", "Lumière du four"),
|
|
},
|
|
},
|
|
{
|
|
example: "discovery-oven",
|
|
n: 1,
|
|
options: {
|
|
...reported,
|
|
nonControllable: true,
|
|
instance: "Stovetop.ResidualHeat",
|
|
friendlyNames: inThreeLanguages("Stovetop is still hot", "Todavía está caliente", "Encore chaude"),
|
|
},
|
|
},
|
|
{
|
|
example: "discovery-semantics",
|
|
options: {
|
|
...reported,
|
|
instance: "GarbageCan.Lid",
|
|
friendlyNames: inThreeLanguages("Garbage can lid", "Tapa del bote de basura", "Couvercle de poubelle"),
|
|
semantics: semantics().action("Close", "TurnOff", {}).action("Open", "TurnOn", {}).state("Closed", "OFF").state("Open", "ON"),
|
|
},
|
|
},
|
|
{
|
|
example: "discovery-range-and-toggle",
|
|
options: {
|
|
...reported,
|
|
instance: "SampleManufacturer.Fan.Oscillate",
|
|
friendlyNames: [
|
|
asset("Alexa.Setting.Oscillate"), text("Rotate"), text("Rotation"), text("Girar", "es-MX"), text("Rotation", "fr-CA"),
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
];
|