From dd071548bca490d4e86be40728651ef90c13b377 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 28 Sep 2026 22:55:23 +0000 Subject: [PATCH] 2.0.0 The interface registry, generated and checked discovery, typed dispatch with automatic error answers, message builders, proactive events and typed helpers for every error type; the 1.x API is kept and the examples of 1.5.2 run unchanged. docs/wire-changes.md lists what a 1.x caller can observe. On 2026-09-28 a real Alexa account drove 33 test devices on this API through the public Alex2MQTT service: Alexa accepted the discovery of all of them, 50 of 58 cases passed, 4 had no action in the control call and 4 failed on what the check could see, none on an answer of the library. The readme table names the interfaces that run covered. Voice commands were not tested yet. 307 tests. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 2 +- examples/README.md | 4 ++-- examples/doorbell.js | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- readme.md | 37 ++++++++++++++++++++----------------- scripts/capability-table.js | 19 ++++++++++++------- test/readme.test.js | 10 ++++++---- 8 files changed, 46 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73091a1..514d95b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ The format is that of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 2.0.0 - unreleased +## [2.0.0] - 2026-09-28 Every call of 1.5.2 is kept. What a 1.x program can observe is under "Changed", and [docs/wire-changes.md](docs/wire-changes.md) has each row with the test that pins it. diff --git a/examples/README.md b/examples/README.md index a4073ef..0fc7ccd 100644 --- a/examples/README.md +++ b/examples/README.md @@ -43,8 +43,8 @@ node examples/testing/lamp.test.js ## What was tested `test/examples.test.js` starts every file with `node`, against a broker on 127.0.0.1, discovers the device, sends -it directives and checks the answers. The examples were not run against an Alexa account. `doorbell.js` publishes -to `/event`, and whether a press reaches Alexa depends on the Alex2MQTT service relaying that topic. +it directives and checks the answers. These files were not run against an Alexa account; test devices that make the +same calls were, on 2026-09-28, the doorbell's `raise()` among them (the readme has the list). ## 1.x diff --git a/examples/doorbell.js b/examples/doorbell.js index ea37370..f879989 100644 --- a/examples/doorbell.js +++ b/examples/doorbell.js @@ -1,8 +1,8 @@ // A doorbell: Alexa.DoorbellEventSource. It takes no directive and reports no state of the button; // device.raise() says that somebody rang. Press Enter to ring. // -// The event is published to /event. Not tested with an Alexa account yet: whether the press reaches Alexa -// depends on the Alex2MQTT service relaying that topic. Alexa wants 30 seconds between two presses of one doorbell. +// The event is published to /event, which the Alex2MQTT service passes on to Alexa. Alexa wants 30 seconds +// between two presses of one doorbell. // // ALEX2MQTT_USERNAME=... ALEX2MQTT_PASSWORD=... ALEX2MQTT_ROOT_TOPIC=... node examples/doorbell.js // diff --git a/package-lock.json b/package-lock.json index 3c2ec5f..bb6e406 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "alex2node", - "version": "1.5.2", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "alex2node", - "version": "1.5.2", + "version": "2.0.0", "license": "MIT", "dependencies": { "mqtt": "^5.10.3" diff --git a/package.json b/package.json index 3323133..d0d6b0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "alex2node", - "version": "1.5.2", + "version": "2.0.0", "description": "Alexa smart home devices in Node.js over MQTT, through the Alex2MQTT service: declare a device, answer its directives, report its state.", "type": "commonjs", "main": "./dist/cjs/index.js", diff --git a/readme.md b/readme.md index 07b6a0d..775ebb1 100644 --- a/readme.md +++ b/readme.md @@ -218,18 +218,18 @@ announced with its version, nothing about it is checked, and a handler gets the | Interface | Version | Properties | Directives | Events | Tier | Through Alexa | | --- | --- | --- | --- | --- | --- | --- | | [Alexa](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-interface.html) | 3 | - | `ReportState` | - | 1 | ReportState | -| [Alexa.BrightnessController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-brightnesscontroller.html) | 3 | `brightness` | `SetBrightness`, `AdjustBrightness` | - | 1 | directives | +| [Alexa.BrightnessController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-brightnesscontroller.html) | 3 | `brightness` | `SetBrightness`, `AdjustBrightness` | - | 1 | directives, change report | | [Alexa.ChannelController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-channelcontroller.html) | 3 | `channel` | `ChangeChannel`, `SkipChannels` | - | 2 | - | | [Alexa.ColorController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-colorcontroller.html) | 3 | `color` | `SetColor` | - | 1 | directives | | [Alexa.ColorTemperatureController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-colortemperaturecontroller.html) | 3 | `colorTemperatureInKelvin` | `SetColorTemperature`, `IncreaseColorTemperature`, `DecreaseColorTemperature` | - | 1 | directives | | [Alexa.ContactSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-contactsensor.html) | 3 | `detectionState` | - | - | 1 | change report | -| [Alexa.DoorbellEventSource](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-doorbelleventsource.html) | 3 | - | - | `DoorbellPress` | 2 | - | +| [Alexa.DoorbellEventSource](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-doorbelleventsource.html) | 3 | - | - | `DoorbellPress` | 2 | event | | [Alexa.EndpointHealth](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-endpointhealth.html) | 3.1 | `connectivity` | - | - | 1 | - | | [Alexa.EqualizerController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-equalizercontroller.html) | 3 | `bands`, `mode` | `SetMode`, `SetBands`, `AdjustBands`, `ResetBands` | - | 2 | - | | [Alexa.HumiditySensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-humiditysensor.html) | 3 | `relativeHumidity` | - | - | 1 | - | | [Alexa.InputController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-inputcontroller.html) | 3 | `input` | `SelectInput` | - | 2 | - | -| [Alexa.InventoryLevelSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-inventorylevelsensor.html) (instances) | 3 | `level` | - | - | 2 | - | -| [Alexa.LockController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-lockcontroller.html) | 3 | `lockState` | `Lock`, `Unlock` | - | 1 | directives, change report | +| [Alexa.InventoryLevelSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-inventorylevelsensor.html) (instances) | 3 | `level` | - | - | 2 | change report | +| [Alexa.LockController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-lockcontroller.html) | 3 | `lockState` | `Lock`, `Unlock` | - | 1 | directives with a deferred answer, change report | | [Alexa.ModeController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-modecontroller.html) (instances) | 3 | `mode` | `SetMode`, `AdjustMode` | - | 1 | directives | | [Alexa.MotionSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-motionsensor.html) | 3 | `detectionState` | - | - | 1 | change report | | [Alexa.PercentageController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-percentagecontroller.html) | 3 | `percentage` | `SetPercentage`, `AdjustPercentage` | - | 1 | directives | @@ -237,22 +237,22 @@ announced with its version, nothing about it is checked, and a handler gets the | [Alexa.PlaybackStateReporter](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-playbackcontroller.html) | 3 | `playbackState` | - | - | 2 | - | | [Alexa.PowerController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-powercontroller.html) | 3 | `powerState` | `TurnOn`, `TurnOff` | - | 1 | directives, change report | | [Alexa.PowerLevelController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-powerlevelcontroller.html) | 3 | `powerLevel` | `SetPowerLevel`, `AdjustPowerLevel` | - | 1 | directives | -| [Alexa.RangeController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-rangecontroller.html) (instances) | 3 | `rangeValue` | `SetRangeValue`, `AdjustRangeValue` | - | 1 | directives | +| [Alexa.RangeController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-rangecontroller.html) (instances) | 3 | `rangeValue` | `SetRangeValue`, `AdjustRangeValue` | - | 1 | directives, change report | | [Alexa.SceneController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-scenecontroller.html) | 3 | - | `Activate`, `Deactivate` | `ActivationStarted`, `DeactivationStarted` | 1 | directives | -| [Alexa.SecurityPanelController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-securitypanelcontroller.html) | 3 | `armState`, `burglaryAlarm`, `fireAlarm`, `carbonMonoxideAlarm`, `waterAlarm` | `Arm`, `Disarm` | - | 2 | - | -| [Alexa.SimpleEventSource](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-simpleeventsource.html) (instances) | 1.0 | - | - | `Event` | 2 | - | +| [Alexa.SecurityPanelController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-securitypanelcontroller.html) | 3 | `armState`, `burglaryAlarm`, `fireAlarm`, `carbonMonoxideAlarm`, `waterAlarm` | `Arm`, `Disarm` | - | 2 | directives, change report | +| [Alexa.SimpleEventSource](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-simpleeventsource.html) (instances) | 1.0 | - | - | `Event` | 2 | event | | [Alexa.Speaker](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-speaker.html) | 3 | `volume`, `muted` | `SetVolume`, `AdjustVolume`, `SetMute` | - | 2 | - | | [Alexa.StepSpeaker](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-stepspeaker.html) | 3 | - | `AdjustVolume`, `SetMute` | - | 2 | - | -| [Alexa.TemperatureSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-temperaturesensor.html) | 3 | `temperature` | - | - | 1 | change report | +| [Alexa.TemperatureSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-temperaturesensor.html) | 3 | `temperature` | - | - | 1 | state report | | [Alexa.ThermostatController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-thermostatcontroller.html) | 3.2 | `targetSetpoint`, `lowerSetpoint`, `upperSetpoint`, `thermostatMode`, `adaptiveRecoveryStatus` | `SetTargetTemperature`, `AdjustTargetTemperature`, `SetThermostatMode`, `ResumeSchedule` | - | 1 | directives | -| [Alexa.ThermostatController.Schedule](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-thermostatcontroller-schedule.html) | 3.2 | `adaptiveRecoveryEnabled`, `scheduleEnabled` | `SetWeeklySchedule`, `SetScheduleState`, `SetAdaptiveRecovery` | - | 2 | - | +| [Alexa.ThermostatController.Schedule](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-thermostatcontroller-schedule.html) | 3.2 | `adaptiveRecoveryEnabled`, `scheduleEnabled` | `SetWeeklySchedule`, `SetScheduleState`, `SetAdaptiveRecovery` | - | 2 | state report | | [Alexa.TimeHoldController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-timeholdcontroller.html) | 3 | `holdStartTime`, `holdEndTime` | `Hold`, `Resume` | - | 2 | - | | [Alexa.ToggleController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-togglecontroller.html) (instances) | 3 | `toggleState` | `TurnOn`, `TurnOff` | - | 1 | directives | | [Alexa.WakeOnLANController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-wakeonlancontroller.html) | 3 | - | - | `WakeUp` | 2 | - | 32 interfaces are described (tier 1 and 2), 39 are named (tier 3). "Through Alexa" is what a real Alexa account drove through the public Alex2MQTT service on 2026-09-28, with -alex2node 1.5.2; "-" is an interface that no such run has covered. +alex2node 2.0.0; "-" is an interface that no such run has covered. Tier 3: [Alexa.ApplicationStateReporter](https://developer.amazon.com/docs/alexaplus/alexa-voice-service/alexa-applicationstatereporter.html) 1, [Alexa.Audio.PlayQueue](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-audio-playqueue.html) 1, [Alexa.AuthorizationController](https://developer.amazon.com/en-US/docs/alexa/ask-overviews/deprecated-features.html) 1, [Alexa.AutomationManagement](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-automationmanagement.html) 1, [Alexa.Automotive.VehicleData](https://developer.amazon.com/en-US/docs/alexa/ask-overviews/deprecated-features.html) 1, [Alexa.Camera.LiveViewController](https://developer.amazon.com/docs/alexaplus/device-apis/list-of-interfaces.html) 1.7, [Alexa.CameraStreamController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-camerastreamcontroller.html) 3, [Alexa.Commissionable](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-commissionable.html) 1, [Alexa.ConsentManagement.ConsentRequiredReporter](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-consentrequiredreporter.html) 1, [Alexa.Cooking](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-cooking.html) 1, [Alexa.Cooking.FoodTemperatureController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-cooking-foodtemperaturecontroller.html) 1, [Alexa.Cooking.FoodTemperatureSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-cooking-foodtemperaturesensor.html) 1, [Alexa.Cooking.PresetController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-cooking-presetcontroller.html) 1, [Alexa.Cooking.TemperatureController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-cooking-temperaturecontroller.html) 1, [Alexa.Cooking.TemperatureSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-cooking-temperaturesensor.html) 1, [Alexa.Cooking.TimeController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-cooking-timecontroller.html) 1, [Alexa.DataController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-datacontroller.html) 1, [Alexa.DeviceUsage.Estimation](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-deviceusage-estimation.html) 1, [Alexa.DeviceUsage.Meter](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-deviceusage-meter.html) 1, [Alexa.InventoryLevelUsageSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-inventorylevelusagesensor.html) 1, [Alexa.InventoryUsageSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-inventoryusagesensor.html) 1, [Alexa.KeypadController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-keypadcontroller.html) 1, [Alexa.Launcher](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-launcher.html) 1.1, [Alexa.Media.PlayQueue](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-media-playqueue.html) 1, [Alexa.Media.Playback](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-media-playback.html) 1, [Alexa.Media.Search](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-media-search.html) 1, [Alexa.ProactiveNotificationSource](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-proactivenotificationsource.html) 1, [Alexa.RTCSessionController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-rtcsessioncontroller.html) 1, [Alexa.RecordController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-recordcontroller.html) 3, [Alexa.RemoteVideoPlayer](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-remotevideoplayer.html) 1, [Alexa.SecurityPanelController.Alert](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-securitypanelcontroller-alert.html) 1, [Alexa.SeekController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-seekcontroller.html) 3, [Alexa.SmartVision.ObjectDetectionSensor](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-smartvision-objectdetectionsensor.html) 1, [Alexa.SmartVision.SnapshotProvider](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-smartvision-snapshotprovider.html) 1, [Alexa.ThermostatController.Configuration](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-thermostatcontroller-configuration.html) 1, [Alexa.ThermostatController.HVAC.Components](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-thermostatcontroller-hvac-components.html) 1, [Alexa.UIController](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-uicontroller.html) 1, [Alexa.UserPreference](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-userpreference.html) 1, [Alexa.VideoRecorder](https://developer.amazon.com/docs/alexaplus/device-apis/alexa-videorecorder.html) 1. @@ -586,13 +586,16 @@ bedroomLight.on("Event", (directive, interfaceType) => { ## Limits -- **What was run against Alexa.** On 2026-09-28 alex2node 1.5.2 was driven from a real Alexa account through the - public Alex2MQTT service: Power, Brightness, Color, ColorTemperature, Percentage, PowerLevel, Thermostat, Range, - Mode, Toggle, Lock and Scene, deferred responses, `ErrorResponse`, `ReportState`, and change reports for contact, - motion, lock, temperature and power. Voice commands were not tested yet. The 2.0 API was not run against an - Alexa account yet: the test suite runs it against a broker on 127.0.0.1. -- **Proactive events.** `device.raise()` publishes to `/event`. Whether a `DoorbellPress` reaches Alexa - depends on the Alex2MQTT service relaying that topic, which was not tested. +- **What was run against Alexa.** On 2026-09-28 a real Alexa account drove 33 test devices on the 2.0 API through + the public Alex2MQTT service; the table above has the interfaces. Alexa accepted the discovery of all 33. Covered + beside the directives: deferred answers, `ErrorResponse`, `ReportState`, change reports, a `DoorbellPress` and a + button event through `device.raise()`. The directives came from the control call of the Alexa app, which has no + way to say "raise", "lower" or "warmer": voice commands, and with them the `Adjust...` directives and the + semantics of a blind, were not tested yet. +- **Not covered by that run.** `Alexa.Speaker`, `Alexa.ChannelController` and the other interfaces of a television + or a speaker were discovered, but the control call has no action for them. `Alexa.HumiditySensor` was + discovered and answered `ReportState`; Alexa's own state call does not return a humidity, so nothing shows + whether Alexa took the value. - **Alexa.WakeOnLANController** needs three messages for one `TurnOn`. The descriptor is there; the exchange was not run through Alex2MQTT. - **Tier 3 interfaces** are named, not described: no check of the declaration, the state or the payload. diff --git a/scripts/capability-table.js b/scripts/capability-table.js index a05bd54..e0f70d3 100644 --- a/scripts/capability-table.js +++ b/scripts/capability-table.js @@ -14,26 +14,31 @@ const README = path.join(ROOT, "readme.md"); const BEGIN = ""; const END = ""; -// What was driven from a real Alexa account through the public Alex2MQTT service, and when. The runs used -// alex2node 1.5.2; a row is added here when a run is recorded, not when a descriptor is written. +// What was driven from a real Alexa account through the public Alex2MQTT service, and when: 33 test devices on +// the 2.0 API, 60 cases. A row is added here when a run is recorded, not when a descriptor is written. const DRIVEN_ON = "2026-09-28"; -const DRIVEN_WITH = "alex2node 1.5.2"; +const DRIVEN_WITH = "alex2node 2.0.0"; const DRIVEN = { "Alexa": "ReportState", - "Alexa.BrightnessController": "directives", + "Alexa.BrightnessController": "directives, change report", "Alexa.ColorController": "directives", "Alexa.ColorTemperatureController": "directives", "Alexa.ContactSensor": "change report", - "Alexa.LockController": "directives, change report", + "Alexa.DoorbellEventSource": "event", + "Alexa.InventoryLevelSensor": "change report", + "Alexa.LockController": "directives with a deferred answer, change report", "Alexa.ModeController": "directives", "Alexa.MotionSensor": "change report", "Alexa.PercentageController": "directives", "Alexa.PowerController": "directives, change report", "Alexa.PowerLevelController": "directives", - "Alexa.RangeController": "directives", + "Alexa.RangeController": "directives, change report", "Alexa.SceneController": "directives", - "Alexa.TemperatureSensor": "change report", + "Alexa.SecurityPanelController": "directives, change report", + "Alexa.SimpleEventSource": "event", + "Alexa.TemperatureSensor": "state report", "Alexa.ThermostatController": "directives", + "Alexa.ThermostatController.Schedule": "state report", "Alexa.ToggleController": "directives", }; diff --git a/test/readme.test.js b/test/readme.test.js index 5891f30..9d070aa 100644 --- a/test/readme.test.js +++ b/test/readme.test.js @@ -32,12 +32,14 @@ test("the table has a row for every described interface, and a line for the othe }); test("an interface is said to be driven through Alexa only when a run is recorded for it", () => { - // The runs of 2026-09-28, with alex2node 1.5.2. A name is added here with the record of its run. + // The run of 2026-09-28 with 33 test devices on the 2.0 API. A name is added here with the record of its run. assert.deepEqual(Object.keys(DRIVEN).sort(), [ "Alexa", "Alexa.BrightnessController", "Alexa.ColorController", "Alexa.ColorTemperatureController", - "Alexa.ContactSensor", "Alexa.LockController", "Alexa.ModeController", "Alexa.MotionSensor", - "Alexa.PercentageController", "Alexa.PowerController", "Alexa.PowerLevelController", "Alexa.RangeController", - "Alexa.SceneController", "Alexa.TemperatureSensor", "Alexa.ThermostatController", "Alexa.ToggleController", + "Alexa.ContactSensor", "Alexa.DoorbellEventSource", "Alexa.InventoryLevelSensor", "Alexa.LockController", + "Alexa.ModeController", "Alexa.MotionSensor", "Alexa.PercentageController", "Alexa.PowerController", + "Alexa.PowerLevelController", "Alexa.RangeController", "Alexa.SceneController", + "Alexa.SecurityPanelController", "Alexa.SimpleEventSource", "Alexa.TemperatureSensor", + "Alexa.ThermostatController", "Alexa.ThermostatController.Schedule", "Alexa.ToggleController", ]); const rows = table().split("\n").filter((line) => line.startsWith("| [Alexa")); for (const line of rows) {