diff --git a/dist/AlexaErrorResponse.js b/dist/AlexaErrorResponse.js new file mode 100644 index 0000000..40577f5 --- /dev/null +++ b/dist/AlexaErrorResponse.js @@ -0,0 +1,125 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AlexaErrorResponse = exports.AlexaErrorType = void 0; +const uuid_1 = require("uuid"); +var AlexaErrorType; +(function (AlexaErrorType) { + AlexaErrorType["ALREADY_IN_OPERATION"] = "ALREADY_IN_OPERATION"; + AlexaErrorType["AUTHORIZATION_REQUIRED"] = "AUTHORIZATION_REQUIRED"; + AlexaErrorType["BRIDGE_UNREACHABLE"] = "BRIDGE_UNREACHABLE"; + AlexaErrorType["BYPASS_NEEDED"] = "BYPASS_NEEDED"; + AlexaErrorType["CLOUD_CONTROL_DISABLED"] = "CLOUD_CONTROL_DISABLED"; + AlexaErrorType["CHILD_LOCK"] = "CHILD_LOCK"; + AlexaErrorType["CONFIGURATION_UPDATE_NOT_ALLOWED"] = "CONFIGURATION_UPDATE_NOT_ALLOWED"; + AlexaErrorType["COOK_DURATION_TOO_LONG"] = "COOK_DURATION_TOO_LONG"; + AlexaErrorType["COOLING_LOCKOUT_TEMPERATURE_VALUE_OUT_OF_RANGE"] = "COOLING_LOCKOUT_TEMPERATURE_VALUE_OUT_OF_RANGE"; + AlexaErrorType["COOLING_STAGES_EXCEEDS_LIMIT"] = "COOLING_STAGES_EXCEEDS_LIMIT"; + AlexaErrorType["DATA_DELETION_NOT_SUPPORTED"] = "DATA_DELETION_NOT_SUPPORTED"; + AlexaErrorType["DATA_RETRIEVAL_NOT_SUPPORTED"] = "DATA_RETRIEVAL_NOT_SUPPORTED"; + AlexaErrorType["DEVICE_STUCK"] = "DEVICE_STUCK"; + AlexaErrorType["DISABLED_BY_USER"] = "DISABLED_BY_USER"; + AlexaErrorType["DO_NOT_DISTURB_MODE"] = "DO_NOT_DISTURB_MODE"; + AlexaErrorType["DOOR_CLOSED_TOO_LONG"] = "DOOR_CLOSED_TOO_LONG"; + AlexaErrorType["DOOR_OPEN"] = "DOOR_OPEN"; + AlexaErrorType["DUAL_SETPOINTS_UNSUPPORTED"] = "DUAL_SETPOINTS_UNSUPPORTED"; + AlexaErrorType["ENDPOINT_BUSY"] = "ENDPOINT_BUSY"; + AlexaErrorType["ENDPOINT_CONTROL_UNAVAILABLE"] = "ENDPOINT_CONTROL_UNAVAILABLE"; + AlexaErrorType["ENDPOINT_LOW_POWER"] = "ENDPOINT_LOW_POWER"; + AlexaErrorType["ENDPOINT_UNREACHABLE"] = "ENDPOINT_UNREACHABLE"; + AlexaErrorType["EXCEEDED_PIN_ATTEMPTS"] = "EXCEEDED_PIN_ATTEMPTS"; + AlexaErrorType["EXPIRED_AUTHORIZATION_CREDENTIAL"] = "EXPIRED_AUTHORIZATION_CREDENTIAL"; + AlexaErrorType["FAILED_TO_BOOTSTRAP_COMMISSIONING_PROCESS"] = "FAILED_TO_BOOTSTRAP_COMMISSIONING_PROCESS"; + AlexaErrorType["FIRMWARE_OUT_OF_DATE"] = "FIRMWARE_OUT_OF_DATE"; + AlexaErrorType["HARDWARE_MALFUNCTION"] = "HARDWARE_MALFUNCTION"; + AlexaErrorType["HEATING_LOCKOUT_TEMPERATURE_VALUE_OUT_OF_RANGE"] = "HEATING_LOCKOUT_TEMPERATURE_VALUE_OUT_OF_RANGE"; + AlexaErrorType["HEATING_STAGES_EXCEEDS_LIMIT"] = "HEATING_STAGES_EXCEEDS_LIMIT"; + AlexaErrorType["INSUFFICIENT_PERMISSIONS"] = "INSUFFICIENT_PERMISSIONS"; + AlexaErrorType["INSUFFICIENT_RESOURCE"] = "INSUFFICIENT_RESOURCE"; + AlexaErrorType["INSUFFICIENT_SPACE"] = "INSUFFICIENT_SPACE"; + AlexaErrorType["INTERNAL_ERROR"] = "INTERNAL_ERROR"; + AlexaErrorType["INVALID_AUTHORIZATION_CREDENTIAL"] = "INVALID_AUTHORIZATION_CREDENTIAL"; + AlexaErrorType["INVALID_AUXILIARY_HEATING_SYSTEM_TYPE"] = "INVALID_AUXILIARY_HEATING_SYSTEM_TYPE"; + AlexaErrorType["INVALID_DIRECTIVE"] = "INVALID_DIRECTIVE"; + AlexaErrorType["INVALID_SYSTEM_TYPE"] = "INVALID_SYSTEM_TYPE"; + AlexaErrorType["INVALID_TARGET_STATE"] = "INVALID_TARGET_STATE"; + AlexaErrorType["INVALID_TEMPERATURE_SCALE"] = "INVALID_TEMPERATURE_SCALE"; + AlexaErrorType["INVALID_TERMINAL_CONNECTION"] = "INVALID_TERMINAL_CONNECTION"; + AlexaErrorType["INVALID_VALUE"] = "INVALID_VALUE"; + AlexaErrorType["MAINTENANCE_REQUIRED"] = "MAINTENANCE_REQUIRED"; + AlexaErrorType["MAX_COMMISSIONING_LIMIT_REACHED"] = "MAX_COMMISSIONING_LIMIT_REACHED"; + AlexaErrorType["MISSING_SETUP_INFORMATION"] = "MISSING_SETUP_INFORMATION"; + AlexaErrorType["NO_SUCH_ENDPOINT"] = "NO_SUCH_ENDPOINT"; + AlexaErrorType["NOT_CALIBRATED"] = "NOT_CALIBRATED"; + AlexaErrorType["NOT_IN_OPERATION"] = "NOT_IN_OPERATION"; + AlexaErrorType["NOT_READY"] = "NOT_READY"; + AlexaErrorType["NOT_SUPPORTED_IN_CURRENT_MODE"] = "NOT_SUPPORTED_IN_CURRENT_MODE"; + AlexaErrorType["NOT_SUPPORTED_WITH_CURRENT_BATTERY_CHARGE_STATE"] = "NOT_SUPPORTED_WITH_CURRENT_BATTERY_CHARGE_STATE"; + AlexaErrorType["OBSTACLE_DETECTED"] = "OBSTACLE_DETECTED"; + AlexaErrorType["PARTNER_APPLICATION_REDIRECTION"] = "PARTNER_APPLICATION_REDIRECTION"; + AlexaErrorType["PIN_SETUP_REQUIRED"] = "PIN_SETUP_REQUIRED"; + AlexaErrorType["POWER_LEVEL_NOT_SUPPORTED"] = "POWER_LEVEL_NOT_SUPPORTED"; + AlexaErrorType["PREHEAT_REQUIRED"] = "PREHEAT_REQUIRED"; + AlexaErrorType["PROBE_REQUIRED"] = "PROBE_REQUIRED"; + AlexaErrorType["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED"; + AlexaErrorType["REMOTE_START_NOT_SUPPORTED"] = "REMOTE_START_NOT_SUPPORTED"; + AlexaErrorType["REMOVE_PROBE"] = "REMOVE_PROBE"; + AlexaErrorType["REMOTE_START_DISABLED"] = "REMOTE_START_DISABLED"; + AlexaErrorType["REQUESTED_SETPOINTS_TOO_CLOSE"] = "REQUESTED_SETPOINTS_TOO_CLOSE"; + AlexaErrorType["SAFETY_BEAM_BREACHED"] = "SAFETY_BEAM_BREACHED"; + AlexaErrorType["SUBSCRIPTION_REQUIRED"] = "SUBSCRIPTION_REQUIRED"; + AlexaErrorType["TEMPERATURE_VALUE_OUT_OF_RANGE"] = "TEMPERATURE_VALUE_OUT_OF_RANGE"; + AlexaErrorType["THERMOSTAT_IS_OFF"] = "THERMOSTAT_IS_OFF"; + AlexaErrorType["TOO_MANY_FAILED_ATTEMPTS"] = "TOO_MANY_FAILED_ATTEMPTS"; + AlexaErrorType["TRIPLE_SETPOINTS_UNSUPPORTED"] = "TRIPLE_SETPOINTS_UNSUPPORTED"; + AlexaErrorType["UNABLE_TO_CHARGE"] = "UNABLE_TO_CHARGE"; + AlexaErrorType["UNAUTHORIZED"] = "UNAUTHORIZED"; + AlexaErrorType["UNCLEARED_ALARM"] = "UNCLEARED_ALARM"; + AlexaErrorType["UNSUPPORTED_THERMOSTAT_MODE"] = "UNSUPPORTED_THERMOSTAT_MODE"; + AlexaErrorType["UNCLEARED_TROUBLE"] = "UNCLEARED_TROUBLE"; + AlexaErrorType["UNWILLING_TO_SET_SCHEDULE"] = "UNWILLING_TO_SET_SCHEDULE"; + AlexaErrorType["UNWILLING_TO_SET_VALUE"] = "UNWILLING_TO_SET_VALUE"; + AlexaErrorType["VALUE_OUT_OF_RANGE"] = "VALUE_OUT_OF_RANGE"; +})(AlexaErrorType || (exports.AlexaErrorType = AlexaErrorType = {})); +class AlexaErrorResponse { + constructor(correlationToken, rootTopic, endpointId, mqttClient) { + this.rootTopic = rootTopic; + this.endpointId = endpointId; + this.mqttClient = mqttClient; + this.event = { + header: { + namespace: "Alexa", + name: "ErrorResponse", + messageId: this.generateMessageId(), + correlationToken, + payloadVersion: "3", + }, + endpoint: { + endpointId, + }, + payload: {}, + }; + } + generateMessageId() { + return (0, uuid_1.v4)(); + } + setErrorMessage(type, message, otherParams = {}) { + this.event.payload = Object.assign({ type, + message }, otherParams); + } + send(sendAsync = false) { + const payload = { + event: this.event, + }; + const topic = `${this.rootTopic}/${this.endpointId}/${sendAsync ? "deferredResponse" : "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); + this.mqttClient.publish(topic, payloadStr, (err) => { + if (err) { + console.error("[AlexaErrorResponse] Failed to publish status message:", err); + } + else { + console.log(`[AlexaErrorResponse] Sent message to ${topic}`); + } + }); + } +} +exports.AlexaErrorResponse = AlexaErrorResponse; diff --git a/dist/Device.js b/dist/Device.js index 94084cf..393310b 100644 --- a/dist/Device.js +++ b/dist/Device.js @@ -4,6 +4,7 @@ const AlexaInterface_1 = require("./AlexaInterface"); const DisplayCategory_1 = require("./DisplayCategory"); const events_1 = require("events"); const AlexaStatusMessage_1 = require("./AlexaStatusMessage"); +const AlexaErrorResponse_1 = require("./AlexaErrorResponse"); class Device extends events_1.EventEmitter { constructor(mqttClient, rootTopic, name, endpointId, displayCategory, description = "Alexa to Node.js bridge", manufacturerName = "Alex2Node", manufacturer = "Alex2Node", model = "Alex2Node_v1.0.0") { super(); @@ -40,6 +41,9 @@ class Device extends events_1.EventEmitter { getDescription() { return this.description; } + getErrorMessage(correlationToken) { + return new AlexaErrorResponse_1.AlexaErrorResponse(correlationToken, this.rootTopic, this.endpointId, this.mqttClient); + } getStatusMessage(correlationToken, isResponse = false, isDeferred = false) { return new AlexaStatusMessage_1.AlexaStatusMessage(correlationToken, this.rootTopic, this.endpointId, this.mqttClient, isResponse, isDeferred); } diff --git a/dist/index.js b/dist/index.js index 6a32109..12e62bf 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.ThermostatMode = exports.TemperatureSensorScale = exports.EndpointHealth = exports.PowerController = exports.DisplayCategory = exports.AlexaActions = exports.ActionMapping = exports.AlexaInterfaceType = exports.Alex2MQTT = void 0; +exports.AlexaErrorResponse = exports.AlexaErrorType = 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"); Object.defineProperty(exports, "Alex2MQTT", { enumerable: true, get: function () { return __importDefault(Alex2Node_1).default; } }); var AlexaInterface_1 = require("./AlexaInterface"); @@ -18,3 +18,6 @@ Object.defineProperty(exports, "PowerController", { enumerable: true, get: funct 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; } }); +var AlexaErrorResponse_1 = require("./AlexaErrorResponse"); +Object.defineProperty(exports, "AlexaErrorType", { enumerable: true, get: function () { return AlexaErrorResponse_1.AlexaErrorType; } }); +Object.defineProperty(exports, "AlexaErrorResponse", { enumerable: true, get: function () { return AlexaErrorResponse_1.AlexaErrorResponse; } }); diff --git a/examples/ExamplePowerControllerWithErrors.js b/examples/ExamplePowerControllerWithErrors.js new file mode 100644 index 0000000..ae0a35b --- /dev/null +++ b/examples/ExamplePowerControllerWithErrors.js @@ -0,0 +1,80 @@ +// Import required modules and types from the compiled TypeScript distribution (via dist/index.js) +const { + Alex2MQTT, + AlexaInterfaceType, + PowerController +} = 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 + +// 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 (bedroom light) +const deviceName = "Bedroom Light"; + +// Register the device with a unique endpoint ID (you can use something like "endpoint1") +const bedroomLight = alex2NodeClient.registerDevice(deviceName, "endpoint2"); + +// Add the PowerController capability (for turning on/off the device) +bedroomLight.addCapability(AlexaInterfaceType.POWER_CONTROLLER); + +// Log the device name to verify registration +console.log(bedroomLight.getName()); + +/** + * Handle Alexa's ReportState directive. + * This occurs when Alexa queries the current state of the device (e.g., during routines or device status checks). + */ +bedroomLight.on("ReportState", (payload) => { + console.log("ReportState received!", payload); + + const { correlationToken } = payload.header; + + const status = bedroomLight.getStatusMessage(correlationToken); + + status + .addHealthProp("OK") // Device is healthy + .addPowerControllerProp(outputState); // Report the current power state (ON/OFF) + + status.send(); // Send the state report back to Alexa +}); + +/** + * Handle incoming control directives (e.g., TurnOn, TurnOff). + * These directives come from Alexa when a user issues a command. + */ +bedroomLight.on("Event", (directive, interfaceType) => { + console.log("Event received", { directive, interfaceType }); + + // Ensure the interfaceType is either PowerController or other valid interfaces + if (interfaceType === AlexaInterfaceType.POWER_CONTROLLER) { + const name = directive.header.name; + const token = directive.header.correlationToken; + + // Update the internal state based on the command (TurnOn / TurnOff) + 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"); + } + + // Respond to the directive with the updated device status + const status = bedroomLight.getStatusMessage(token, true); + status + .addHealthProp("OK") + .addPowerControllerProp(outputState) + .send(); + } +}); diff --git a/examples/ExampleThermostat.js b/examples/ExampleThermostat.js index 006858d..ba3caef 100644 --- a/examples/ExampleThermostat.js +++ b/examples/ExampleThermostat.js @@ -45,11 +45,11 @@ console.log(thermostat.getName()); */ const deviceData = { - lowerSetpoint: 20, - upperSetpoint: 20, - targetSetpoint: 20, - current: 20, - thermostatMode: ThermostatMode.AUTO, + lowerSetpoint: 50, + upperSetpoint: 70, + targetSetpoint: 60, + current: 69, + thermostatMode: ThermostatMode.HEAT, }; thermostat.on("ReportState", (payload) => { // console.log("ReportState received!", payload); @@ -101,17 +101,17 @@ thermostat.on("Event", (directive, interfaceType) => { if (name == "SetTargetTemperature") { console.log({ name: directive.payload }); if (directive.payload.upperSetpoint) { - deviceData.upperSetpoint = praseTempValue( + deviceData.upperSetpoint = parseTempValue( directive.payload.upperSetpoint ); } if (directive.payload.lowerSetpoint) { - deviceData.lowerSetpoint = praseTempValue( + deviceData.lowerSetpoint = parseTempValue( directive.payload.lowerSetpoint ); } if (directive.payload.targetSetpoint) { - deviceData.targetSetpoint = praseTempValue( + deviceData.targetSetpoint = parseTempValue( directive.payload.targetSetpoint ); } @@ -128,7 +128,7 @@ thermostat.on("Event", (directive, interfaceType) => { status .addHealthProp("OK") // Device is healthy and reachable .addTemperatureSensorProp( - TemperatureSensorScale.CELSIUS, + TemperatureSensorScale.FAHRENHEIT, deviceData.current ) // Report current temperature .addThermostatModeProp(deviceData.thermostatMode); // Report current thermostat mode @@ -136,18 +136,18 @@ thermostat.on("Event", (directive, interfaceType) => { status = status .addThermostatControllerProp( "lowerSetpoint", - TemperatureSensorScale.CELSIUS, + TemperatureSensorScale.FAHRENHEIT, deviceData.lowerSetpoint ) // Lower bound of temperature range .addThermostatControllerProp( "upperSetpoint", - TemperatureSensorScale.CELSIUS, + TemperatureSensorScale.FAHRENHEIT, deviceData.upperSetpoint ); // Upper bound of temperature range } else { status = status.addThermostatControllerProp( "targetSetpoint", - TemperatureSensorScale.CELSIUS, + TemperatureSensorScale.FAHRENHEIT, deviceData.targetSetpoint ); // Lower bound of temperature range } @@ -155,6 +155,13 @@ thermostat.on("Event", (directive, interfaceType) => { } }); -function praseTempValue(data) { - return data.value; +function parseTempValue(data) { + console.log(data); + + if (data.scale === "FAHRENHEIT") { + return data.value; + } + + const fahrenheit = (data.value * 9) / 5 + 32; + return fahrenheit; } diff --git a/package.json b/package.json index 1274a8a..873d0b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "alex2node", - "version": "1.2.0", + "version": "1.3.0", "description": "A Node.js library for creating Alexa-compatible devices using MQTT, based on Alex2MQTT.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/AlexaErrorResponse.ts b/src/AlexaErrorResponse.ts new file mode 100644 index 0000000..ee845df --- /dev/null +++ b/src/AlexaErrorResponse.ts @@ -0,0 +1,148 @@ +import { v4 as uuidv4 } from "uuid"; +import { AlexaInterface, AlexaInterfaceType } from "./AlexaInterface"; +import { MqttClient } from "mqtt"; + +export enum AlexaErrorType { + ALREADY_IN_OPERATION = "ALREADY_IN_OPERATION", + AUTHORIZATION_REQUIRED = "AUTHORIZATION_REQUIRED", + BRIDGE_UNREACHABLE = "BRIDGE_UNREACHABLE", + BYPASS_NEEDED = "BYPASS_NEEDED", + CLOUD_CONTROL_DISABLED = "CLOUD_CONTROL_DISABLED", + CHILD_LOCK = "CHILD_LOCK", + CONFIGURATION_UPDATE_NOT_ALLOWED = "CONFIGURATION_UPDATE_NOT_ALLOWED", + COOK_DURATION_TOO_LONG = "COOK_DURATION_TOO_LONG", + COOLING_LOCKOUT_TEMPERATURE_VALUE_OUT_OF_RANGE = "COOLING_LOCKOUT_TEMPERATURE_VALUE_OUT_OF_RANGE", + COOLING_STAGES_EXCEEDS_LIMIT = "COOLING_STAGES_EXCEEDS_LIMIT", + DATA_DELETION_NOT_SUPPORTED = "DATA_DELETION_NOT_SUPPORTED", + DATA_RETRIEVAL_NOT_SUPPORTED = "DATA_RETRIEVAL_NOT_SUPPORTED", + DEVICE_STUCK = "DEVICE_STUCK", + DISABLED_BY_USER = "DISABLED_BY_USER", + DO_NOT_DISTURB_MODE = "DO_NOT_DISTURB_MODE", + DOOR_CLOSED_TOO_LONG = "DOOR_CLOSED_TOO_LONG", + DOOR_OPEN = "DOOR_OPEN", + DUAL_SETPOINTS_UNSUPPORTED = "DUAL_SETPOINTS_UNSUPPORTED", + ENDPOINT_BUSY = "ENDPOINT_BUSY", + ENDPOINT_CONTROL_UNAVAILABLE = "ENDPOINT_CONTROL_UNAVAILABLE", + ENDPOINT_LOW_POWER = "ENDPOINT_LOW_POWER", + ENDPOINT_UNREACHABLE = "ENDPOINT_UNREACHABLE", + EXCEEDED_PIN_ATTEMPTS = "EXCEEDED_PIN_ATTEMPTS", + EXPIRED_AUTHORIZATION_CREDENTIAL = "EXPIRED_AUTHORIZATION_CREDENTIAL", + FAILED_TO_BOOTSTRAP_COMMISSIONING_PROCESS = "FAILED_TO_BOOTSTRAP_COMMISSIONING_PROCESS", + FIRMWARE_OUT_OF_DATE = "FIRMWARE_OUT_OF_DATE", + HARDWARE_MALFUNCTION = "HARDWARE_MALFUNCTION", + HEATING_LOCKOUT_TEMPERATURE_VALUE_OUT_OF_RANGE = "HEATING_LOCKOUT_TEMPERATURE_VALUE_OUT_OF_RANGE", + HEATING_STAGES_EXCEEDS_LIMIT = "HEATING_STAGES_EXCEEDS_LIMIT", + INSUFFICIENT_PERMISSIONS = "INSUFFICIENT_PERMISSIONS", + INSUFFICIENT_RESOURCE = "INSUFFICIENT_RESOURCE", + INSUFFICIENT_SPACE = "INSUFFICIENT_SPACE", + INTERNAL_ERROR = "INTERNAL_ERROR", + INVALID_AUTHORIZATION_CREDENTIAL = "INVALID_AUTHORIZATION_CREDENTIAL", + INVALID_AUXILIARY_HEATING_SYSTEM_TYPE = "INVALID_AUXILIARY_HEATING_SYSTEM_TYPE", + INVALID_DIRECTIVE = "INVALID_DIRECTIVE", + INVALID_SYSTEM_TYPE = "INVALID_SYSTEM_TYPE", + INVALID_TARGET_STATE = "INVALID_TARGET_STATE", + INVALID_TEMPERATURE_SCALE = "INVALID_TEMPERATURE_SCALE", + INVALID_TERMINAL_CONNECTION = "INVALID_TERMINAL_CONNECTION", + INVALID_VALUE = "INVALID_VALUE", + MAINTENANCE_REQUIRED = "MAINTENANCE_REQUIRED", + MAX_COMMISSIONING_LIMIT_REACHED = "MAX_COMMISSIONING_LIMIT_REACHED", + MISSING_SETUP_INFORMATION = "MISSING_SETUP_INFORMATION", + NO_SUCH_ENDPOINT = "NO_SUCH_ENDPOINT", + NOT_CALIBRATED = "NOT_CALIBRATED", + NOT_IN_OPERATION = "NOT_IN_OPERATION", + NOT_READY = "NOT_READY", + NOT_SUPPORTED_IN_CURRENT_MODE = "NOT_SUPPORTED_IN_CURRENT_MODE", + NOT_SUPPORTED_WITH_CURRENT_BATTERY_CHARGE_STATE = "NOT_SUPPORTED_WITH_CURRENT_BATTERY_CHARGE_STATE", + OBSTACLE_DETECTED = "OBSTACLE_DETECTED", + PARTNER_APPLICATION_REDIRECTION = "PARTNER_APPLICATION_REDIRECTION", + PIN_SETUP_REQUIRED = "PIN_SETUP_REQUIRED", + POWER_LEVEL_NOT_SUPPORTED = "POWER_LEVEL_NOT_SUPPORTED", + PREHEAT_REQUIRED = "PREHEAT_REQUIRED", + PROBE_REQUIRED = "PROBE_REQUIRED", + RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED", + REMOTE_START_NOT_SUPPORTED = "REMOTE_START_NOT_SUPPORTED", + REMOVE_PROBE = "REMOVE_PROBE", + REMOTE_START_DISABLED = "REMOTE_START_DISABLED", + REQUESTED_SETPOINTS_TOO_CLOSE = "REQUESTED_SETPOINTS_TOO_CLOSE", + SAFETY_BEAM_BREACHED = "SAFETY_BEAM_BREACHED", + SUBSCRIPTION_REQUIRED = "SUBSCRIPTION_REQUIRED", + TEMPERATURE_VALUE_OUT_OF_RANGE = "TEMPERATURE_VALUE_OUT_OF_RANGE", + THERMOSTAT_IS_OFF = "THERMOSTAT_IS_OFF", + TOO_MANY_FAILED_ATTEMPTS = "TOO_MANY_FAILED_ATTEMPTS", + TRIPLE_SETPOINTS_UNSUPPORTED = "TRIPLE_SETPOINTS_UNSUPPORTED", + UNABLE_TO_CHARGE = "UNABLE_TO_CHARGE", + UNAUTHORIZED = "UNAUTHORIZED", + UNCLEARED_ALARM = "UNCLEARED_ALARM", + UNSUPPORTED_THERMOSTAT_MODE = "UNSUPPORTED_THERMOSTAT_MODE", + UNCLEARED_TROUBLE = "UNCLEARED_TROUBLE", + UNWILLING_TO_SET_SCHEDULE = "UNWILLING_TO_SET_SCHEDULE", + UNWILLING_TO_SET_VALUE = "UNWILLING_TO_SET_VALUE", + VALUE_OUT_OF_RANGE = "VALUE_OUT_OF_RANGE", +} + +export class AlexaErrorResponse { + private event: any; + private rootTopic: string; + private endpointId: string; + private mqttClient: MqttClient; + + constructor( + correlationToken: string, + rootTopic: string, + endpointId: string, + mqttClient: MqttClient + ) { + this.rootTopic = rootTopic; + this.endpointId = endpointId; + this.mqttClient = mqttClient; + + this.event = { + header: { + namespace: "Alexa", + name: "ErrorResponse", + messageId: this.generateMessageId(), + correlationToken, + payloadVersion: "3", + }, + endpoint: { + endpointId, + }, + payload: {}, + }; + } + private generateMessageId(): string { + return uuidv4(); + } + setErrorMessage( + type: string, + message: string, + otherParams: Record = {} + ) { + this.event.payload = { + type, + message, + ...otherParams, + }; + } + + send(sendAsync = false) { + const payload = { + event: this.event, + }; + const topic = `${this.rootTopic}/${this.endpointId}/${ + sendAsync ? "deferredResponse" : "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); + + this.mqttClient.publish(topic, payloadStr, (err) => { + if (err) { + console.error( + "[AlexaErrorResponse] Failed to publish status message:", + err + ); + } else { + console.log(`[AlexaErrorResponse] Sent message to ${topic}`); + } + }); + } +} diff --git a/src/Device.ts b/src/Device.ts index cdcc22e..74cb6e5 100644 --- a/src/Device.ts +++ b/src/Device.ts @@ -3,6 +3,7 @@ import { DisplayCategory } from "./DisplayCategory"; import { EventEmitter } from "events"; import { MqttClient } from "mqtt"; import { AlexaStatusMessage } from "./AlexaStatusMessage"; +import { AlexaErrorResponse } from "./AlexaErrorResponse"; class Device extends EventEmitter { protected softwareVersion: string = "1.0.0"; @@ -37,9 +38,9 @@ class Device extends EventEmitter { setDisplayCategory(category: DisplayCategory | DisplayCategory[]): void { this.displayCategory = Array.isArray(category) ? category : [category]; } - + getDisplayCategory(): Array { - return this.displayCategory ||[DisplayCategory.LIGHT]; + return this.displayCategory || [DisplayCategory.LIGHT]; } setDescription(description: string): void { @@ -49,10 +50,18 @@ class Device extends EventEmitter { getDescription(): string { return this.description; } + getErrorMessage(correlationToken: string): AlexaErrorResponse { + return new AlexaErrorResponse( + correlationToken, + this.rootTopic, + this.endpointId, + this.mqttClient + ); + } getStatusMessage( correlationToken: string, isResponse = false, - isDeferred = false, + isDeferred = false ): AlexaStatusMessage { return new AlexaStatusMessage( correlationToken, diff --git a/src/index.ts b/src/index.ts index 4dde791..f381872 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,3 +3,4 @@ export { AlexaInterfaceType } from './AlexaInterface'; export { ActionMapping, AlexaActions } from './ActionMapping'; export {DisplayCategory} from "./DisplayCategory"; export { PowerController,EndpointHealth,TemperatureSensorScale,ThermostatMode } from './AlexaStatusMessage'; +export { AlexaErrorType,AlexaErrorResponse} from './AlexaErrorResponse';