registry: ThermostatController 3.2 and ThermostatController.Schedule

The thermostat is described from its page: one, two or three setpoints with holdUntil, AdjustTargetTemperature,
SetThermostatMode with the six modes (EM_HEAT is new), ResumeSchedule, adaptiveRecoveryStatus, and its seven error
types under Alexa.ThermostatController. A declaration gives supportedModes, supportsScheduling and the properties
the device has; without them discovery is what 1.5.2 sent: HEAT, COOL, AUTO, OFF and the three setpoints with
thermostatMode. supportedModes without HEAT or COOL is refused where it is declared.

ThermostatController.Schedule replaces its stub. Wire change for a 1.x declaration of it: version 3.2 (was 1) and
scheduleEnabled listed (was nothing). SetAdaptiveRecovery is taken only with supportsAdaptiveRecovery.

24 examples of three pages are saved as fixtures; the page folds the SetWeeklySchedule example away, its payload
is tested from the tables. 207 tests pass, 191 before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
David 2026-09-28 20:34:49 +00:00
parent af1489696d
commit f555a5df70
67 changed files with 2445 additions and 58 deletions

View file

@ -0,0 +1,77 @@
import { s } from "../schema.js";
import { defineInterface } from "../types.js";
const FAN_MODES = ["ON", "AUTO", "CIRCULATE"];
// alexa-thermostatcontroller-schedule.html, "ScheduleEntry object": an entry ends where the next one starts
const entry = s.object({
startTimeInMinutes: s.number({ min: 0, max: 1439, integer: true }),
setpoints: s.object({ upperSetpoint: s.optional(s.temperature()), lowerSetpoint: s.optional(s.temperature()) }),
/** Without a mode the fan runs as the manufacturer set it. */
fanSetting: s.optional(s.object({ mode: s.optional(s.enum(...FAN_MODES)) })),
activityType: s.optional(s.object({ type: s.enum("AWAY", "HOME", "SLEEP") })),
});
const day = s.array(entry);
// Same page, "WeeklySchedule object"
const weeklySchedule = s.object({
temperatureScale: s.enum("CELSIUS", "FAHRENHEIT", "KELVIN"),
Monday: day,
Tuesday: day,
Wednesday: day,
Thursday: day,
Friday: day,
Saturday: day,
Sunday: day,
});
/**
* Version 3.2. The schedule a user writes in the Alexa app: the interface has no utterances. Discovery lists
* adaptiveRecoveryEnabled for a thermostat declared with supportsAdaptiveRecovery, and Alexa sends
* SetAdaptiveRecovery to such a thermostat only.
*/
export const ThermostatControllerSchedule = defineInterface({
namespace: "Alexa.ThermostatController.Schedule",
version: "3.2",
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-thermostatcontroller-schedule.html",
kind: "controller",
tier: 2,
instanced: false,
properties: {
adaptiveRecoveryEnabled: { name: "adaptiveRecoveryEnabled", value: s.boolean() },
scheduleEnabled: { name: "scheduleEnabled", value: s.boolean() },
},
directives: {
SetWeeklySchedule: {
name: "SetWeeklySchedule",
payload: s.object({ weeklySchedule: s.optional(weeklySchedule) }),
note: "a schedule the thermostat cannot keep leaves the one it has and is answered with an error",
},
SetScheduleState: {
name: "SetScheduleState",
payload: s.object({ scheduleEnabled: s.boolean() }),
note: "a disabled schedule is kept: the user can enable it again",
},
SetAdaptiveRecovery: {
name: "SetAdaptiveRecovery",
payload: s.object({ adaptiveRecoveryEnabled: s.boolean() }),
when: ({ options }) => options.supportsAdaptiveRecovery === true,
},
},
options: s.object({
supportedFanModes: s.array(s.enum(...FAN_MODES)),
/** The thermostat heats or cools ahead of an entry, to be at its temperature when it starts. */
supportsAdaptiveRecovery: s.boolean(),
maxEntryPerDay: s.optional(s.number({ min: 1, integer: true })),
}, { unknownKeys: "reject" }),
// A capability declared the 1.x way has none of the options; check() says so, discovery lists what there is
discovery({ options: { supportedFanModes, supportsAdaptiveRecovery, maxEntryPerDay } }) {
const configuration = {
...(supportedFanModes && { supportedFanModes }),
...(supportsAdaptiveRecovery !== undefined && { supportsAdaptiveRecovery }),
...(maxEntryPerDay !== undefined && { maxEntryPerDay }),
};
return {
supported: supportsAdaptiveRecovery ? ["adaptiveRecoveryEnabled", "scheduleEnabled"] : ["scheduleEnabled"],
...(Object.keys(configuration).length > 0 && { configuration }),
};
},
errorNamespace: "Alexa.ThermostatController.Schedule",
errorTypes: ["INSUFFICIENT_SPACE"],
});