import { mismatch, s } from "../schema.js"; import { DeclarationError, defineInterface } from "../types.js"; /** alexa-property-schemas.html, "ThermostatMode values". EM_HEAT, emergency heating, came with version 3.2. */ export const THERMOSTAT_MODES = ["AUTO", "COOL", "ECO", "EM_HEAT", "HEAT", "OFF"]; const PROPERTIES = ["targetSetpoint", "lowerSetpoint", "upperSetpoint", "thermostatMode", "adaptiveRecoveryStatus"]; // What 1.x announced for every thermostat. A declaration that leaves an option out announces the same. const MODES_OF_1X = ["HEAT", "COOL", "AUTO", "OFF"]; const PROPERTIES_OF_1X = ["targetSetpoint", "lowerSetpoint", "upperSetpoint", "thermostatMode"]; const setpoints = s.object({ targetSetpoint: s.optional(s.temperature()), lowerSetpoint: s.optional(s.temperature()), upperSetpoint: s.optional(s.temperature()), holdUntil: s.optional(s.timeInterval()), }); // One setpoint for a thermostat that holds a temperature, the lower and the upper for one that holds a range, all // three for one that aims at a temperature within a range. The page marks each as not required; a payload without // any sets nothing. const setTargetTemperature = { expects: "one or more of targetSetpoint, lowerSetpoint, upperSetpoint", parse(input, path = "") { const payload = setpoints.parse(input, path); if (!payload.targetSetpoint && !payload.lowerSetpoint && !payload.upperSetpoint) throw mismatch(path, this.expects, input); return payload; }, }; /** * Version 3.2. A thermostat declares the properties it has: targetSetpoint for a single setpoint, lowerSetpoint and * upperSetpoint for two, all three for a target within a range. Temperatures are reported "in the same temperature * scale (Celsius or Fahrenheit) that the thermostat is set to display". An error of errorTypes goes under the * namespace of the interface, TEMPERATURE_VALUE_OUT_OF_RANGE under Alexa * (alexa-thermostatcontroller-errorresponse.html). */ export const ThermostatController = defineInterface({ namespace: "Alexa.ThermostatController", version: "3.2", doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-thermostatcontroller.html", kind: "controller", tier: 1, instanced: false, properties: { targetSetpoint: { name: "targetSetpoint", value: s.temperature() }, lowerSetpoint: { name: "lowerSetpoint", value: s.temperature() }, upperSetpoint: { name: "upperSetpoint", value: s.temperature() }, thermostatMode: { name: "thermostatMode", value: s.enum(...THERMOSTAT_MODES) }, adaptiveRecoveryStatus: { name: "adaptiveRecoveryStatus", value: s.enum("PREHEATING", "PRECOOLING", "INACTIVE"), note: "heating or cooling ahead of an entry of the schedule, of Alexa.ThermostatController.Schedule", }, }, directives: { SetTargetTemperature: { name: "SetTargetTemperature", payload: setTargetTemperature, note: "holdUntil comes only to a thermostat declared with supportsScheduling", }, AdjustTargetTemperature: { name: "AdjustTargetTemperature", payload: s.object({ targetSetpointDelta: s.temperature() }) }, SetThermostatMode: { name: "SetThermostatMode", payload: s.object({ thermostatMode: s.object({ value: s.enum(...THERMOSTAT_MODES) }) }), }, ResumeSchedule: { name: "ResumeSchedule", payload: s.object({}), note: "en-US only" }, }, options: s.object({ /** Default: HEAT, COOL, AUTO, OFF. */ supportedModes: s.optional(s.array(s.enum(...THERMOSTAT_MODES), { min: 1 })), /** The user can set a temperature for a time: SetTargetTemperature comes with holdUntil. Default: false. */ supportsScheduling: s.optional(s.boolean()), /** The properties the thermostat has. Default: the three setpoints and thermostatMode. */ properties: s.optional(s.array(s.enum(...PROPERTIES), { min: 1 })), }, { unknownKeys: "reject" }), discovery({ options: { supportedModes = MODES_OF_1X, supportsScheduling = false, properties = PROPERTIES_OF_1X } }) { return { supported: properties, configuration: { supportedModes, supportsScheduling } }; }, validate(capability) { const { supportedModes = MODES_OF_1X, properties = PROPERTIES_OF_1X } = capability.options; // alexa-thermostatcontroller.html, "Configuration object": "You must include at least one of HEAT or COOL" if (!supportedModes.includes("HEAT") && !supportedModes.includes("COOL")) { throw new DeclarationError(capability, `supportedModes ${supportedModes.join(", ")} has neither HEAT nor COOL, Alexa wants one`); } const lists = [supportedModes, properties]; for (const list of lists) { const twice = list.find((entry, i) => list.indexOf(entry) !== i); if (twice) throw new DeclarationError(capability, `${twice} is listed twice`); } }, errorNamespace: "Alexa.ThermostatController", errorTypes: [ "DUAL_SETPOINTS_UNSUPPORTED", "REQUESTED_SETPOINTS_TOO_CLOSE", "THERMOSTAT_IS_OFF", "TRIPLE_SETPOINTS_UNSUPPORTED", "UNSUPPORTED_THERMOSTAT_MODE", "UNWILLING_TO_SET_SCHEDULE", "UNWILLING_TO_SET_VALUE", ], });