Node.js library for Alexa-compatible devices over MQTT (the Alex2MQTT client). npm: alex2node
Find a file
David aa0ffd64ea registry: describe an Alexa interface as data
src/registry/ holds what the library knows about an interface: namespace, version, the page it was read from,
properties with their value schemas and directives with their payload schemas. Five interfaces are described
(Alexa, PowerController, BrightnessController, TemperatureSensor, EndpointHealth); the other 65 names of
AlexaInterfaceType are stubs with the version and property names of 1.5.2. schema.ts is the run-time check behind
it (241 lines, no new dependency), catalog.ts the vocabularies of the pages: 103 assets (23 units), 6 actions,
9 states, 56 display categories, 22 reserved words, 73 error types under 11 namespaces.

AlexaInterface.getVersion() and getProps() read the registry; the two switch statements are gone (-167 lines).
On the wire: Alexa.EndpointHealth is announced at 3.1 (was 3.3; the page is titled 3.1 and no page mentions 3.3),
and TimeHoldController and Camera.LiveViewController at 3 and 1.7 (1.5.2 sent the string "UNKNOWN").
DisplayCategory gains VACUUM. New exports: registry, DeclarationError, SchemaError, Assets, Units, Actions,
States, DisplayCategories and the descriptor types.

Tests: 20 JSON examples of the five pages under test/fixtures/alexa-docs; every directive payload and property
value in them parses with its descriptor. npm test: 57 pass (was 30) in 10.8 s, also on Node 18.20.8 and 20.20.2.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 15:18:22 +00:00
dist registry: describe an Alexa interface as data 2026-09-28 15:18:22 +00:00
examples 1.5.2: send() never rejects (status, error, scene, change report: resolves the topic or "" and reports the failure through the bridge's error event when listened to - 1.5.1 rejected, and an un-caught .send() killed the host on any broker hiccup); types ship (declaration: true, dist/*.d.ts tracked; addSupportedModes takes {value, modeResources}, ActionMapping payload optional, addHealthProp accepts EndpointHealth or the string); disconnect()/connect() re-binds devices to the new client; registerDevice returns the existing device on a duplicate endpointId (warning via the log hook, console.warn without one); ThermostatController discovery lists targetSetpoint and drops adaptiveRecoveryStatus; the UNSUPORTED INTERFACE TYPE stderr spam goes through the log hook. Examples: require("alex2node"), an error listener in each, EndpointHealth.OK, neutral endpoint ids, BlindControl reads correlationToken from the header, the thermostat reports Fahrenheit as Fahrenheit, ExamplePowerController is power-only again + new ExamplePowerControllerWithBrightness. readme (install from Forgejo, 1.5.2 changelog, table syntax), LICENSE (MIT); tests for the non-rejecting send, reconnect, duplicate, thermostat discovery and the shipped declaration signatures (8/8 on an in-process broker). 2026-09-28 04:58:39 +00:00
scripts build: refuse unknown arguments; name the fix when typescript is missing 2026-09-28 14:42:33 +00:00
src registry: describe an Alexa interface as data 2026-09-28 15:18:22 +00:00
test registry: describe an Alexa interface as data 2026-09-28 15:18:22 +00:00
.gitignore build: stage beside dist/, not inside it 2026-09-28 14:40:36 +00:00
LICENSE 1.5.2: send() never rejects (status, error, scene, change report: resolves the topic or "" and reports the failure through the bridge's error event when listened to - 1.5.1 rejected, and an un-caught .send() killed the host on any broker hiccup); types ship (declaration: true, dist/*.d.ts tracked; addSupportedModes takes {value, modeResources}, ActionMapping payload optional, addHealthProp accepts EndpointHealth or the string); disconnect()/connect() re-binds devices to the new client; registerDevice returns the existing device on a duplicate endpointId (warning via the log hook, console.warn without one); ThermostatController discovery lists targetSetpoint and drops adaptiveRecoveryStatus; the UNSUPORTED INTERFACE TYPE stderr spam goes through the log hook. Examples: require("alex2node"), an error listener in each, EndpointHealth.OK, neutral endpoint ids, BlindControl reads correlationToken from the header, the thermostat reports Fahrenheit as Fahrenheit, ExamplePowerController is power-only again + new ExamplePowerControllerWithBrightness. readme (install from Forgejo, 1.5.2 changelog, table syntax), LICENSE (MIT); tests for the non-rejecting send, reconnect, duplicate, thermostat discovery and the shipped declaration signatures (8/8 on an in-process broker). 2026-09-28 04:58:39 +00:00
package-lock.json build: type-check src against the Node 18 declarations 2026-09-28 14:29:15 +00:00
package.json registry: describe an Alexa interface as data 2026-09-28 15:18:22 +00:00
readme.md 1.5.2: send() never rejects (status, error, scene, change report: resolves the topic or "" and reports the failure through the bridge's error event when listened to - 1.5.1 rejected, and an un-caught .send() killed the host on any broker hiccup); types ship (declaration: true, dist/*.d.ts tracked; addSupportedModes takes {value, modeResources}, ActionMapping payload optional, addHealthProp accepts EndpointHealth or the string); disconnect()/connect() re-binds devices to the new client; registerDevice returns the existing device on a duplicate endpointId (warning via the log hook, console.warn without one); ThermostatController discovery lists targetSetpoint and drops adaptiveRecoveryStatus; the UNSUPORTED INTERFACE TYPE stderr spam goes through the log hook. Examples: require("alex2node"), an error listener in each, EndpointHealth.OK, neutral endpoint ids, BlindControl reads correlationToken from the header, the thermostat reports Fahrenheit as Fahrenheit, ExamplePowerController is power-only again + new ExamplePowerControllerWithBrightness. readme (install from Forgejo, 1.5.2 changelog, table syntax), LICENSE (MIT); tests for the non-rejecting send, reconnect, duplicate, thermostat discovery and the shipped declaration signatures (8/8 on an in-process broker). 2026-09-28 04:58:39 +00:00
tsconfig.cjs.json build: dual CJS/ESM output, ES2020 target, Node 18 floor 2026-09-28 13:58:17 +00:00
tsconfig.esm.json build: the ES module build gets its own declarations 2026-09-28 14:31:34 +00:00
tsconfig.json build: dual CJS/ESM output, ES2020 target, Node 18 floor 2026-09-28 13:58:17 +00:00

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.


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).
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:

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:

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:

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:

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.