248 lines
14 KiB
Markdown
248 lines
14 KiB
Markdown
# Alex2Node
|
||
|
||
Alex2Node is a lightweight Node.js library for integrating devices with Amazon Alexa smart home APIs using MQTT. The library simplifies the process of creating Alexa-compatible devices using Alex2MQTT instead of an Alexa Skill directly.
|
||
|
||
For more details on how to configure Alex2MQTT, visit [Alex2MQTT Documentation](https://alex2mqtt.stormysdream.club/).
|
||
|
||
---
|
||
|
||
## Features
|
||
- Supports "All" Alexa smart home capabilities.
|
||
- Simplifies Alexa device integration with MQTT.
|
||
- Event-driven architecture for handling Alexa directives.
|
||
- Easily configure devices and their capabilities via simple API.
|
||
|
||
|
||
## What is new in 1.5.2
|
||
|
||
- `send()` (status, error and scene responses, change reports) never rejects any more: it resolves with the topic, or `""` when the publish failed, and the error goes to the bridge's `error` event when a listener is attached. In 1.5.1 a failing publish rejected the promise, and an un-caught `.send()` (every example, the Quick Start) then killed the host process.
|
||
- **Types ship.** `dist/index.d.ts` (and the other declarations) are generated and tracked, so TypeScript users get
|
||
types. `addSupportedModes` accepts the `{ value, modeResources }` objects a ModeController needs, `ActionMapping`'s
|
||
payload argument is optional, `addHealthProp` takes `EndpointHealth.OK` (or the plain string).
|
||
- **`disconnect()` then `connect()` works.** Devices registered before a `disconnect()` publish through the new
|
||
connection (1.5.1 left them bound to the closed client: discoverable, but they never answered a directive).
|
||
- **Duplicate endpointIds.** `registerDevice()` with an endpointId that is already registered returns the existing
|
||
device (with a warning: `console.warn`, or the log hook when one is set) instead of adding a second one that never
|
||
received a directive. EndpointIds are unique per root topic.
|
||
- **Thermostat discovery** lists `targetSetpoint` (with `lowerSetpoint`, `upperSetpoint`, `thermostatMode`) and no
|
||
longer advertises `adaptiveRecoveryStatus`, which nothing reported. A consumer (insteonDashboard) re-announces its
|
||
thermostats once on the next discovery because the payload changed - harmless.
|
||
- Discovery no longer prints `UNSUPORTED INTERFACE TYPE` to stderr for every interface the library has no property
|
||
list for; the bridge notes it through the log hook (debug) instead.
|
||
- Examples: `require("alex2node")`, an `error` listener in every one, `EndpointHealth.OK`, neutral endpoint ids;
|
||
BlindControl's ReportState reads the correlationToken from the header, the thermostat reports its Fahrenheit data
|
||
as Fahrenheit, ExamplePowerController is the plain on/off light again and the new
|
||
ExamplePowerControllerWithBrightness declares the BrightnessController it reports. A LICENSE file (MIT).
|
||
|
||
## What is new in 1.5.1
|
||
|
||
- **A broker outage no longer crashes your process.** 1.4.0 emitted `error` unconditionally, and Node terminates a
|
||
process that has an unhandled `error` event, so every reconnect failure killed the host. 1.5.1 emits `error` only
|
||
when you listen for it, keeps reconnecting on its own (mqtt.js, 1 s), and reports `connect`, `offline`, `reconnect`
|
||
and `close`. `bridge.connected` says where you stand.
|
||
- **Configurable broker.** `new Alex2MQTT(user, pass, rootTopic, debug, { host: "mqtt://broker:1883", mqtt: { ... } })`
|
||
(the default is still the public Alex2MQTT broker). `options.mqtt` is merged over the mqtt.js client options;
|
||
`options.log` replaces the console output.
|
||
- **Proactive state (ChangeReport).** `device.getChangeReport(cause)` builds an `Alexa.ChangeReport`: the
|
||
`add*Prop` calls describe what changed, `.unchanged()` switches the following calls to the context, `.send()`
|
||
publishes it to `<rootTopic>/changeReport`, which Alex2MQTT forwards to the Alexa event gateway with your account's
|
||
token. Register the capability with `{ proactivelyReported: true }` so Alexa accepts it.
|
||
- **Scenes.** `Alexa.SceneController` discovers correctly (`supportsDeactivation`), and
|
||
`device.sendSceneResponse(correlationToken, activated)` answers Activate / Deactivate with ActivationStarted /
|
||
DeactivationStarted.
|
||
- **Device management.** `unregisterDevice(endpointId)`, `clearDevices()`, `getDevices()`, `getDevice(id)`,
|
||
`disconnect()`; `discover` (device count) and `directive` ({ endpointId, namespace, name }) events;
|
||
`lastDiscoveryAt`. `addCapability(type, { retrievable, proactivelyReported, instance })`.
|
||
- `send()` on every message returns a promise (resolves with the topic) and no longer logs to the console.
|
||
- Tests: `npm test` runs the library against an in-process MQTT broker (aedes, a dev dependency).
|
||
|
||
```js
|
||
const { Alex2MQTT, AlexaInterfaceType, DisplayCategory, PowerController } = require("alex2node");
|
||
const bridge = new Alex2MQTT(user, pass, rootTopic, false, { host: process.env.MQTT_URL });
|
||
bridge.on("error", (e) => console.warn("broker:", e.message)); // optional - without it the error is swallowed, never thrown
|
||
bridge.on("connect", () => console.log("connected"));
|
||
bridge.connect();
|
||
const lamp = bridge.registerDevice("Dining Room Light", "dining-room-light-1", DisplayCategory.LIGHT);
|
||
lamp.addCapability(AlexaInterfaceType.POWER_CONTROLLER, { proactivelyReported: true });
|
||
lamp.on("Event", (directive) => { /* ... act, then: */ lamp.getStatusMessage(directive.header.correlationToken, true).addPowerControllerProp(PowerController.ON).send(); });
|
||
// later, when the light changes locally:
|
||
lamp.getChangeReport("PHYSICAL_INTERACTION").addPowerControllerProp(PowerController.OFF).send();
|
||
```
|
||
|
||
## Quick Start
|
||
|
||
### Installation
|
||
|
||
npm has **1.5.0**. Everything since - 1.5.1 (ChangeReport, configurable broker host, error events) and 1.5.2 (types,
|
||
the reconnect fix; see "What is new") - installs straight from the Forgejo repository, so nothing waits on an npm
|
||
publish:
|
||
|
||
```bash
|
||
npm install alex2node # 1.5.0 from npm
|
||
npm install git+https://git.stormysdream.club/apps/Alex2Node.git # latest main from Forgejo
|
||
npm install git+https://git.stormysdream.club/apps/Alex2Node.git#<commit> # pinned to one commit
|
||
```
|
||
|
||
The repository is public, so the `https` form needs no account. `#<commit>` (or `#<tag>`, when one exists) pins a
|
||
revision; either way npm records the resolved commit in `package-lock.json`, so `npm ci` reproduces it. `npm update
|
||
alex2node` re-resolves a branch ref such as `#main` to its latest commit; a commit or tag pin stays put.
|
||
|
||
Source: https://git.stormysdream.club/apps/Alex2Node
|
||
|
||
Then, import it into your JavaScript or TypeScript file:
|
||
|
||
```js
|
||
const { Alex2MQTT, AlexaInterfaceType, PowerController } = require('alex2node');
|
||
```
|
||
|
||
Make sure to include your MQTT connection credentials via a `.env` file or directly in the code.
|
||
|
||
Credentials can be found at https://alex2mqtt.stormysdream.club/ after logging in with amazon.
|
||
|
||
---
|
||
|
||
## Creating a Basic Device
|
||
|
||
### Example: Power Control for a Light
|
||
|
||
Here’s how to create a simple device that controls a light:
|
||
|
||
First, create an instance of the Alexa-to-MQTT client and initialize it with your MQTT credentials:
|
||
|
||
```js
|
||
require("dotenv").config(); // Load environment variables from .env file
|
||
|
||
const { Alex2MQTT, AlexaInterfaceType, PowerController, EndpointHealth } = require("alex2node");
|
||
|
||
const username = process.env.MQTT_USERNAME;
|
||
const password = process.env.MQTT_PASSWORD;
|
||
const rootTopic = process.env.MQTT_ROOT_TOPIC;
|
||
|
||
const alex2NodeClient = new Alex2MQTT(username, password, rootTopic, false); // 1.5.1: add { host } as a fifth argument for another broker
|
||
alex2NodeClient.connect(); // Connect to the MQTT broker
|
||
alex2NodeClient.on("error", (e) => console.error("broker:", e.message)); // Log broker errors - on 1.5.0 this listener is also what keeps an outage from killing the process
|
||
```
|
||
|
||
Once initialized, you can begin to add virtual devices. In this example, we will add a PowerController for a light:
|
||
|
||
```js
|
||
const deviceName = "Bedroom Light";
|
||
const bedroomLight = alex2NodeClient.registerDevice(deviceName, "bedroom-light-1"); // the endpointId: unique per root topic
|
||
|
||
// Add the PowerController capability
|
||
bedroomLight.addCapability(AlexaInterfaceType.POWER_CONTROLLER);
|
||
|
||
// Handle Alexa's ReportState directive to report the current state
|
||
let outputState = PowerController.OFF;
|
||
bedroomLight.on("ReportState", (payload) => {
|
||
const { correlationToken } = payload.header; // the whole directive is passed; Alexa needs the token echoed
|
||
const status = bedroomLight.getStatusMessage(correlationToken);
|
||
status
|
||
.addHealthProp(EndpointHealth.OK)
|
||
.addPowerControllerProp(outputState)
|
||
.send();
|
||
});
|
||
|
||
// Handle Alexa's Event directive to process state changes
|
||
bedroomLight.on("Event", (directive, interfaceType) => {
|
||
if (interfaceType === AlexaInterfaceType.POWER_CONTROLLER) {
|
||
const name = directive.header.name;
|
||
const token = directive.header.correlationToken;
|
||
|
||
if (name === "TurnOn") {
|
||
outputState = PowerController.ON;
|
||
console.log("Turning ON the Bedroom Light");
|
||
} else if (name === "TurnOff") {
|
||
outputState = PowerController.OFF;
|
||
console.log("Turning OFF the Bedroom Light");
|
||
}
|
||
|
||
const status = bedroomLight.getStatusMessage(token, true);
|
||
status
|
||
.addHealthProp(EndpointHealth.OK)
|
||
.addPowerControllerProp(outputState)
|
||
.send();
|
||
}
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## Interface Types
|
||
| Alexa Interface Type | Status |
|
||
|-----------------------------------------------------|-------------|
|
||
| AlexaInterfaceType.ENDPOINT_HEALTH | Fully Supported |
|
||
| AlexaInterfaceType.POWER_CONTROLLER | Fully Supported |
|
||
| AlexaInterfaceType.BRIGHTNESS_CONTROLLER | Fully Supported |
|
||
| AlexaInterfaceType.TOGGLE_CONTROLLER | Fully Supported |
|
||
| AlexaInterfaceType.TEMPERATURE_SENSOR | Fully Supported |
|
||
| AlexaInterfaceType.COLOR_TEMPERATURE_CONTROLLER | Fully Supported |
|
||
| AlexaInterfaceType.AUTOMATION_MANAGEMENT | Supported* |
|
||
| AlexaInterfaceType.CHANNEL_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.COLOR_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.CONTACT_SENSOR | Supported* |
|
||
| AlexaInterfaceType.APPLICATION_STATE_REPORTER | Supported* |
|
||
| AlexaInterfaceType.AUDIO_PLAY_QUEUE | Supported* |
|
||
| AlexaInterfaceType.AUTHORIZATION_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.AUTOMOTIVE_VEHICLE_DATA | Supported* |
|
||
| AlexaInterfaceType.CAMERA_LIVE_VIEW_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.CAMERA_STREAM_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.COMMISSIONABLE | Supported* |
|
||
| AlexaInterfaceType.CONSENT_MANAGEMENT_CONSENT_REQUIRED_REPORTER | Supported* |
|
||
| AlexaInterfaceType.COOKING | Supported* |
|
||
| AlexaInterfaceType.DATA_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.DEVICE_USAGE_ESTIMATION | Supported* |
|
||
| AlexaInterfaceType.DEVICE_USAGE_METER | Supported* |
|
||
| AlexaInterfaceType.DOORBELL_EVENT_SOURCE | Supported* |
|
||
| AlexaInterfaceType.EQUALIZER_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.INPUT_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.INVENTORY_LEVEL_SENSOR | Supported* |
|
||
| AlexaInterfaceType.INVENTORY_LEVEL_USAGE_SENSOR | Supported* |
|
||
| AlexaInterfaceType.INVENTORY_USAGE_SENSOR | Supported* |
|
||
| AlexaInterfaceType.KEYPAD_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.LAUNCHER | Supported* |
|
||
| AlexaInterfaceType.LOCK_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.MEDIA_PLAYBACK | Supported* |
|
||
| AlexaInterfaceType.MEDIA_SEARCH | Supported* |
|
||
| AlexaInterfaceType.MODE_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.MOTION_SENSOR | Supported* |
|
||
| AlexaInterfaceType.PERCENTAGE_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.PLAYBACK_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.PLAYBACK_STATE_REPORTER | Supported* |
|
||
| AlexaInterfaceType.PROACTIVE_NOTIFICATION_SOURCE | Supported* |
|
||
| AlexaInterfaceType.RANGE_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.RECORD_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.REMOTE_VIDEO_PLAYER | Supported* |
|
||
| AlexaInterfaceType.RTC_SESSION_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.SCENE_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.SECURITY_PANEL_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.SEEK_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.SIMPLE_EVENT_SOURCE | Supported* |
|
||
| AlexaInterfaceType.SMART_VISION_OBJECT_DETECTION_SENSOR | Supported* |
|
||
| AlexaInterfaceType.SMART_VISION_SNAPSHOT_PROVIDER | Supported* |
|
||
| AlexaInterfaceType.SPEAKER | Supported* |
|
||
| AlexaInterfaceType.STEP_SPEAKER | Supported* |
|
||
| AlexaInterfaceType.THERMOSTAT_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.THERMOSTAT_CONTROLLER_CONFIGURATION | Supported* |
|
||
| AlexaInterfaceType.THERMOSTAT_CONTROLLER_HVAC_COMPONENTS | Supported* |
|
||
| AlexaInterfaceType.THERMOSTAT_CONTROLLER_SCHEDULE | Supported* |
|
||
| AlexaInterfaceType.TIME_HOLD_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.UI_CONTROLLER | Supported* |
|
||
| AlexaInterfaceType.USER_PREFERENCE | Supported* |
|
||
| AlexaInterfaceType.VIDEO_RECORDER | Supported* |
|
||
| AlexaInterfaceType.WAKE_ON_LAN_CONTROLLER | Supported* |
|
||
|
||
|
||
(*Partial support or limited implementation advanced configuration is required)
|
||
|
||
---
|
||
|
||
## Contributing
|
||
|
||
Feel free to submit pull requests or issues for feature requests and bug fixes.
|
||
|
||
---
|
||
|
||
## License
|
||
|
||
This project is licensed under the MIT License. See the LICENSE file for details.
|