registry: RangeController, ModeController, ToggleController with resources, presets and semantics

The three generic controllers are described in full and leave the stub table (62 stubs remain). A declaration
takes an instance name, friendly names from text() and asset(), and the options of the interface: range, unit and
presets for a range; supportedModes and ordered for a mode; semantics() for all three, with actions mapped to
directives and states mapped to a value or a range of values.

Refused at device.add(), each with the endpoint, interface and instance in the message: no instance name or no
friendly name; an instance declared twice; a first friendly name or a phrase used by another capability of the
endpoint; a reserved word, an asset id that is not in the catalog; range.max not above range.min; a preset off the
precision grid or outside the range; fewer than two modes or a mode listed twice; a mapping to a directive the
interface does not have, to a payload that does not fit, to a mode that is not listed, to AdjustMode on modes
that are not ordered; SetEcoOn, SetEcoOff, EcoOn and EcoOff on anything but a toggle.

For 1.x callers: Alexa.RangeController lists rangeValue (1.5.2 sent supported: []); addSupportedModes() announces
a mode given as a string as { value } and notes it, and takes { ordered: false } as a second argument, the default
stays true as 1.5.2 sent it (device.add() defaults to false). Declared the 1.x way, the zoo fan and curtain give
the same JSON as before.

Tests: the zoo blind, whose range, unit and semantics were written by hand into the JSON of 1.5.2, is declared
with device.add() and equals that JSON plus the Alexa capability; five capability objects of Amazon's examples
are reproduced the same way. 27 examples of four pages added. npm test: 108 pass (was 85) in 10.5-11.3 s, also on
Node 18.20.8 and 20.20.2.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
David 2026-09-28 15:50:25 +00:00
parent c492ef74d1
commit 2558e21787
80 changed files with 3131 additions and 107 deletions

View file

@ -1,20 +1,101 @@
"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 those examples.
// 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, as: [example, 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)
// 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.
//
// test/registry/descriptors.test.js and test/device/discovery.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 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: {}, as: ["discovery"] }] },
{ namespace: "Alexa.BrightnessController", page: "alexa-brightnesscontroller", declared: [{ options: reported, as: ["discovery"] }] },
{ namespace: "Alexa.EndpointHealth", page: "alexa-endpointhealth", declared: [{ options: reported, as: ["discovery"] }] },
{ namespace: "Alexa.PowerController", page: "alexa-powercontroller", declared: [{ options: reported, as: ["discovery"] }] },
{ namespace: "Alexa.TemperatureSensor", page: "alexa-temperaturesensor", declared: [{ options: reported, as: ["discovery"] }] },
{ namespace: "Alexa", page: "alexa-interface", declared: [{ options: {}, example: "discovery" }] },
{ namespace: "Alexa.BrightnessController", page: "alexa-brightnesscontroller", declared: [{ options: reported, example: "discovery" }] },
{ namespace: "Alexa.EndpointHealth", page: "alexa-endpointhealth", declared: [{ options: reported, example: "discovery" }] },
{ namespace: "Alexa.ModeController", page: "alexa-modecontroller", declared: [] },
{ namespace: "Alexa.PowerController", page: "alexa-powercontroller", declared: [{ options: reported, example: "discovery" }] },
{
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.TemperatureSensor", page: "alexa-temperaturesensor", declared: [{ options: reported, example: "discovery" }] },
{
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"),
],
},
},
],
},
];