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) {