Add full support and examples for thermostat
This commit is contained in:
parent
7f051dfdcb
commit
f4d07b589c
12 changed files with 307 additions and 32 deletions
11
dist/Alex2Node.js
vendored
11
dist/Alex2Node.js
vendored
|
|
@ -56,7 +56,7 @@ class Alex2MQTT extends events_1.EventEmitter {
|
||||||
}
|
}
|
||||||
else if (this.debugLogging) {
|
else if (this.debugLogging) {
|
||||||
console.log(`[Alex2Node.ts] Discovery payloads published to ${topic + "_r"}`);
|
console.log(`[Alex2Node.ts] Discovery payloads published to ${topic + "_r"}`);
|
||||||
console.log(deviceArray);
|
console.log(JSON.stringify(deviceArray, null, 2));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -102,14 +102,19 @@ class Alex2MQTT extends events_1.EventEmitter {
|
||||||
this.emit("error", err);
|
this.emit("error", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
registerDevice(name, endpointId) {
|
registerDevice(name, endpointId, displayCategory) {
|
||||||
if (!this.client) {
|
if (!this.client) {
|
||||||
throw new Error("Must call connect before creating devices");
|
throw new Error("Must call connect before creating devices");
|
||||||
}
|
}
|
||||||
if (this.debugLogging) {
|
if (this.debugLogging) {
|
||||||
console.log(`[Alex2Node.ts] Creating new device with endpoint: ${endpointId}`);
|
console.log(`[Alex2Node.ts] Creating new device with endpoint: ${endpointId}`);
|
||||||
}
|
}
|
||||||
const device = new Device_1.default(this.client, this.rootTopic, name, endpointId);
|
const normalizedCategory = displayCategory === null
|
||||||
|
? null
|
||||||
|
: Array.isArray(displayCategory)
|
||||||
|
? displayCategory
|
||||||
|
: [displayCategory];
|
||||||
|
const device = new Device_1.default(this.client, this.rootTopic, name, endpointId, normalizedCategory);
|
||||||
this.devices.push(device);
|
this.devices.push(device);
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
dist/AlexaInterface.js
vendored
19
dist/AlexaInterface.js
vendored
|
|
@ -164,11 +164,12 @@ class AlexaInterface {
|
||||||
case AlexaInterfaceType.SCENE_CONTROLLER:
|
case AlexaInterfaceType.SCENE_CONTROLLER:
|
||||||
case AlexaInterfaceType.SEEK_CONTROLLER:
|
case AlexaInterfaceType.SEEK_CONTROLLER:
|
||||||
case AlexaInterfaceType.TEMPERATURE_SENSOR:
|
case AlexaInterfaceType.TEMPERATURE_SENSOR:
|
||||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
|
|
||||||
case AlexaInterfaceType.TOGGLE_CONTROLLER:
|
case AlexaInterfaceType.TOGGLE_CONTROLLER:
|
||||||
return "3";
|
return "3";
|
||||||
case AlexaInterfaceType.LAUNCHER:
|
case AlexaInterfaceType.LAUNCHER:
|
||||||
return "1.1";
|
return "1.1";
|
||||||
|
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
|
||||||
|
return "3.2";
|
||||||
case AlexaInterfaceType.ENDPOINT_HEALTH:
|
case AlexaInterfaceType.ENDPOINT_HEALTH:
|
||||||
return "3.3";
|
return "3.3";
|
||||||
default:
|
default:
|
||||||
|
|
@ -197,6 +198,13 @@ class AlexaInterface {
|
||||||
return ["toggleState"];
|
return ["toggleState"];
|
||||||
case AlexaInterfaceType.TEMPERATURE_SENSOR:
|
case AlexaInterfaceType.TEMPERATURE_SENSOR:
|
||||||
return ["temperature"];
|
return ["temperature"];
|
||||||
|
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
|
||||||
|
return [
|
||||||
|
"lowerSetpoint",
|
||||||
|
"upperSetpoint",
|
||||||
|
"thermostatMode",
|
||||||
|
"adaptiveRecoveryStatus",
|
||||||
|
];
|
||||||
case AlexaInterfaceType.APPLICATION_STATE_REPORTER:
|
case AlexaInterfaceType.APPLICATION_STATE_REPORTER:
|
||||||
case AlexaInterfaceType.AUDIO_PLAY_QUEUE:
|
case AlexaInterfaceType.AUDIO_PLAY_QUEUE:
|
||||||
case AlexaInterfaceType.AUTHORIZATION_CONTROLLER:
|
case AlexaInterfaceType.AUTHORIZATION_CONTROLLER:
|
||||||
|
|
@ -238,7 +246,6 @@ class AlexaInterface {
|
||||||
case AlexaInterfaceType.SMART_VISION_SNAPSHOT_PROVIDER:
|
case AlexaInterfaceType.SMART_VISION_SNAPSHOT_PROVIDER:
|
||||||
case AlexaInterfaceType.SPEAKER:
|
case AlexaInterfaceType.SPEAKER:
|
||||||
case AlexaInterfaceType.STEP_SPEAKER:
|
case AlexaInterfaceType.STEP_SPEAKER:
|
||||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
|
|
||||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_CONFIGURATION:
|
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_CONFIGURATION:
|
||||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_HVAC_COMPONENTS:
|
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_HVAC_COMPONENTS:
|
||||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_SCHEDULE:
|
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_SCHEDULE:
|
||||||
|
|
@ -261,9 +268,15 @@ class AlexaInterface {
|
||||||
properties: {
|
properties: {
|
||||||
retrievable: this.retrievable,
|
retrievable: this.retrievable,
|
||||||
proactivelyReported: this.proactivelyReported,
|
proactivelyReported: this.proactivelyReported,
|
||||||
supported: [{ name: this.getProps()[0] }],
|
supported: this.getProps().map(name => ({ name })),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
if (this.type == AlexaInterfaceType.THERMOSTAT_CONTROLLER) {
|
||||||
|
doc.configuration = {
|
||||||
|
"supportedModes": ["HEAT", "COOL", "AUTO", "OFF"],
|
||||||
|
"supportsScheduling": false
|
||||||
|
};
|
||||||
|
}
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
doc.instance = this.instance;
|
doc.instance = this.instance;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
dist/AlexaStatusMessage.js
vendored
23
dist/AlexaStatusMessage.js
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.AlexaStatusMessage = exports.TemperatureSensorScale = exports.PowerController = exports.EndpointHealth = void 0;
|
exports.AlexaStatusMessage = exports.TemperatureSensorScale = exports.PowerController = exports.ThermostatMode = exports.EndpointHealth = void 0;
|
||||||
const uuid_1 = require("uuid");
|
const uuid_1 = require("uuid");
|
||||||
const AlexaInterface_1 = require("./AlexaInterface");
|
const AlexaInterface_1 = require("./AlexaInterface");
|
||||||
var EndpointHealth;
|
var EndpointHealth;
|
||||||
|
|
@ -8,6 +8,15 @@ var EndpointHealth;
|
||||||
EndpointHealth["OK"] = "OK";
|
EndpointHealth["OK"] = "OK";
|
||||||
EndpointHealth["UNREACHABLE"] = "UNREACHABLE";
|
EndpointHealth["UNREACHABLE"] = "UNREACHABLE";
|
||||||
})(EndpointHealth || (exports.EndpointHealth = EndpointHealth = {}));
|
})(EndpointHealth || (exports.EndpointHealth = EndpointHealth = {}));
|
||||||
|
var ThermostatMode;
|
||||||
|
(function (ThermostatMode) {
|
||||||
|
ThermostatMode["OFF"] = "OFF";
|
||||||
|
ThermostatMode["HEAT"] = "HEAT";
|
||||||
|
ThermostatMode["COOL"] = "COOL";
|
||||||
|
ThermostatMode["AUTO"] = "AUTO";
|
||||||
|
ThermostatMode["ECO"] = "ECO";
|
||||||
|
ThermostatMode["CUSTOM"] = "CUSTOM";
|
||||||
|
})(ThermostatMode || (exports.ThermostatMode = ThermostatMode = {}));
|
||||||
var PowerController;
|
var PowerController;
|
||||||
(function (PowerController) {
|
(function (PowerController) {
|
||||||
PowerController["ON"] = "ON";
|
PowerController["ON"] = "ON";
|
||||||
|
|
@ -57,6 +66,18 @@ class AlexaStatusMessage {
|
||||||
this.context.properties.push(prop);
|
this.context.properties.push(prop);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
addThermostatModeProp(mode, uncertaintyInMs = 0) {
|
||||||
|
return this.addProperty(AlexaInterface_1.AlexaInterfaceType.THERMOSTAT_CONTROLLER, "thermostatMode", mode, uncertaintyInMs);
|
||||||
|
}
|
||||||
|
addThermostatControllerProp(name, scale, value, uncertaintyInMs = 0) {
|
||||||
|
const tempValue = {
|
||||||
|
value: scale === TemperatureSensorScale.FAHRENHEIT
|
||||||
|
? (value - 32) * (5 / 9)
|
||||||
|
: value,
|
||||||
|
scale: "CELSIUS",
|
||||||
|
};
|
||||||
|
return this.addProperty(AlexaInterface_1.AlexaInterfaceType.THERMOSTAT_CONTROLLER, name, tempValue, uncertaintyInMs);
|
||||||
|
}
|
||||||
addHealthProp(health, uncertaintyInMs = 0) {
|
addHealthProp(health, uncertaintyInMs = 0) {
|
||||||
return this.addProperty(AlexaInterface_1.AlexaInterfaceType.ENDPOINT_HEALTH, "connectivity", { value: health }, uncertaintyInMs);
|
return this.addProperty(AlexaInterface_1.AlexaInterfaceType.ENDPOINT_HEALTH, "connectivity", { value: health }, uncertaintyInMs);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
dist/Device.js
vendored
8
dist/Device.js
vendored
|
|
@ -5,7 +5,7 @@ const DisplayCategory_1 = require("./DisplayCategory");
|
||||||
const events_1 = require("events");
|
const events_1 = require("events");
|
||||||
const AlexaStatusMessage_1 = require("./AlexaStatusMessage");
|
const AlexaStatusMessage_1 = require("./AlexaStatusMessage");
|
||||||
class Device extends events_1.EventEmitter {
|
class Device extends events_1.EventEmitter {
|
||||||
constructor(mqttClient, rootTopic, name, endpointId, displayCategory = DisplayCategory_1.DisplayCategory.LIGHT, description = "Alexa to Node.js bridge", manufacturerName = "Alex2Node", manufacturer = "Alex2Node", model = "Alex2Node_v1.0.0") {
|
constructor(mqttClient, rootTopic, name, endpointId, displayCategory, description = "Alexa to Node.js bridge", manufacturerName = "Alex2Node", manufacturer = "Alex2Node", model = "Alex2Node_v1.0.0") {
|
||||||
super();
|
super();
|
||||||
this.mqttClient = mqttClient;
|
this.mqttClient = mqttClient;
|
||||||
this.rootTopic = rootTopic;
|
this.rootTopic = rootTopic;
|
||||||
|
|
@ -29,10 +29,10 @@ class Device extends events_1.EventEmitter {
|
||||||
return this.endpointId;
|
return this.endpointId;
|
||||||
}
|
}
|
||||||
setDisplayCategory(category) {
|
setDisplayCategory(category) {
|
||||||
this.displayCategory = category;
|
this.displayCategory = Array.isArray(category) ? category : [category];
|
||||||
}
|
}
|
||||||
getDisplayCategory() {
|
getDisplayCategory() {
|
||||||
return this.displayCategory;
|
return this.displayCategory || [DisplayCategory_1.DisplayCategory.LIGHT];
|
||||||
}
|
}
|
||||||
setDescription(description) {
|
setDescription(description) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
|
@ -82,7 +82,7 @@ class Device extends events_1.EventEmitter {
|
||||||
friendlyName: this.name,
|
friendlyName: this.name,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
manufacturerName: this.manufacturerName,
|
manufacturerName: this.manufacturerName,
|
||||||
displayCategories: [this.displayCategory],
|
displayCategories: this.getDisplayCategory(),
|
||||||
additionalAttributes: {
|
additionalAttributes: {
|
||||||
manufacturer: this.manufacturer,
|
manufacturer: this.manufacturer,
|
||||||
model: this.model,
|
model: this.model,
|
||||||
|
|
|
||||||
7
dist/index.js
vendored
7
dist/index.js
vendored
|
|
@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.PowerController = exports.AlexaActions = exports.ActionMapping = exports.AlexaInterfaceType = exports.Alex2MQTT = void 0;
|
exports.ThermostatMode = exports.TemperatureSensorScale = exports.EndpointHealth = exports.PowerController = exports.DisplayCategory = exports.AlexaActions = exports.ActionMapping = exports.AlexaInterfaceType = exports.Alex2MQTT = void 0;
|
||||||
var Alex2Node_1 = require("./Alex2Node");
|
var Alex2Node_1 = require("./Alex2Node");
|
||||||
Object.defineProperty(exports, "Alex2MQTT", { enumerable: true, get: function () { return __importDefault(Alex2Node_1).default; } });
|
Object.defineProperty(exports, "Alex2MQTT", { enumerable: true, get: function () { return __importDefault(Alex2Node_1).default; } });
|
||||||
var AlexaInterface_1 = require("./AlexaInterface");
|
var AlexaInterface_1 = require("./AlexaInterface");
|
||||||
|
|
@ -11,5 +11,10 @@ Object.defineProperty(exports, "AlexaInterfaceType", { enumerable: true, get: fu
|
||||||
var ActionMapping_1 = require("./ActionMapping");
|
var ActionMapping_1 = require("./ActionMapping");
|
||||||
Object.defineProperty(exports, "ActionMapping", { enumerable: true, get: function () { return ActionMapping_1.ActionMapping; } });
|
Object.defineProperty(exports, "ActionMapping", { enumerable: true, get: function () { return ActionMapping_1.ActionMapping; } });
|
||||||
Object.defineProperty(exports, "AlexaActions", { enumerable: true, get: function () { return ActionMapping_1.AlexaActions; } });
|
Object.defineProperty(exports, "AlexaActions", { enumerable: true, get: function () { return ActionMapping_1.AlexaActions; } });
|
||||||
|
var DisplayCategory_1 = require("./DisplayCategory");
|
||||||
|
Object.defineProperty(exports, "DisplayCategory", { enumerable: true, get: function () { return DisplayCategory_1.DisplayCategory; } });
|
||||||
var AlexaStatusMessage_1 = require("./AlexaStatusMessage");
|
var AlexaStatusMessage_1 = require("./AlexaStatusMessage");
|
||||||
Object.defineProperty(exports, "PowerController", { enumerable: true, get: function () { return AlexaStatusMessage_1.PowerController; } });
|
Object.defineProperty(exports, "PowerController", { enumerable: true, get: function () { return AlexaStatusMessage_1.PowerController; } });
|
||||||
|
Object.defineProperty(exports, "EndpointHealth", { enumerable: true, get: function () { return AlexaStatusMessage_1.EndpointHealth; } });
|
||||||
|
Object.defineProperty(exports, "TemperatureSensorScale", { enumerable: true, get: function () { return AlexaStatusMessage_1.TemperatureSensorScale; } });
|
||||||
|
Object.defineProperty(exports, "ThermostatMode", { enumerable: true, get: function () { return AlexaStatusMessage_1.ThermostatMode; } });
|
||||||
|
|
|
||||||
160
examples/ExampleThermostat.js
Normal file
160
examples/ExampleThermostat.js
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
// Import required modules and types from the compiled TypeScript distribution (via dist/index.js)
|
||||||
|
const {
|
||||||
|
Alex2MQTT,
|
||||||
|
AlexaInterfaceType,
|
||||||
|
PowerController,
|
||||||
|
TemperatureSensorScale,
|
||||||
|
DisplayCategory,
|
||||||
|
ThermostatMode,
|
||||||
|
} = require("../dist/index.js");
|
||||||
|
|
||||||
|
require("dotenv").config(); // Load environment variables from .env file
|
||||||
|
|
||||||
|
// Simulated in-memory device state (used for example/demo purposes)
|
||||||
|
let outputState = PowerController.OFF; // Initialize the power state to OFF (if thermostat supports ON/OFF modes)
|
||||||
|
|
||||||
|
// Load MQTT connection credentials and root topic from environment variables
|
||||||
|
const username = process.env.MQTT_USERNAME;
|
||||||
|
const password = process.env.MQTT_PASSWORD;
|
||||||
|
const rootTopic = process.env.MQTT_ROOT_TOPIC;
|
||||||
|
|
||||||
|
// Create and initialize a new Alexa-to-MQTT client
|
||||||
|
const alex2NodeClient = new Alex2MQTT(username, password, rootTopic, false);
|
||||||
|
alex2NodeClient.connect(); // Connect to the MQTT broker
|
||||||
|
|
||||||
|
// Define the device name (Thermostat Test)
|
||||||
|
const deviceName = "Thermostat Test";
|
||||||
|
|
||||||
|
// Register the thermostat device with a unique endpoint ID
|
||||||
|
const thermostat = alex2NodeClient.registerDevice(
|
||||||
|
deviceName,
|
||||||
|
"thermostatEndpoint1",
|
||||||
|
[DisplayCategory.TEMPERATURE_SENSOR, DisplayCategory.THERMOSTAT]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add thermostat-related capabilities
|
||||||
|
thermostat.addCapability(AlexaInterfaceType.THERMOSTAT_CONTROLLER); // Allows Alexa to control thermostat modes and setpoints
|
||||||
|
thermostat.addCapability(AlexaInterfaceType.TEMPERATURE_SENSOR); // Allows Alexa to read current temperature from the sensor
|
||||||
|
|
||||||
|
// Log the device name to verify registration
|
||||||
|
console.log(thermostat.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Alexa's ReportState directive.
|
||||||
|
* This occurs when Alexa queries the current state of the thermostat (e.g., during routines or device status checks).
|
||||||
|
*/
|
||||||
|
|
||||||
|
const deviceData = {
|
||||||
|
lowerSetpoint: 20,
|
||||||
|
upperSetpoint: 20,
|
||||||
|
targetSetpoint: 20,
|
||||||
|
current: 20,
|
||||||
|
thermostatMode: ThermostatMode.AUTO,
|
||||||
|
};
|
||||||
|
thermostat.on("ReportState", (payload) => {
|
||||||
|
// console.log("ReportState received!", payload);
|
||||||
|
|
||||||
|
const { correlationToken } = payload;
|
||||||
|
|
||||||
|
let status = thermostat.getStatusMessage(correlationToken);
|
||||||
|
|
||||||
|
status
|
||||||
|
.addHealthProp("OK") // Device is healthy and reachable
|
||||||
|
.addTemperatureSensorProp(
|
||||||
|
TemperatureSensorScale.CELSIUS,
|
||||||
|
deviceData.current
|
||||||
|
) // Report current temperature
|
||||||
|
.addThermostatModeProp(deviceData.thermostatMode); // Report current thermostat mode
|
||||||
|
|
||||||
|
if (deviceData.thermostatMode == "AUTO") {
|
||||||
|
status = status
|
||||||
|
.addThermostatControllerProp(
|
||||||
|
"lowerSetpoint",
|
||||||
|
TemperatureSensorScale.CELSIUS,
|
||||||
|
deviceData.lowerSetpoint
|
||||||
|
) // Lower bound of temperature range
|
||||||
|
.addThermostatControllerProp(
|
||||||
|
"upperSetpoint",
|
||||||
|
TemperatureSensorScale.CELSIUS,
|
||||||
|
deviceData.upperSetpoint
|
||||||
|
); // Upper bound of temperature range
|
||||||
|
} else {
|
||||||
|
status = status.addThermostatControllerProp(
|
||||||
|
"targetSetpoint",
|
||||||
|
TemperatureSensorScale.CELSIUS,
|
||||||
|
deviceData.targetSetpoint
|
||||||
|
); // Lower bound of temperature range
|
||||||
|
}
|
||||||
|
status.send(); // Send the full state report back to Alexa
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle incoming control directives for the thermostat.
|
||||||
|
* These directives come from Alexa when a user issues a command (e.g., to change temperature or mode).
|
||||||
|
*/
|
||||||
|
thermostat.on("Event", (directive, interfaceType) => {
|
||||||
|
console.log("Event received", { directive, interfaceType });
|
||||||
|
if (interfaceType === AlexaInterfaceType.THERMOSTAT_CONTROLLER) {
|
||||||
|
const name = directive.header.name;
|
||||||
|
const token = directive.header.correlationToken;
|
||||||
|
|
||||||
|
if (name == "SetTargetTemperature") {
|
||||||
|
console.log({ name: directive.payload });
|
||||||
|
if (directive.payload.upperSetpoint) {
|
||||||
|
deviceData.upperSetpoint = praseTempValue(
|
||||||
|
directive.payload.upperSetpoint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (directive.payload.lowerSetpoint) {
|
||||||
|
deviceData.lowerSetpoint = praseTempValue(
|
||||||
|
directive.payload.lowerSetpoint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (directive.payload.targetSetpoint) {
|
||||||
|
deviceData.targetSetpoint = praseTempValue(
|
||||||
|
directive.payload.targetSetpoint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name == "SetThermostatMode") {
|
||||||
|
console.log(directive.payload.thermostatMode);
|
||||||
|
if (directive.payload.thermostatMode) {
|
||||||
|
deviceData.thermostatMode = directive.payload.thermostatMode.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = thermostat.getStatusMessage(token, true);
|
||||||
|
|
||||||
|
status
|
||||||
|
.addHealthProp("OK") // Device is healthy and reachable
|
||||||
|
.addTemperatureSensorProp(
|
||||||
|
TemperatureSensorScale.CELSIUS,
|
||||||
|
deviceData.current
|
||||||
|
) // Report current temperature
|
||||||
|
.addThermostatModeProp(deviceData.thermostatMode); // Report current thermostat mode
|
||||||
|
if (deviceData.thermostatMode == "AUTO") {
|
||||||
|
status = status
|
||||||
|
.addThermostatControllerProp(
|
||||||
|
"lowerSetpoint",
|
||||||
|
TemperatureSensorScale.CELSIUS,
|
||||||
|
deviceData.lowerSetpoint
|
||||||
|
) // Lower bound of temperature range
|
||||||
|
.addThermostatControllerProp(
|
||||||
|
"upperSetpoint",
|
||||||
|
TemperatureSensorScale.CELSIUS,
|
||||||
|
deviceData.upperSetpoint
|
||||||
|
); // Upper bound of temperature range
|
||||||
|
} else {
|
||||||
|
status = status.addThermostatControllerProp(
|
||||||
|
"targetSetpoint",
|
||||||
|
TemperatureSensorScale.CELSIUS,
|
||||||
|
deviceData.targetSetpoint
|
||||||
|
); // Lower bound of temperature range
|
||||||
|
}
|
||||||
|
status.send(); // Send the full state report back to Alexa
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function praseTempValue(data) {
|
||||||
|
return data.value;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "alex2node",
|
"name": "alex2node",
|
||||||
"version": "1.0.1",
|
"version": "1.1.0",
|
||||||
"description": "A Node.js library for creating Alexa-compatible devices using MQTT, based on Alex2MQTT.",
|
"description": "A Node.js library for creating Alexa-compatible devices using MQTT, based on Alex2MQTT.",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import mqtt, { MqttClient } from "mqtt";
|
import mqtt, { MqttClient } from "mqtt";
|
||||||
import Device from "./Device";
|
import Device from "./Device";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
import { DisplayCategory } from "./DisplayCategory";
|
||||||
|
|
||||||
class Alex2MQTT extends EventEmitter {
|
class Alex2MQTT extends EventEmitter {
|
||||||
private client: MqttClient | null = null;
|
private client: MqttClient | null = null;
|
||||||
|
|
@ -15,7 +16,6 @@ class Alex2MQTT extends EventEmitter {
|
||||||
) {
|
) {
|
||||||
super(); // Initialize EventEmitter
|
super(); // Initialize EventEmitter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
connect(): void {
|
connect(): void {
|
||||||
const options = {
|
const options = {
|
||||||
|
|
@ -69,7 +69,7 @@ class Alex2MQTT extends EventEmitter {
|
||||||
topic + "_r"
|
topic + "_r"
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
console.log(deviceArray);
|
console.log(JSON.stringify(deviceArray, null, 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -106,8 +106,8 @@ class Alex2MQTT extends EventEmitter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
device.emit("ReportState", payload);
|
device.emit("ReportState", payload);
|
||||||
}else{
|
} else {
|
||||||
device.emit("Event", payload,payload.header.namespace);
|
device.emit("Event", payload, payload.header.namespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -120,7 +120,11 @@ class Alex2MQTT extends EventEmitter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerDevice(name: string, endpointId: string): Device {
|
registerDevice(
|
||||||
|
name: string,
|
||||||
|
endpointId: string,
|
||||||
|
displayCategory: DisplayCategory | DisplayCategory[] | null
|
||||||
|
): Device {
|
||||||
if (!this.client) {
|
if (!this.client) {
|
||||||
throw new Error("Must call connect before creating devices");
|
throw new Error("Must call connect before creating devices");
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +133,20 @@ class Alex2MQTT extends EventEmitter {
|
||||||
`[Alex2Node.ts] Creating new device with endpoint: ${endpointId}`
|
`[Alex2Node.ts] Creating new device with endpoint: ${endpointId}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const device = new Device(this.client!, this.rootTopic, name, endpointId);
|
const normalizedCategory =
|
||||||
|
displayCategory === null
|
||||||
|
? null
|
||||||
|
: Array.isArray(displayCategory)
|
||||||
|
? displayCategory
|
||||||
|
: [displayCategory];
|
||||||
|
|
||||||
|
const device = new Device(
|
||||||
|
this.client!,
|
||||||
|
this.rootTopic,
|
||||||
|
name,
|
||||||
|
endpointId,
|
||||||
|
normalizedCategory
|
||||||
|
);
|
||||||
this.devices.push(device);
|
this.devices.push(device);
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,11 +176,12 @@ export class AlexaInterface {
|
||||||
case AlexaInterfaceType.SCENE_CONTROLLER:
|
case AlexaInterfaceType.SCENE_CONTROLLER:
|
||||||
case AlexaInterfaceType.SEEK_CONTROLLER:
|
case AlexaInterfaceType.SEEK_CONTROLLER:
|
||||||
case AlexaInterfaceType.TEMPERATURE_SENSOR:
|
case AlexaInterfaceType.TEMPERATURE_SENSOR:
|
||||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
|
|
||||||
case AlexaInterfaceType.TOGGLE_CONTROLLER:
|
case AlexaInterfaceType.TOGGLE_CONTROLLER:
|
||||||
return "3";
|
return "3";
|
||||||
case AlexaInterfaceType.LAUNCHER:
|
case AlexaInterfaceType.LAUNCHER:
|
||||||
return "1.1";
|
return "1.1";
|
||||||
|
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
|
||||||
|
return "3.2";
|
||||||
case AlexaInterfaceType.ENDPOINT_HEALTH:
|
case AlexaInterfaceType.ENDPOINT_HEALTH:
|
||||||
return "3.3";
|
return "3.3";
|
||||||
default:
|
default:
|
||||||
|
|
@ -210,7 +211,13 @@ export class AlexaInterface {
|
||||||
return ["toggleState"];
|
return ["toggleState"];
|
||||||
case AlexaInterfaceType.TEMPERATURE_SENSOR:
|
case AlexaInterfaceType.TEMPERATURE_SENSOR:
|
||||||
return ["temperature"];
|
return ["temperature"];
|
||||||
|
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
|
||||||
|
return [
|
||||||
|
"lowerSetpoint",
|
||||||
|
"upperSetpoint",
|
||||||
|
"thermostatMode",
|
||||||
|
"adaptiveRecoveryStatus",
|
||||||
|
];
|
||||||
case AlexaInterfaceType.APPLICATION_STATE_REPORTER:
|
case AlexaInterfaceType.APPLICATION_STATE_REPORTER:
|
||||||
case AlexaInterfaceType.AUDIO_PLAY_QUEUE:
|
case AlexaInterfaceType.AUDIO_PLAY_QUEUE:
|
||||||
case AlexaInterfaceType.AUTHORIZATION_CONTROLLER:
|
case AlexaInterfaceType.AUTHORIZATION_CONTROLLER:
|
||||||
|
|
@ -252,7 +259,6 @@ export class AlexaInterface {
|
||||||
case AlexaInterfaceType.SMART_VISION_SNAPSHOT_PROVIDER:
|
case AlexaInterfaceType.SMART_VISION_SNAPSHOT_PROVIDER:
|
||||||
case AlexaInterfaceType.SPEAKER:
|
case AlexaInterfaceType.SPEAKER:
|
||||||
case AlexaInterfaceType.STEP_SPEAKER:
|
case AlexaInterfaceType.STEP_SPEAKER:
|
||||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
|
|
||||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_CONFIGURATION:
|
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_CONFIGURATION:
|
||||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_HVAC_COMPONENTS:
|
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_HVAC_COMPONENTS:
|
||||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_SCHEDULE:
|
case AlexaInterfaceType.THERMOSTAT_CONTROLLER_SCHEDULE:
|
||||||
|
|
@ -277,9 +283,15 @@ export class AlexaInterface {
|
||||||
properties: {
|
properties: {
|
||||||
retrievable: this.retrievable,
|
retrievable: this.retrievable,
|
||||||
proactivelyReported: this.proactivelyReported,
|
proactivelyReported: this.proactivelyReported,
|
||||||
supported: [{ name: this.getProps()[0] }],
|
supported: this.getProps().map(name => ({ name })),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
if(this.type==AlexaInterfaceType.THERMOSTAT_CONTROLLER){
|
||||||
|
doc.configuration={
|
||||||
|
"supportedModes": ["HEAT", "COOL", "AUTO", "OFF"],
|
||||||
|
"supportsScheduling": false
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
doc.instance = this.instance;
|
doc.instance = this.instance;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,15 @@ export enum EndpointHealth {
|
||||||
UNREACHABLE = "UNREACHABLE",
|
UNREACHABLE = "UNREACHABLE",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ThermostatMode {
|
||||||
|
OFF = "OFF",
|
||||||
|
HEAT = "HEAT",
|
||||||
|
COOL = "COOL",
|
||||||
|
AUTO = "AUTO",
|
||||||
|
ECO = "ECO",
|
||||||
|
CUSTOM = "CUSTOM",
|
||||||
|
}
|
||||||
|
|
||||||
export enum PowerController {
|
export enum PowerController {
|
||||||
ON = "ON",
|
ON = "ON",
|
||||||
OFF = "OFF",
|
OFF = "OFF",
|
||||||
|
|
@ -101,6 +110,38 @@ export class AlexaStatusMessage {
|
||||||
this.context.properties.push(prop);
|
this.context.properties.push(prop);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public addThermostatModeProp(
|
||||||
|
mode: string,
|
||||||
|
uncertaintyInMs = 0
|
||||||
|
): this {
|
||||||
|
return this.addProperty(
|
||||||
|
AlexaInterfaceType.THERMOSTAT_CONTROLLER,
|
||||||
|
"thermostatMode",
|
||||||
|
mode,
|
||||||
|
uncertaintyInMs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addThermostatControllerProp(
|
||||||
|
name: "lowerSetpoint" | "upperSetpoint" | "targetSetpoint",
|
||||||
|
scale: TemperatureSensorScale,
|
||||||
|
value: number,
|
||||||
|
uncertaintyInMs = 0
|
||||||
|
): this {
|
||||||
|
const tempValue = {
|
||||||
|
value:
|
||||||
|
scale === TemperatureSensorScale.FAHRENHEIT
|
||||||
|
? (value - 32) * (5 / 9)
|
||||||
|
: value,
|
||||||
|
scale: "CELSIUS",
|
||||||
|
};
|
||||||
|
return this.addProperty(
|
||||||
|
AlexaInterfaceType.THERMOSTAT_CONTROLLER,
|
||||||
|
name,
|
||||||
|
tempValue,
|
||||||
|
uncertaintyInMs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public addHealthProp(health: EndpointHealth, uncertaintyInMs = 0): this {
|
public addHealthProp(health: EndpointHealth, uncertaintyInMs = 0): this {
|
||||||
return this.addProperty(
|
return this.addProperty(
|
||||||
|
|
@ -191,7 +232,7 @@ export class AlexaStatusMessage {
|
||||||
context: this.context,
|
context: this.context,
|
||||||
event: this.event,
|
event: this.event,
|
||||||
};
|
};
|
||||||
const topic = `${this.rootTopic}/${this.endpointId}/alexaResponce`;//Yes this should be response but it is incorrect in both Alex2MQTT and Alex2ESP so for consistency is is wrong here too
|
const topic = `${this.rootTopic}/${this.endpointId}/alexaResponce`; //Yes this should be response but it is incorrect in both Alex2MQTT and Alex2ESP so for consistency is is wrong here too
|
||||||
const payloadStr = JSON.stringify(payload);
|
const payloadStr = JSON.stringify(payload);
|
||||||
|
|
||||||
this.mqttClient.publish(topic, payloadStr, (err) => {
|
this.mqttClient.publish(topic, payloadStr, (err) => {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class Device extends EventEmitter {
|
||||||
private rootTopic: string,
|
private rootTopic: string,
|
||||||
public name: string,
|
public name: string,
|
||||||
public endpointId: string,
|
public endpointId: string,
|
||||||
public displayCategory: DisplayCategory = DisplayCategory.LIGHT,
|
public displayCategory: Array<DisplayCategory> | null,
|
||||||
public description: string = "Alexa to Node.js bridge",
|
public description: string = "Alexa to Node.js bridge",
|
||||||
public manufacturerName: string = "Alex2Node",
|
public manufacturerName: string = "Alex2Node",
|
||||||
public manufacturer: string = "Alex2Node",
|
public manufacturer: string = "Alex2Node",
|
||||||
|
|
@ -34,12 +34,12 @@ class Device extends EventEmitter {
|
||||||
return this.endpointId;
|
return this.endpointId;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDisplayCategory(category: DisplayCategory): void {
|
setDisplayCategory(category: DisplayCategory | DisplayCategory[]): void {
|
||||||
this.displayCategory = category;
|
this.displayCategory = Array.isArray(category) ? category : [category];
|
||||||
}
|
}
|
||||||
|
|
||||||
getDisplayCategory(): DisplayCategory {
|
getDisplayCategory(): Array<DisplayCategory> {
|
||||||
return this.displayCategory;
|
return this.displayCategory ||[DisplayCategory.LIGHT];
|
||||||
}
|
}
|
||||||
|
|
||||||
setDescription(description: string): void {
|
setDescription(description: string): void {
|
||||||
|
|
@ -111,7 +111,7 @@ class Device extends EventEmitter {
|
||||||
friendlyName: this.name,
|
friendlyName: this.name,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
manufacturerName: this.manufacturerName,
|
manufacturerName: this.manufacturerName,
|
||||||
displayCategories: [this.displayCategory],
|
displayCategories: this.getDisplayCategory(),
|
||||||
additionalAttributes: {
|
additionalAttributes: {
|
||||||
manufacturer: this.manufacturer,
|
manufacturer: this.manufacturer,
|
||||||
model: this.model,
|
model: this.model,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export { default as Alex2MQTT } from './Alex2Node';
|
export { default as Alex2MQTT } from './Alex2Node';
|
||||||
export { AlexaInterfaceType } from './AlexaInterface';
|
export { AlexaInterfaceType } from './AlexaInterface';
|
||||||
export { ActionMapping, AlexaActions } from './ActionMapping';
|
export { ActionMapping, AlexaActions } from './ActionMapping';
|
||||||
export { PowerController } from './AlexaStatusMessage';
|
export {DisplayCategory} from "./DisplayCategory";
|
||||||
|
export { PowerController,EndpointHealth,TemperatureSensorScale,ThermostatMode } from './AlexaStatusMessage';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue