Add full support for mode controller
This commit is contained in:
parent
e7a2079ff7
commit
3bcd767127
8 changed files with 198 additions and 6 deletions
3
dist/Alex2Node.js
vendored
3
dist/Alex2Node.js
vendored
|
|
@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
const mqtt_1 = __importDefault(require("mqtt"));
|
||||
const Device_1 = __importDefault(require("./Device"));
|
||||
const events_1 = require("events");
|
||||
const DisplayCategory_1 = require("./DisplayCategory");
|
||||
class Alex2MQTT extends events_1.EventEmitter {
|
||||
constructor(username, password, rootTopic, debugLogging) {
|
||||
super(); // Initialize EventEmitter
|
||||
|
|
@ -114,7 +115,7 @@ class Alex2MQTT extends events_1.EventEmitter {
|
|||
: Array.isArray(displayCategory)
|
||||
? displayCategory
|
||||
: [displayCategory];
|
||||
const device = new Device_1.default(this.client, this.rootTopic, name, endpointId, normalizedCategory);
|
||||
const device = new Device_1.default(this.client, this.rootTopic, name, endpointId, normalizedCategory || [DisplayCategory_1.DisplayCategory.LIGHT]);
|
||||
this.devices.push(device);
|
||||
return device;
|
||||
}
|
||||
|
|
|
|||
14
dist/AlexaInterface.js
vendored
14
dist/AlexaInterface.js
vendored
|
|
@ -82,6 +82,7 @@ class AlexaInterface {
|
|||
this.instance = instance;
|
||||
this.friendlyNames = [];
|
||||
this.actionMappings = [];
|
||||
this.supportedModes = [];
|
||||
}
|
||||
addActionMapping(mapping) {
|
||||
this.actionMappings.push(mapping);
|
||||
|
|
@ -89,6 +90,9 @@ class AlexaInterface {
|
|||
addFriendlyName(name, locale) {
|
||||
this.friendlyNames.push({ text: name, locale });
|
||||
}
|
||||
addSupportedModes(modes) {
|
||||
this.supportedModes = modes;
|
||||
}
|
||||
setInstance(name) {
|
||||
this.instance = name;
|
||||
}
|
||||
|
|
@ -196,6 +200,8 @@ class AlexaInterface {
|
|||
return ["connectivity"];
|
||||
case AlexaInterfaceType.TOGGLE_CONTROLLER:
|
||||
return ["toggleState"];
|
||||
case AlexaInterfaceType.MODE_CONTROLLER:
|
||||
return ["mode"];
|
||||
case AlexaInterfaceType.TEMPERATURE_SENSOR:
|
||||
return ["temperature"];
|
||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
|
||||
|
|
@ -228,7 +234,6 @@ class AlexaInterface {
|
|||
case AlexaInterfaceType.LOCK_CONTROLLER:
|
||||
case AlexaInterfaceType.MEDIA_PLAYBACK:
|
||||
case AlexaInterfaceType.MEDIA_SEARCH:
|
||||
case AlexaInterfaceType.MODE_CONTROLLER:
|
||||
case AlexaInterfaceType.MOTION_SENSOR:
|
||||
case AlexaInterfaceType.PERCENTAGE_CONTROLLER:
|
||||
case AlexaInterfaceType.PLAYBACK_CONTROLLER:
|
||||
|
|
@ -277,6 +282,12 @@ class AlexaInterface {
|
|||
"supportsScheduling": false
|
||||
};
|
||||
}
|
||||
else if (this.supportedModes.length > 0) {
|
||||
doc["configuration"] = {
|
||||
ordered: true,
|
||||
supportedModes: this.supportedModes,
|
||||
};
|
||||
}
|
||||
if (this.instance) {
|
||||
doc.instance = this.instance;
|
||||
}
|
||||
|
|
@ -291,6 +302,7 @@ class AlexaInterface {
|
|||
};
|
||||
friendlyNamesArray.push(friendlyNameObj);
|
||||
});
|
||||
capabilityResources.friendlyNames = friendlyNamesArray;
|
||||
doc["capabilityResources"] = capabilityResources;
|
||||
}
|
||||
if (this.actionMappings.length > 0) {
|
||||
|
|
|
|||
3
dist/AlexaStatusMessage.js
vendored
3
dist/AlexaStatusMessage.js
vendored
|
|
@ -71,6 +71,9 @@ class AlexaStatusMessage {
|
|||
this.context.properties.push(prop);
|
||||
return this;
|
||||
}
|
||||
addModeControllerProp(instance, value, uncertaintyInMs = 0) {
|
||||
return this.addProperty(AlexaInterface_1.AlexaInterfaceType.MODE_CONTROLLER, "mode", value, uncertaintyInMs, instance);
|
||||
}
|
||||
addThermostatModeProp(mode, uncertaintyInMs = 0) {
|
||||
return this.addProperty(AlexaInterface_1.AlexaInterfaceType.THERMOSTAT_CONTROLLER, "thermostatMode", mode, uncertaintyInMs);
|
||||
}
|
||||
|
|
|
|||
118
examples/ExampleModeController.js
Normal file
118
examples/ExampleModeController.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
// Import required modules and types from the compiled TypeScript distribution (via dist/index.js)
|
||||
const {
|
||||
Alex2MQTT,
|
||||
AlexaInterfaceType,
|
||||
PowerController,
|
||||
DisplayCategory,
|
||||
} = require("../dist/index.js");
|
||||
|
||||
require("dotenv").config(); // Load environment variables from .env file
|
||||
|
||||
// Simulated in-memory device state (used for example/demo purposes)
|
||||
let currentMode = "mode.fanmode.Normal";
|
||||
|
||||
// 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 (Test mode controller)
|
||||
const deviceName = "Test mode controller";
|
||||
|
||||
// Register the device with a unique endpoint ID (you can use something like "endpoint1")
|
||||
const modeControllerDevice = alex2NodeClient.registerDevice(
|
||||
deviceName,
|
||||
"endpoint6",
|
||||
[DisplayCategory.OTHER]
|
||||
);
|
||||
|
||||
// Add the 'MODE_CONTROLLER' capability for controlling device modes
|
||||
const modeController = modeControllerDevice.addCapability(
|
||||
AlexaInterfaceType.MODE_CONTROLLER
|
||||
);
|
||||
|
||||
modeController.setInstance("mode.fanmode");
|
||||
modeController.addFriendlyName("Fan Mode", "en-US");
|
||||
modeController.addSupportedModes([
|
||||
{
|
||||
value: "mode.fanmode.Normal",
|
||||
modeResources: {
|
||||
friendlyNames: [
|
||||
{
|
||||
"@type": "text",
|
||||
value: {
|
||||
text: "Normal",
|
||||
locale: "en-US",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "mode.fanmode.Low",
|
||||
modeResources: {
|
||||
friendlyNames: [
|
||||
{
|
||||
"@type": "text",
|
||||
value: {
|
||||
text: "Low",
|
||||
locale: "en-US",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Log the device name to verify registration
|
||||
console.log(modeControllerDevice.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).
|
||||
*/
|
||||
modeControllerDevice.on("ReportState", (payload) => {
|
||||
console.log("ReportState received!", payload);
|
||||
|
||||
const { correlationToken } = payload.header;
|
||||
|
||||
const status = modeControllerDevice.getStatusMessage(correlationToken);
|
||||
|
||||
status
|
||||
.addHealthProp("OK") // Device is healthy
|
||||
.addModeControllerProp("mode.fanmode", currentMode);
|
||||
|
||||
status.send(); // Send the state report back to Alexa
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle incoming control directives (e.g., SetMode).
|
||||
* These directives come from Alexa when a user issues a command.
|
||||
*/
|
||||
modeControllerDevice.on("Event", (directive, interfaceType) => {
|
||||
console.log("Event received", { directive, interfaceType });
|
||||
// Ensure the interfaceType is either PowerController or other valid interfaces
|
||||
if (interfaceType === AlexaInterfaceType.MODE_CONTROLLER) {
|
||||
const name = directive.header.name;
|
||||
const token = directive.header.correlationToken;
|
||||
const instance = directive.header.instance;
|
||||
|
||||
if (instance == "mode.fanmode") {
|
||||
if (name === "SetMode") {
|
||||
currentMode = directive.payload.mode;
|
||||
}
|
||||
}
|
||||
|
||||
const status = modeControllerDevice.getStatusMessage(token, true);
|
||||
|
||||
status
|
||||
.addHealthProp("OK") // Device is healthy
|
||||
.addModeControllerProp("mode.fanmode", currentMode);
|
||||
|
||||
status.send(); // Send the state report back to Alexa
|
||||
}
|
||||
});
|
||||
|
|
@ -29,12 +29,48 @@ const deviceName = "Thermostat Test";
|
|||
const thermostat = alex2NodeClient.registerDevice(
|
||||
deviceName,
|
||||
"thermostatEndpoint1",
|
||||
[DisplayCategory.TEMPERATURE_SENSOR, DisplayCategory.THERMOSTAT]
|
||||
[DisplayCategory.TEMPERATURE_SENSOR, DisplayCategory.THERMOSTAT,DisplayCategory.OTHER]
|
||||
);
|
||||
|
||||
// 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
|
||||
const modeController = thermostat.addCapability(
|
||||
AlexaInterfaceType.MODE_CONTROLLER
|
||||
);
|
||||
|
||||
modeController.setInstance("mode.fanmode");
|
||||
modeController.addFriendlyName("Fan", "en-US");
|
||||
modeController.addSupportedModes([
|
||||
{
|
||||
value: "mode.fanmode.Auto",
|
||||
modeResources: {
|
||||
friendlyNames: [
|
||||
{
|
||||
"@type": "text",
|
||||
value: {
|
||||
text: "Bannana",
|
||||
locale: "en-US",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "mode.fanmode.On",
|
||||
modeResources: {
|
||||
friendlyNames: [
|
||||
{
|
||||
"@type": "text",
|
||||
value: {
|
||||
text: "Apple",
|
||||
locale: "en-US",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Log the device name to verify registration
|
||||
console.log(thermostat.getName());
|
||||
|
|
@ -50,6 +86,7 @@ const deviceData = {
|
|||
targetSetpoint: 60,
|
||||
current: 69,
|
||||
thermostatMode: ThermostatMode.HEAT,
|
||||
fanMode:"mode.fanmode.Auto"
|
||||
};
|
||||
thermostat.on("ReportState", (payload) => {
|
||||
// console.log("ReportState received!", payload);
|
||||
|
|
@ -60,6 +97,7 @@ thermostat.on("ReportState", (payload) => {
|
|||
|
||||
status
|
||||
.addHealthProp("OK") // Device is healthy and reachable
|
||||
.addModeControllerProp("mode.fanmode", deviceData.fanMode)
|
||||
.addTemperatureSensorProp(
|
||||
TemperatureSensorScale.CELSIUS,
|
||||
deviceData.current
|
||||
|
|
@ -127,6 +165,7 @@ thermostat.on("Event", (directive, interfaceType) => {
|
|||
|
||||
status
|
||||
.addHealthProp("OK") // Device is healthy and reachable
|
||||
.addModeControllerProp("mode.fanmode", deviceData.fanMode)
|
||||
.addTemperatureSensorProp(
|
||||
TemperatureSensorScale.FAHRENHEIT,
|
||||
deviceData.current
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class Alex2MQTT extends EventEmitter {
|
|||
this.rootTopic,
|
||||
name,
|
||||
endpointId,
|
||||
normalizedCategory
|
||||
normalizedCategory|| [DisplayCategory.LIGHT]
|
||||
);
|
||||
this.devices.push(device);
|
||||
return device;
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ interface FriendlyName {
|
|||
export class AlexaInterface {
|
||||
private friendlyNames: FriendlyName[] = [];
|
||||
private actionMappings: ActionMapping[] = [];
|
||||
private supportedModes: string[] = [];
|
||||
|
||||
constructor(
|
||||
public type: AlexaInterfaceType,
|
||||
|
|
@ -96,7 +97,9 @@ export class AlexaInterface {
|
|||
addFriendlyName(name: string, locale: string): void {
|
||||
this.friendlyNames.push({ text: name, locale });
|
||||
}
|
||||
|
||||
addSupportedModes(modes: string[]): void {
|
||||
this.supportedModes = modes;
|
||||
}
|
||||
setInstance(name: string): void {
|
||||
this.instance = name;
|
||||
}
|
||||
|
|
@ -209,6 +212,8 @@ export class AlexaInterface {
|
|||
return ["connectivity"];
|
||||
case AlexaInterfaceType.TOGGLE_CONTROLLER:
|
||||
return ["toggleState"];
|
||||
case AlexaInterfaceType.MODE_CONTROLLER:
|
||||
return ["mode"];
|
||||
case AlexaInterfaceType.TEMPERATURE_SENSOR:
|
||||
return ["temperature"];
|
||||
case AlexaInterfaceType.THERMOSTAT_CONTROLLER:
|
||||
|
|
@ -241,7 +246,6 @@ export class AlexaInterface {
|
|||
case AlexaInterfaceType.LOCK_CONTROLLER:
|
||||
case AlexaInterfaceType.MEDIA_PLAYBACK:
|
||||
case AlexaInterfaceType.MEDIA_SEARCH:
|
||||
case AlexaInterfaceType.MODE_CONTROLLER:
|
||||
case AlexaInterfaceType.MOTION_SENSOR:
|
||||
case AlexaInterfaceType.PERCENTAGE_CONTROLLER:
|
||||
case AlexaInterfaceType.PLAYBACK_CONTROLLER:
|
||||
|
|
@ -291,6 +295,11 @@ export class AlexaInterface {
|
|||
"supportedModes": ["HEAT", "COOL", "AUTO", "OFF"],
|
||||
"supportsScheduling": false
|
||||
}
|
||||
}else if (this.supportedModes.length > 0) {
|
||||
doc["configuration"] = {
|
||||
ordered:true,
|
||||
supportedModes: this.supportedModes,
|
||||
};
|
||||
}
|
||||
if (this.instance) {
|
||||
doc.instance = this.instance;
|
||||
|
|
@ -306,6 +315,7 @@ export class AlexaInterface {
|
|||
};
|
||||
friendlyNamesArray.push(friendlyNameObj);
|
||||
});
|
||||
capabilityResources.friendlyNames=friendlyNamesArray
|
||||
doc["capabilityResources"] = capabilityResources;
|
||||
}
|
||||
if (this.actionMappings.length > 0) {
|
||||
|
|
|
|||
|
|
@ -115,6 +115,15 @@ export class AlexaStatusMessage {
|
|||
this.context.properties.push(prop);
|
||||
return this;
|
||||
}
|
||||
public addModeControllerProp(instance: string, value: string, uncertaintyInMs = 0): this {
|
||||
return this.addProperty(
|
||||
AlexaInterfaceType.MODE_CONTROLLER,
|
||||
"mode",
|
||||
value,
|
||||
uncertaintyInMs,
|
||||
instance
|
||||
);
|
||||
}
|
||||
public addThermostatModeProp(mode: string, uncertaintyInMs = 0): this {
|
||||
return this.addProperty(
|
||||
AlexaInterfaceType.THERMOSTAT_CONTROLLER,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue