// A thermostat with one setpoint and the modes HEAT, COOL and OFF: Alexa.ThermostatController, and // Alexa.TemperatureSensor for the temperature of the room. It works in Celsius and takes Fahrenheit and Kelvin. // // ALEX2MQTT_USERNAME=... ALEX2MQTT_PASSWORD=... ALEX2MQTT_ROOT_TOPIC=... node examples/thermostat.js // // "alex2node" is the installed package; inside this checkout it resolves to the built dist/. const { Alex2MQTT, AlexaErrors, ThermostatController, TemperatureSensor } = require("alex2node"); const missing = ["ALEX2MQTT_USERNAME", "ALEX2MQTT_PASSWORD", "ALEX2MQTT_ROOT_TOPIC"].filter((name) => !process.env[name]); if (missing.length > 0) { console.error(`${missing.join(", ")} not set. Set the MQTT user name, the password and the root topic of your Alex2MQTT account.`); process.exit(1); } const { ALEX2MQTT_USERNAME, ALEX2MQTT_PASSWORD, ALEX2MQTT_ROOT_TOPIC } = process.env; const bridge = new Alex2MQTT(ALEX2MQTT_USERNAME, ALEX2MQTT_PASSWORD, ALEX2MQTT_ROOT_TOPIC); bridge.on("connect", () => console.log("connected, discover the devices in the Alexa app")); bridge.on("error", (err) => console.error("bridge:", err.message)); const MODES = ["HEAT", "COOL", "OFF"]; const RANGE = { min: 5, max: 30 }; const thermostat = { mode: "HEAT", target: 21, room: 19.5 }; const celsius = (value) => ({ value, scale: "CELSIUS" }); // A difference has no offset: 9 degrees Fahrenheit more are 5 degrees Celsius more const deltaInCelsius = ({ value, scale }) => (scale === "FAHRENHEIT" ? (value * 5) / 9 : value); function inCelsius({ value, scale }) { if (scale === "FAHRENHEIT") return ((value - 32) * 5) / 9; return scale === "KELVIN" ? value - 273.15 : value; } const device = bridge.addDevice({ endpointId: "hall-thermostat", name: "Hall Thermostat", categories: ["THERMOSTAT", "TEMPERATURE_SENSOR"], }); const control = device.add(ThermostatController, { supportedModes: MODES, properties: ["targetSetpoint", "thermostatMode"] }); const sensor = device.add(TemperatureSensor); device.state((s) => s .set(control, "targetSetpoint", celsius(thermostat.target)) .set(control, "thermostatMode", thermostat.mode) .set(sensor, "temperature", celsius(thermostat.room)) .health("OK")); function setTarget(ctx, target) { if (thermostat.mode === "OFF") throw AlexaErrors.thermostatIsOff("The thermostat is off"); const rounded = Math.round(target * 2) / 2; if (rounded < RANGE.min || rounded > RANGE.max) { throw AlexaErrors.temperatureOutOfRange( `The thermostat takes ${RANGE.min} to ${RANGE.max} degrees Celsius`, { minimumValue: celsius(RANGE.min), maximumValue: celsius(RANGE.max) } ); } thermostat.target = rounded; return ctx.respond(); } control.on("SetTargetTemperature", (ctx) => { const { targetSetpoint, lowerSetpoint, upperSetpoint } = ctx.payload; if (!targetSetpoint || lowerSetpoint || upperSetpoint) { throw AlexaErrors.dualSetpointsUnsupported("The thermostat has one setpoint"); } return setTarget(ctx, inCelsius(targetSetpoint)); }); control.on("AdjustTargetTemperature", (ctx) => setTarget(ctx, thermostat.target + deltaInCelsius(ctx.payload.targetSetpointDelta))); control.on("SetThermostatMode", (ctx) => { const mode = ctx.payload.thermostatMode.value; if (!MODES.includes(mode)) throw AlexaErrors.unsupportedThermostatMode(`The thermostat has no mode ${mode}`); thermostat.mode = mode; return ctx.respond(); }); bridge.connect();