import { LIMITS } from "../catalog.js"; import { happened } from "../events.js"; import { s } from "../schema.js"; import { DeclarationError, defineInterface } from "../types.js"; const started = (name) => ({ name, payload: happened("VOICE_INTERACTION"), topic: "response" }); const events = { ActivationStarted: started("ActivationStarted"), DeactivationStarted: started("DeactivationStarted"), }; /** * A scene: an endpoint that is not a device, but several devices set to a state each. Activate is answered with * ActivationStarted and Deactivate with DeactivationStarted; ctx.respond() sends them, with the time and * VOICE_INTERACTION as the cause. A scene reports nothing and has no Alexa.EndpointHealth. * * Alexa takes no scene with a lock, a garage door, a camera, a cooking appliance or a security device in it. */ export const SceneController = defineInterface({ namespace: "Alexa.SceneController", version: "3", doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-scenecontroller.html", kind: "controller", tier: 1, instanced: false, properties: {}, directives: { Activate: { name: "Activate", payload: s.object({}) }, Deactivate: { name: "Deactivate", payload: s.object({}), when: ({ options }) => options.supportsDeactivation !== false, }, }, events, options: s.object({ /** The scene can be switched off again. */ supportsDeactivation: s.optional(s.boolean()), }, { unknownKeys: "reject" }), // A scene declared without the option is announced as 1.5.2 announced every scene, which Alexa accepted: it can // be deactivated, and proactivelyReported is on the capability. The example of the page has supportsDeactivation // alone. discovery({ proactivelyReported, options: { supportsDeactivation } }) { const topLevel = supportsDeactivation === undefined ? { supportsDeactivation: true, proactivelyReported } : { supportsDeactivation }; return { properties: false, topLevel }; }, // alexa-scenecontroller.html, "Discovery" validate(capability, endpoint) { const { friendlyName, description, displayCategories } = endpoint; if (!displayCategories.includes("SCENE_TRIGGER") && !displayCategories.includes("ACTIVITY_TRIGGER")) { throw new DeclarationError(capability, "a scene has the display category SCENE_TRIGGER, or ACTIVITY_TRIGGER when the order of its steps matters"); } if (!/scene/i.test(description)) { throw new DeclarationError(capability, 'the description of a scene has the word "scene" in it, like "Party scene connected by Alex2Node"'); } if (friendlyName.length > LIMITS.sceneFriendlyNameLength) { throw new DeclarationError(capability, `the name of a scene takes up to ${LIMITS.sceneFriendlyNameLength} characters, this one has ${friendlyName.length}`); } }, responseFor(directive) { if (directive !== "Activate" && directive !== "Deactivate") return undefined; const { name, payload } = directive === "Activate" ? events.ActivationStarted : events.DeactivationStarted; return { namespace: "Alexa.SceneController", name, payload }; }, });