The test that compares the imports of dist/ with "dependencies" matched a regular expression against the whole
file. tsc keeps comments in its output, so a usage line in a doc comment (import { ActionMapping } from
"alex2node") counted as an import and failed the commit gate with a message about dependencies.
ts.preProcessFile lists the imports instead. Stripping comments with two more expressions was tried first and
reads strings wrong: in `"src/*.ts"; require("real-one"); "*/"` it drops the require, and it never saw
import("lazy-one"); the scanner returns both. The declarations are scanned too: a .d.ts that imports a package the
consumer does not get breaks the consumer's type check.
Checked in a scratch copy: a doc comment and a string with an import in them pass; require("left-pad") in
dist/cjs, an aedes import in a declaration and an unused declared dependency each fail. npm test: 30 pass in
8.9-10.0 s.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|---|---|---|
| dist | ||
| examples | ||
| scripts | ||
| src | ||
| test | ||
| .gitignore | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| readme.md | ||
| tsconfig.cjs.json | ||
| tsconfig.esm.json | ||
| tsconfig.json | ||
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'serrorevent 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.addSupportedModesaccepts the{ value, modeResources }objects a ModeController needs,ActionMapping's payload argument is optional,addHealthProptakesEndpointHealth.OK(or the plain string). disconnect()thenconnect()works. Devices registered before adisconnect()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(withlowerSetpoint,upperSetpoint,thermostatMode) and no longer advertisesadaptiveRecoveryStatus, 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 TYPEto stderr for every interface the library has no property list for; the bridge notes it through the log hook (debug) instead. - Examples:
require("alex2node"), anerrorlistener 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
errorunconditionally, and Node terminates a process that has an unhandlederrorevent, so every reconnect failure killed the host. 1.5.1 emitserroronly when you listen for it, keeps reconnecting on its own (mqtt.js, 1 s), and reportsconnect,offline,reconnectandclose.bridge.connectedsays 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.mqttis merged over the mqtt.js client options;options.logreplaces the console output. - Proactive state (ChangeReport).
device.getChangeReport(cause)builds anAlexa.ChangeReport: theadd*Propcalls 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.SceneControllerdiscovers correctly (supportsDeactivation), anddevice.sendSceneResponse(correlationToken, activated)answers Activate / Deactivate with ActivationStarted / DeactivationStarted. - Device management.
unregisterDevice(endpointId),clearDevices(),getDevices(),getDevice(id),disconnect();discover(device count) anddirective({ 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 testruns 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.