registry: SecurityPanelController with Arm.Response and its errors
Alexa.SecurityPanelController is described from its page: version 3 (the stub of 1.5.2 said 1), armState and the four alarms, Arm and Disarm. ctx.respond() answers Arm with Arm.Response under the namespace of the interface, its payload checked (exitDelayInSeconds 0 to 255, bypassedEndpoints), and Disarm with a Response. A Disarm carries the PIN the user said when the panel is declared with supportedAuthorizationTypes; the handler checks it and answers UNAUTHORIZED, which goes under Alexa.SecurityPanelController like the five other error types. A panel declared without options lists armState, where 1.5.2 listed no property; alarms are declared. 17 examples of the two pages are saved as fixtures. 215 tests pass, 207 before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
f555a5df70
commit
c59cf3ff10
39 changed files with 1248 additions and 23 deletions
5
dist/cjs/index.js
vendored
5
dist/cjs/index.js
vendored
|
|
@ -36,8 +36,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.PowerState = exports.DisplayCategory = exports.AlexaInterfaceType = exports.AlexaErrorType = exports.AlexaActions = exports.ActionMapping = exports.AlexaInterface = exports.MemoryPublisher = exports.topics = exports.property = exports.StateBuilder = exports.MessageError = exports.AlexaErrors = exports.AlexaError = exports.messages = exports.ThermostatModes = exports.DisplayCategories = exports.States = exports.Actions = exports.Units = exports.Assets = exports.SemanticsBuilder = exports.semantics = exports.text = exports.asset = exports.EndpointHealth = exports.PowerController = exports.ToggleController = exports.ThermostatControllerSchedule = exports.ThermostatController = exports.TemperatureSensor = exports.RangeController = exports.PowerLevelController = exports.PercentageController = exports.MotionSensor = exports.ModeController = exports.LockController = exports.HumiditySensor = exports.ContactSensor = exports.ColorTemperatureController = exports.ColorController = exports.BrightnessController = exports.Alexa = exports.SchemaError = exports.DeclarationError = exports.registry = exports.Capability = exports.Device = exports.DEFAULT_HOST = exports.Alex2MQTT = void 0;
|
exports.DisplayCategory = exports.AlexaInterfaceType = exports.AlexaErrorType = exports.AlexaActions = exports.ActionMapping = exports.AlexaInterface = exports.MemoryPublisher = exports.topics = exports.property = exports.StateBuilder = exports.MessageError = exports.AlexaErrors = exports.AlexaError = exports.messages = exports.ThermostatModes = exports.DisplayCategories = exports.States = exports.Actions = exports.Units = exports.Assets = exports.SemanticsBuilder = exports.semantics = exports.text = exports.asset = exports.EndpointHealth = exports.PowerController = exports.ToggleController = exports.ThermostatControllerSchedule = exports.ThermostatController = exports.TemperatureSensor = exports.SecurityPanelController = exports.RangeController = exports.PowerLevelController = exports.PercentageController = exports.MotionSensor = exports.ModeController = exports.LockController = exports.HumiditySensor = exports.ContactSensor = exports.ColorTemperatureController = exports.ColorController = exports.BrightnessController = exports.Alexa = exports.SchemaError = exports.DeclarationError = exports.registry = exports.Capability = exports.Device = exports.DEFAULT_HOST = exports.Alex2MQTT = void 0;
|
||||||
exports.AlexaErrorResponse = exports.AlexaStatusMessage = exports.ThermostatMode = exports.TemperatureSensorScale = void 0;
|
exports.AlexaErrorResponse = exports.AlexaStatusMessage = exports.ThermostatMode = exports.TemperatureSensorScale = exports.PowerState = void 0;
|
||||||
// Relative specifiers carry ".js": Node's ES module loader resolves no extension, and TypeScript maps it back to the .ts.
|
// Relative specifiers carry ".js": Node's ES module loader resolves no extension, and TypeScript maps it back to the .ts.
|
||||||
var Alex2Node_js_1 = require("./Alex2Node.js");
|
var Alex2Node_js_1 = require("./Alex2Node.js");
|
||||||
Object.defineProperty(exports, "Alex2MQTT", { enumerable: true, get: function () { return __importDefault(Alex2Node_js_1).default; } });
|
Object.defineProperty(exports, "Alex2MQTT", { enumerable: true, get: function () { return __importDefault(Alex2Node_js_1).default; } });
|
||||||
|
|
@ -64,6 +64,7 @@ Object.defineProperty(exports, "MotionSensor", { enumerable: true, get: function
|
||||||
Object.defineProperty(exports, "PercentageController", { enumerable: true, get: function () { return index_js_2.PercentageController; } });
|
Object.defineProperty(exports, "PercentageController", { enumerable: true, get: function () { return index_js_2.PercentageController; } });
|
||||||
Object.defineProperty(exports, "PowerLevelController", { enumerable: true, get: function () { return index_js_2.PowerLevelController; } });
|
Object.defineProperty(exports, "PowerLevelController", { enumerable: true, get: function () { return index_js_2.PowerLevelController; } });
|
||||||
Object.defineProperty(exports, "RangeController", { enumerable: true, get: function () { return index_js_2.RangeController; } });
|
Object.defineProperty(exports, "RangeController", { enumerable: true, get: function () { return index_js_2.RangeController; } });
|
||||||
|
Object.defineProperty(exports, "SecurityPanelController", { enumerable: true, get: function () { return index_js_2.SecurityPanelController; } });
|
||||||
Object.defineProperty(exports, "TemperatureSensor", { enumerable: true, get: function () { return index_js_2.TemperatureSensor; } });
|
Object.defineProperty(exports, "TemperatureSensor", { enumerable: true, get: function () { return index_js_2.TemperatureSensor; } });
|
||||||
Object.defineProperty(exports, "ThermostatController", { enumerable: true, get: function () { return index_js_2.ThermostatController; } });
|
Object.defineProperty(exports, "ThermostatController", { enumerable: true, get: function () { return index_js_2.ThermostatController; } });
|
||||||
Object.defineProperty(exports, "ThermostatControllerSchedule", { enumerable: true, get: function () { return index_js_2.ThermostatControllerSchedule; } });
|
Object.defineProperty(exports, "ThermostatControllerSchedule", { enumerable: true, get: function () { return index_js_2.ThermostatControllerSchedule; } });
|
||||||
|
|
|
||||||
5
dist/cjs/registry/index.js
vendored
5
dist/cjs/registry/index.js
vendored
|
|
@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.SchemaError = exports.s = exports.defineInterface = exports.DeclarationError = exports.SemanticsBuilder = exports.semantics = exports.text = exports.asset = exports.THERMOSTAT_MODES = exports.ToggleController = exports.ThermostatControllerSchedule = exports.ThermostatController = exports.TemperatureSensor = exports.RangeController = exports.PowerLevelController = exports.PowerController = exports.PercentageController = exports.MotionSensor = exports.ModeController = exports.LockController = exports.HumiditySensor = exports.EndpointHealth = exports.ContactSensor = exports.ColorTemperatureController = exports.ColorController = exports.BrightnessController = exports.Alexa = exports.registry = void 0;
|
exports.SchemaError = exports.s = exports.defineInterface = exports.DeclarationError = exports.SemanticsBuilder = exports.semantics = exports.text = exports.asset = exports.THERMOSTAT_MODES = exports.ToggleController = exports.ThermostatControllerSchedule = exports.ThermostatController = exports.TemperatureSensor = exports.SecurityPanelController = exports.RangeController = exports.PowerLevelController = exports.PowerController = exports.PercentageController = exports.MotionSensor = exports.ModeController = exports.LockController = exports.HumiditySensor = exports.EndpointHealth = exports.ContactSensor = exports.ColorTemperatureController = exports.ColorController = exports.BrightnessController = exports.Alexa = exports.registry = void 0;
|
||||||
// The interfaces the library knows, by namespace.
|
// The interfaces the library knows, by namespace.
|
||||||
const Alexa_js_1 = require("./interfaces/Alexa.js");
|
const Alexa_js_1 = require("./interfaces/Alexa.js");
|
||||||
Object.defineProperty(exports, "Alexa", { enumerable: true, get: function () { return Alexa_js_1.Alexa; } });
|
Object.defineProperty(exports, "Alexa", { enumerable: true, get: function () { return Alexa_js_1.Alexa; } });
|
||||||
|
|
@ -44,6 +44,8 @@ const PowerLevelController_js_1 = require("./interfaces/PowerLevelController.js"
|
||||||
Object.defineProperty(exports, "PowerLevelController", { enumerable: true, get: function () { return PowerLevelController_js_1.PowerLevelController; } });
|
Object.defineProperty(exports, "PowerLevelController", { enumerable: true, get: function () { return PowerLevelController_js_1.PowerLevelController; } });
|
||||||
const RangeController_js_1 = require("./interfaces/RangeController.js");
|
const RangeController_js_1 = require("./interfaces/RangeController.js");
|
||||||
Object.defineProperty(exports, "RangeController", { enumerable: true, get: function () { return RangeController_js_1.RangeController; } });
|
Object.defineProperty(exports, "RangeController", { enumerable: true, get: function () { return RangeController_js_1.RangeController; } });
|
||||||
|
const SecurityPanelController_js_1 = require("./interfaces/SecurityPanelController.js");
|
||||||
|
Object.defineProperty(exports, "SecurityPanelController", { enumerable: true, get: function () { return SecurityPanelController_js_1.SecurityPanelController; } });
|
||||||
const TemperatureSensor_js_1 = require("./interfaces/TemperatureSensor.js");
|
const TemperatureSensor_js_1 = require("./interfaces/TemperatureSensor.js");
|
||||||
Object.defineProperty(exports, "TemperatureSensor", { enumerable: true, get: function () { return TemperatureSensor_js_1.TemperatureSensor; } });
|
Object.defineProperty(exports, "TemperatureSensor", { enumerable: true, get: function () { return TemperatureSensor_js_1.TemperatureSensor; } });
|
||||||
const ThermostatController_js_1 = require("./interfaces/ThermostatController.js");
|
const ThermostatController_js_1 = require("./interfaces/ThermostatController.js");
|
||||||
|
|
@ -69,6 +71,7 @@ const described = [
|
||||||
PowerController_js_1.PowerController,
|
PowerController_js_1.PowerController,
|
||||||
PowerLevelController_js_1.PowerLevelController,
|
PowerLevelController_js_1.PowerLevelController,
|
||||||
RangeController_js_1.RangeController,
|
RangeController_js_1.RangeController,
|
||||||
|
SecurityPanelController_js_1.SecurityPanelController,
|
||||||
TemperatureSensor_js_1.TemperatureSensor,
|
TemperatureSensor_js_1.TemperatureSensor,
|
||||||
ThermostatController_js_1.ThermostatController,
|
ThermostatController_js_1.ThermostatController,
|
||||||
ThermostatControllerSchedule_js_1.ThermostatControllerSchedule,
|
ThermostatControllerSchedule_js_1.ThermostatControllerSchedule,
|
||||||
|
|
|
||||||
91
dist/cjs/registry/interfaces/SecurityPanelController.js
vendored
Normal file
91
dist/cjs/registry/interfaces/SecurityPanelController.js
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.SecurityPanelController = void 0;
|
||||||
|
const schema_js_1 = require("../schema.js");
|
||||||
|
const types_js_1 = require("../types.js");
|
||||||
|
const ARM_STATES = ["ARMED_AWAY", "ARMED_STAY", "ARMED_NIGHT", "DISARMED"];
|
||||||
|
const ALARMS = ["burglaryAlarm", "fireAlarm", "carbonMonoxideAlarm", "waterAlarm"];
|
||||||
|
const alarm = schema_js_1.s.object({ value: schema_js_1.s.enum("OK", "ALARM") });
|
||||||
|
// A sensor that is left out when the panel arms. The endpointId is for a sensor that is an endpoint of its own.
|
||||||
|
const bypassed = schema_js_1.s.object({ friendlyName: schema_js_1.s.optional(schema_js_1.s.string({ min: 1 })), endpointId: schema_js_1.s.optional(schema_js_1.s.string({ min: 1 })) });
|
||||||
|
const armResponse = schema_js_1.s.object({
|
||||||
|
/** How long the occupants have to leave before the panel arms. 0: it arms at once. */
|
||||||
|
exitDelayInSeconds: schema_js_1.s.optional(schema_js_1.s.number({ min: 0, max: 255, integer: true })),
|
||||||
|
/** For an Arm that came with bypassType only. */
|
||||||
|
bypassedEndpoints: schema_js_1.s.optional(schema_js_1.s.array(bypassed)),
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* A security panel. Arm is answered with Arm.Response, Disarm with a Response, an error of errorTypes under the
|
||||||
|
* namespace of the interface (alexa-securitypanelcontroller-errorresponse.html).
|
||||||
|
*
|
||||||
|
* Alexa sends Disarm only when the user switched on disarming by voice in the Alexa app. A panel declared with
|
||||||
|
* supportedAuthorizationTypes gets the PIN the user said in payload.authorization and has to check it: a wrong PIN
|
||||||
|
* is answered with UNAUTHORIZED. Without authorization in the payload Alexa checked a voice code of its own.
|
||||||
|
*
|
||||||
|
* A panel that is ARMED_AWAY is disarmed before it is armed another way: Arm to ARMED_STAY or ARMED_NIGHT is
|
||||||
|
* then answered with AUTHORIZATION_REQUIRED. Arm to the state the panel is in is answered with Arm.Response.
|
||||||
|
*/
|
||||||
|
exports.SecurityPanelController = (0, types_js_1.defineInterface)({
|
||||||
|
namespace: "Alexa.SecurityPanelController",
|
||||||
|
version: "3",
|
||||||
|
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-securitypanelcontroller.html",
|
||||||
|
kind: "controller",
|
||||||
|
tier: 2,
|
||||||
|
instanced: false,
|
||||||
|
properties: {
|
||||||
|
armState: { name: "armState", value: schema_js_1.s.enum(...ARM_STATES) },
|
||||||
|
burglaryAlarm: { name: "burglaryAlarm", value: alarm },
|
||||||
|
fireAlarm: { name: "fireAlarm", value: alarm },
|
||||||
|
carbonMonoxideAlarm: { name: "carbonMonoxideAlarm", value: alarm },
|
||||||
|
waterAlarm: { name: "waterAlarm", value: alarm },
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
Arm: {
|
||||||
|
name: "Arm",
|
||||||
|
payload: schema_js_1.s.object({ armState: schema_js_1.s.enum(...ARM_STATES), bypassType: schema_js_1.s.optional(schema_js_1.s.enum("BYPASS_ALL")) }),
|
||||||
|
note: "bypassType: the user said to arm with the open sensors left out, which BYPASS_NEEDED listed",
|
||||||
|
},
|
||||||
|
Disarm: {
|
||||||
|
name: "Disarm",
|
||||||
|
payload: schema_js_1.s.object({
|
||||||
|
authorization: schema_js_1.s.optional(schema_js_1.s.object({
|
||||||
|
type: schema_js_1.s.enum("FOUR_DIGIT_PIN"),
|
||||||
|
value: schema_js_1.s.string({ pattern: /^\d{4}$/, expects: "a PIN of four digits" }),
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
note: "authorization: the PIN the user said, for the panel to check",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: schema_js_1.s.object({
|
||||||
|
/** Default: Alexa takes the panel to have all four. */
|
||||||
|
supportedArmStates: schema_js_1.s.optional(schema_js_1.s.array(schema_js_1.s.enum(...ARM_STATES), { min: 1 })),
|
||||||
|
/** For a panel that has four digit PINs and checks the PIN of a Disarm itself. */
|
||||||
|
supportedAuthorizationTypes: schema_js_1.s.optional(schema_js_1.s.array(schema_js_1.s.enum("FOUR_DIGIT_PIN"), { min: 1 })),
|
||||||
|
/** The alarms the panel reports. Default: none. */
|
||||||
|
alarms: schema_js_1.s.optional(schema_js_1.s.array(schema_js_1.s.enum(...ALARMS))),
|
||||||
|
}, { unknownKeys: "reject" }),
|
||||||
|
discovery({ options: { supportedArmStates, supportedAuthorizationTypes, alarms = [] } }) {
|
||||||
|
const configuration = {
|
||||||
|
...(supportedArmStates && { supportedArmStates: supportedArmStates.map((value) => ({ value })) }),
|
||||||
|
...(supportedAuthorizationTypes && { supportedAuthorizationTypes: supportedAuthorizationTypes.map((type) => ({ type })) }),
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
supported: ["armState", ...alarms],
|
||||||
|
...(Object.keys(configuration).length > 0 && { configuration }),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
validate(capability) {
|
||||||
|
const { supportedArmStates = [], supportedAuthorizationTypes = [], alarms = [] } = capability.options;
|
||||||
|
const lists = [supportedArmStates, supportedAuthorizationTypes, alarms];
|
||||||
|
for (const list of lists) {
|
||||||
|
const twice = list.find((entry, i) => list.indexOf(entry) !== i);
|
||||||
|
if (twice)
|
||||||
|
throw new types_js_1.DeclarationError(capability, `${twice} is listed twice`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responseFor: (directive) => (directive === "Arm"
|
||||||
|
? { namespace: "Alexa.SecurityPanelController", name: "Arm.Response", payload: armResponse }
|
||||||
|
: undefined),
|
||||||
|
errorNamespace: "Alexa.SecurityPanelController",
|
||||||
|
errorTypes: ["AUTHORIZATION_REQUIRED", "BYPASS_NEEDED", "NOT_READY", "UNAUTHORIZED", "UNCLEARED_ALARM", "UNCLEARED_TROUBLE"],
|
||||||
|
});
|
||||||
1
dist/cjs/registry/interfaces/stubs.js
vendored
1
dist/cjs/registry/interfaces/stubs.js
vendored
|
|
@ -51,7 +51,6 @@ const TABLE = [
|
||||||
["Alexa.RecordController", "3", [], "alexa-recordcontroller.html"],
|
["Alexa.RecordController", "3", [], "alexa-recordcontroller.html"],
|
||||||
["Alexa.RemoteVideoPlayer", "1", [], "alexa-remotevideoplayer.html"],
|
["Alexa.RemoteVideoPlayer", "1", [], "alexa-remotevideoplayer.html"],
|
||||||
["Alexa.SceneController", "3", [], "alexa-scenecontroller.html"],
|
["Alexa.SceneController", "3", [], "alexa-scenecontroller.html"],
|
||||||
["Alexa.SecurityPanelController", "1", [], "alexa-securitypanelcontroller.html"],
|
|
||||||
["Alexa.SecurityPanelController.Alert", "1", [], "alexa-securitypanelcontroller-alert.html"],
|
["Alexa.SecurityPanelController.Alert", "1", [], "alexa-securitypanelcontroller-alert.html"],
|
||||||
["Alexa.SeekController", "3", [], "alexa-seekcontroller.html"],
|
["Alexa.SeekController", "3", [], "alexa-seekcontroller.html"],
|
||||||
["Alexa.SimpleEventSource", "1", [], "alexa-simpleeventsource.html"],
|
["Alexa.SimpleEventSource", "1", [], "alexa-simpleeventsource.html"],
|
||||||
|
|
|
||||||
2
dist/esm/index.d.ts
vendored
2
dist/esm/index.d.ts
vendored
|
|
@ -5,7 +5,7 @@ export type { EndpointDefinition, EndpointJson } from "./device/Device.js";
|
||||||
export { Capability } from "./device/Capability.js";
|
export { Capability } from "./device/Capability.js";
|
||||||
export type { CapabilityJson, CommonOptions, Declaration } from "./device/Capability.js";
|
export type { CapabilityJson, CommonOptions, Declaration } from "./device/Capability.js";
|
||||||
export { registry, DeclarationError, SchemaError } from "./registry/index.js";
|
export { registry, DeclarationError, SchemaError } from "./registry/index.js";
|
||||||
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerLevelController, RangeController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, } from "./registry/index.js";
|
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerLevelController, RangeController, SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, } from "./registry/index.js";
|
||||||
export { PowerController, EndpointHealth } from "./compat/enums.js";
|
export { PowerController, EndpointHealth } from "./compat/enums.js";
|
||||||
export { asset, text, semantics, SemanticsBuilder } from "./registry/index.js";
|
export { asset, text, semantics, SemanticsBuilder } from "./registry/index.js";
|
||||||
export { ASSETS as Assets, UNITS_OF_MEASURE as Units, ACTIONS as Actions, STATES as States, DISPLAY_CATEGORIES as DisplayCategories, THERMOSTAT_MODES as ThermostatModes, } from "./registry/index.js";
|
export { ASSETS as Assets, UNITS_OF_MEASURE as Units, ACTIONS as Actions, STATES as States, DISPLAY_CATEGORIES as DisplayCategories, THERMOSTAT_MODES as ThermostatModes, } from "./registry/index.js";
|
||||||
|
|
|
||||||
2
dist/esm/index.js
vendored
2
dist/esm/index.js
vendored
|
|
@ -4,7 +4,7 @@ export { default as Device } from "./device/Device.js";
|
||||||
export { Capability } from "./device/Capability.js";
|
export { Capability } from "./device/Capability.js";
|
||||||
// The interfaces, for device.add(), and the registry that holds them by name
|
// The interfaces, for device.add(), and the registry that holds them by name
|
||||||
export { registry, DeclarationError, SchemaError } from "./registry/index.js";
|
export { registry, DeclarationError, SchemaError } from "./registry/index.js";
|
||||||
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerLevelController, RangeController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, } from "./registry/index.js";
|
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerLevelController, RangeController, SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, } from "./registry/index.js";
|
||||||
export { PowerController, EndpointHealth } from "./compat/enums.js";
|
export { PowerController, EndpointHealth } from "./compat/enums.js";
|
||||||
export { asset, text, semantics, SemanticsBuilder } from "./registry/index.js";
|
export { asset, text, semantics, SemanticsBuilder } from "./registry/index.js";
|
||||||
// The vocabularies of the Smart Home API
|
// The vocabularies of the Smart Home API
|
||||||
|
|
|
||||||
3
dist/esm/registry/index.d.ts
vendored
3
dist/esm/registry/index.d.ts
vendored
|
|
@ -12,6 +12,7 @@ import { PercentageController } from "./interfaces/PercentageController.js";
|
||||||
import { PowerController } from "./interfaces/PowerController.js";
|
import { PowerController } from "./interfaces/PowerController.js";
|
||||||
import { PowerLevelController } from "./interfaces/PowerLevelController.js";
|
import { PowerLevelController } from "./interfaces/PowerLevelController.js";
|
||||||
import { RangeController } from "./interfaces/RangeController.js";
|
import { RangeController } from "./interfaces/RangeController.js";
|
||||||
|
import { SecurityPanelController } from "./interfaces/SecurityPanelController.js";
|
||||||
import { TemperatureSensor } from "./interfaces/TemperatureSensor.js";
|
import { TemperatureSensor } from "./interfaces/TemperatureSensor.js";
|
||||||
import { ThermostatController } from "./interfaces/ThermostatController.js";
|
import { ThermostatController } from "./interfaces/ThermostatController.js";
|
||||||
import { ThermostatControllerSchedule } from "./interfaces/ThermostatControllerSchedule.js";
|
import { ThermostatControllerSchedule } from "./interfaces/ThermostatControllerSchedule.js";
|
||||||
|
|
@ -25,7 +26,7 @@ export declare const registry: {
|
||||||
/** Every descriptor, ordered by namespace. */
|
/** Every descriptor, ordered by namespace. */
|
||||||
list(): AnyDescriptor[];
|
list(): AnyDescriptor[];
|
||||||
};
|
};
|
||||||
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerController, PowerLevelController, RangeController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, };
|
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerController, PowerLevelController, RangeController, SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, };
|
||||||
export type { Mode } from "./interfaces/ModeController.js";
|
export type { Mode } from "./interfaces/ModeController.js";
|
||||||
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
|
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
|
||||||
export type { ThermostatModeName } from "./interfaces/ThermostatController.js";
|
export type { ThermostatModeName } from "./interfaces/ThermostatController.js";
|
||||||
|
|
|
||||||
4
dist/esm/registry/index.js
vendored
4
dist/esm/registry/index.js
vendored
|
|
@ -13,6 +13,7 @@ import { PercentageController } from "./interfaces/PercentageController.js";
|
||||||
import { PowerController } from "./interfaces/PowerController.js";
|
import { PowerController } from "./interfaces/PowerController.js";
|
||||||
import { PowerLevelController } from "./interfaces/PowerLevelController.js";
|
import { PowerLevelController } from "./interfaces/PowerLevelController.js";
|
||||||
import { RangeController } from "./interfaces/RangeController.js";
|
import { RangeController } from "./interfaces/RangeController.js";
|
||||||
|
import { SecurityPanelController } from "./interfaces/SecurityPanelController.js";
|
||||||
import { TemperatureSensor } from "./interfaces/TemperatureSensor.js";
|
import { TemperatureSensor } from "./interfaces/TemperatureSensor.js";
|
||||||
import { ThermostatController } from "./interfaces/ThermostatController.js";
|
import { ThermostatController } from "./interfaces/ThermostatController.js";
|
||||||
import { ThermostatControllerSchedule } from "./interfaces/ThermostatControllerSchedule.js";
|
import { ThermostatControllerSchedule } from "./interfaces/ThermostatControllerSchedule.js";
|
||||||
|
|
@ -34,6 +35,7 @@ const described = [
|
||||||
PowerController,
|
PowerController,
|
||||||
PowerLevelController,
|
PowerLevelController,
|
||||||
RangeController,
|
RangeController,
|
||||||
|
SecurityPanelController,
|
||||||
TemperatureSensor,
|
TemperatureSensor,
|
||||||
ThermostatController,
|
ThermostatController,
|
||||||
ThermostatControllerSchedule,
|
ThermostatControllerSchedule,
|
||||||
|
|
@ -63,7 +65,7 @@ export const registry = {
|
||||||
return [...descriptors.values()].sort((a, b) => (a.namespace < b.namespace ? -1 : 1));
|
return [...descriptors.values()].sort((a, b) => (a.namespace < b.namespace ? -1 : 1));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerController, PowerLevelController, RangeController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, };
|
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerController, PowerLevelController, RangeController, SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, };
|
||||||
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
|
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
|
||||||
export { asset, text } from "./resources.js";
|
export { asset, text } from "./resources.js";
|
||||||
export { semantics, SemanticsBuilder } from "./semantics.js";
|
export { semantics, SemanticsBuilder } from "./semantics.js";
|
||||||
|
|
|
||||||
67
dist/esm/registry/interfaces/SecurityPanelController.d.ts
vendored
Normal file
67
dist/esm/registry/interfaces/SecurityPanelController.d.ts
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* A security panel. Arm is answered with Arm.Response, Disarm with a Response, an error of errorTypes under the
|
||||||
|
* namespace of the interface (alexa-securitypanelcontroller-errorresponse.html).
|
||||||
|
*
|
||||||
|
* Alexa sends Disarm only when the user switched on disarming by voice in the Alexa app. A panel declared with
|
||||||
|
* supportedAuthorizationTypes gets the PIN the user said in payload.authorization and has to check it: a wrong PIN
|
||||||
|
* is answered with UNAUTHORIZED. Without authorization in the payload Alexa checked a voice code of its own.
|
||||||
|
*
|
||||||
|
* A panel that is ARMED_AWAY is disarmed before it is armed another way: Arm to ARMED_STAY or ARMED_NIGHT is
|
||||||
|
* then answered with AUTHORIZATION_REQUIRED. Arm to the state the panel is in is answered with Arm.Response.
|
||||||
|
*/
|
||||||
|
export declare const SecurityPanelController: import("../types.js").InterfaceDescriptor<{
|
||||||
|
armState: {
|
||||||
|
name: string;
|
||||||
|
value: import("../schema.js").EnumSchema<"ARMED_AWAY" | "ARMED_STAY" | "ARMED_NIGHT" | "DISARMED">;
|
||||||
|
};
|
||||||
|
burglaryAlarm: {
|
||||||
|
name: string;
|
||||||
|
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
value: import("../schema.js").EnumSchema<"OK" | "ALARM">;
|
||||||
|
}>>;
|
||||||
|
};
|
||||||
|
fireAlarm: {
|
||||||
|
name: string;
|
||||||
|
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
value: import("../schema.js").EnumSchema<"OK" | "ALARM">;
|
||||||
|
}>>;
|
||||||
|
};
|
||||||
|
carbonMonoxideAlarm: {
|
||||||
|
name: string;
|
||||||
|
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
value: import("../schema.js").EnumSchema<"OK" | "ALARM">;
|
||||||
|
}>>;
|
||||||
|
};
|
||||||
|
waterAlarm: {
|
||||||
|
name: string;
|
||||||
|
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
value: import("../schema.js").EnumSchema<"OK" | "ALARM">;
|
||||||
|
}>>;
|
||||||
|
};
|
||||||
|
}, {
|
||||||
|
Arm: {
|
||||||
|
name: string;
|
||||||
|
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
armState: import("../schema.js").EnumSchema<"ARMED_AWAY" | "ARMED_STAY" | "ARMED_NIGHT" | "DISARMED">;
|
||||||
|
bypassType: import("../schema.js").OptionalSchema<"BYPASS_ALL">;
|
||||||
|
}>>;
|
||||||
|
note: string;
|
||||||
|
};
|
||||||
|
Disarm: {
|
||||||
|
name: string;
|
||||||
|
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
authorization: import("../schema.js").OptionalSchema<import("../schema.js").InferShape<{
|
||||||
|
type: import("../schema.js").EnumSchema<"FOUR_DIGIT_PIN">;
|
||||||
|
value: import("../schema.js").Schema<string>;
|
||||||
|
}>>;
|
||||||
|
}>>;
|
||||||
|
note: string;
|
||||||
|
};
|
||||||
|
}, import("../schema.js").InferShape<{
|
||||||
|
/** Default: Alexa takes the panel to have all four. */
|
||||||
|
supportedArmStates: import("../schema.js").OptionalSchema<("ARMED_AWAY" | "ARMED_STAY" | "ARMED_NIGHT" | "DISARMED")[]>;
|
||||||
|
/** For a panel that has four digit PINs and checks the PIN of a Disarm itself. */
|
||||||
|
supportedAuthorizationTypes: import("../schema.js").OptionalSchema<"FOUR_DIGIT_PIN"[]>;
|
||||||
|
/** The alarms the panel reports. Default: none. */
|
||||||
|
alarms: import("../schema.js").OptionalSchema<("burglaryAlarm" | "fireAlarm" | "carbonMonoxideAlarm" | "waterAlarm")[]>;
|
||||||
|
}>, false>;
|
||||||
88
dist/esm/registry/interfaces/SecurityPanelController.js
vendored
Normal file
88
dist/esm/registry/interfaces/SecurityPanelController.js
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { s } from "../schema.js";
|
||||||
|
import { DeclarationError, defineInterface } from "../types.js";
|
||||||
|
const ARM_STATES = ["ARMED_AWAY", "ARMED_STAY", "ARMED_NIGHT", "DISARMED"];
|
||||||
|
const ALARMS = ["burglaryAlarm", "fireAlarm", "carbonMonoxideAlarm", "waterAlarm"];
|
||||||
|
const alarm = s.object({ value: s.enum("OK", "ALARM") });
|
||||||
|
// A sensor that is left out when the panel arms. The endpointId is for a sensor that is an endpoint of its own.
|
||||||
|
const bypassed = s.object({ friendlyName: s.optional(s.string({ min: 1 })), endpointId: s.optional(s.string({ min: 1 })) });
|
||||||
|
const armResponse = s.object({
|
||||||
|
/** How long the occupants have to leave before the panel arms. 0: it arms at once. */
|
||||||
|
exitDelayInSeconds: s.optional(s.number({ min: 0, max: 255, integer: true })),
|
||||||
|
/** For an Arm that came with bypassType only. */
|
||||||
|
bypassedEndpoints: s.optional(s.array(bypassed)),
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* A security panel. Arm is answered with Arm.Response, Disarm with a Response, an error of errorTypes under the
|
||||||
|
* namespace of the interface (alexa-securitypanelcontroller-errorresponse.html).
|
||||||
|
*
|
||||||
|
* Alexa sends Disarm only when the user switched on disarming by voice in the Alexa app. A panel declared with
|
||||||
|
* supportedAuthorizationTypes gets the PIN the user said in payload.authorization and has to check it: a wrong PIN
|
||||||
|
* is answered with UNAUTHORIZED. Without authorization in the payload Alexa checked a voice code of its own.
|
||||||
|
*
|
||||||
|
* A panel that is ARMED_AWAY is disarmed before it is armed another way: Arm to ARMED_STAY or ARMED_NIGHT is
|
||||||
|
* then answered with AUTHORIZATION_REQUIRED. Arm to the state the panel is in is answered with Arm.Response.
|
||||||
|
*/
|
||||||
|
export const SecurityPanelController = defineInterface({
|
||||||
|
namespace: "Alexa.SecurityPanelController",
|
||||||
|
version: "3",
|
||||||
|
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-securitypanelcontroller.html",
|
||||||
|
kind: "controller",
|
||||||
|
tier: 2,
|
||||||
|
instanced: false,
|
||||||
|
properties: {
|
||||||
|
armState: { name: "armState", value: s.enum(...ARM_STATES) },
|
||||||
|
burglaryAlarm: { name: "burglaryAlarm", value: alarm },
|
||||||
|
fireAlarm: { name: "fireAlarm", value: alarm },
|
||||||
|
carbonMonoxideAlarm: { name: "carbonMonoxideAlarm", value: alarm },
|
||||||
|
waterAlarm: { name: "waterAlarm", value: alarm },
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
Arm: {
|
||||||
|
name: "Arm",
|
||||||
|
payload: s.object({ armState: s.enum(...ARM_STATES), bypassType: s.optional(s.enum("BYPASS_ALL")) }),
|
||||||
|
note: "bypassType: the user said to arm with the open sensors left out, which BYPASS_NEEDED listed",
|
||||||
|
},
|
||||||
|
Disarm: {
|
||||||
|
name: "Disarm",
|
||||||
|
payload: s.object({
|
||||||
|
authorization: s.optional(s.object({
|
||||||
|
type: s.enum("FOUR_DIGIT_PIN"),
|
||||||
|
value: s.string({ pattern: /^\d{4}$/, expects: "a PIN of four digits" }),
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
note: "authorization: the PIN the user said, for the panel to check",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: s.object({
|
||||||
|
/** Default: Alexa takes the panel to have all four. */
|
||||||
|
supportedArmStates: s.optional(s.array(s.enum(...ARM_STATES), { min: 1 })),
|
||||||
|
/** For a panel that has four digit PINs and checks the PIN of a Disarm itself. */
|
||||||
|
supportedAuthorizationTypes: s.optional(s.array(s.enum("FOUR_DIGIT_PIN"), { min: 1 })),
|
||||||
|
/** The alarms the panel reports. Default: none. */
|
||||||
|
alarms: s.optional(s.array(s.enum(...ALARMS))),
|
||||||
|
}, { unknownKeys: "reject" }),
|
||||||
|
discovery({ options: { supportedArmStates, supportedAuthorizationTypes, alarms = [] } }) {
|
||||||
|
const configuration = {
|
||||||
|
...(supportedArmStates && { supportedArmStates: supportedArmStates.map((value) => ({ value })) }),
|
||||||
|
...(supportedAuthorizationTypes && { supportedAuthorizationTypes: supportedAuthorizationTypes.map((type) => ({ type })) }),
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
supported: ["armState", ...alarms],
|
||||||
|
...(Object.keys(configuration).length > 0 && { configuration }),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
validate(capability) {
|
||||||
|
const { supportedArmStates = [], supportedAuthorizationTypes = [], alarms = [] } = capability.options;
|
||||||
|
const lists = [supportedArmStates, supportedAuthorizationTypes, alarms];
|
||||||
|
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`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responseFor: (directive) => (directive === "Arm"
|
||||||
|
? { namespace: "Alexa.SecurityPanelController", name: "Arm.Response", payload: armResponse }
|
||||||
|
: undefined),
|
||||||
|
errorNamespace: "Alexa.SecurityPanelController",
|
||||||
|
errorTypes: ["AUTHORIZATION_REQUIRED", "BYPASS_NEEDED", "NOT_READY", "UNAUTHORIZED", "UNCLEARED_ALARM", "UNCLEARED_TROUBLE"],
|
||||||
|
});
|
||||||
1
dist/esm/registry/interfaces/stubs.js
vendored
1
dist/esm/registry/interfaces/stubs.js
vendored
|
|
@ -48,7 +48,6 @@ const TABLE = [
|
||||||
["Alexa.RecordController", "3", [], "alexa-recordcontroller.html"],
|
["Alexa.RecordController", "3", [], "alexa-recordcontroller.html"],
|
||||||
["Alexa.RemoteVideoPlayer", "1", [], "alexa-remotevideoplayer.html"],
|
["Alexa.RemoteVideoPlayer", "1", [], "alexa-remotevideoplayer.html"],
|
||||||
["Alexa.SceneController", "3", [], "alexa-scenecontroller.html"],
|
["Alexa.SceneController", "3", [], "alexa-scenecontroller.html"],
|
||||||
["Alexa.SecurityPanelController", "1", [], "alexa-securitypanelcontroller.html"],
|
|
||||||
["Alexa.SecurityPanelController.Alert", "1", [], "alexa-securitypanelcontroller-alert.html"],
|
["Alexa.SecurityPanelController.Alert", "1", [], "alexa-securitypanelcontroller-alert.html"],
|
||||||
["Alexa.SeekController", "3", [], "alexa-seekcontroller.html"],
|
["Alexa.SeekController", "3", [], "alexa-seekcontroller.html"],
|
||||||
["Alexa.SimpleEventSource", "1", [], "alexa-simpleeventsource.html"],
|
["Alexa.SimpleEventSource", "1", [], "alexa-simpleeventsource.html"],
|
||||||
|
|
|
||||||
2
dist/types/index.d.ts
vendored
2
dist/types/index.d.ts
vendored
|
|
@ -5,7 +5,7 @@ export type { EndpointDefinition, EndpointJson } from "./device/Device.js";
|
||||||
export { Capability } from "./device/Capability.js";
|
export { Capability } from "./device/Capability.js";
|
||||||
export type { CapabilityJson, CommonOptions, Declaration } from "./device/Capability.js";
|
export type { CapabilityJson, CommonOptions, Declaration } from "./device/Capability.js";
|
||||||
export { registry, DeclarationError, SchemaError } from "./registry/index.js";
|
export { registry, DeclarationError, SchemaError } from "./registry/index.js";
|
||||||
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerLevelController, RangeController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, } from "./registry/index.js";
|
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerLevelController, RangeController, SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, } from "./registry/index.js";
|
||||||
export { PowerController, EndpointHealth } from "./compat/enums.js";
|
export { PowerController, EndpointHealth } from "./compat/enums.js";
|
||||||
export { asset, text, semantics, SemanticsBuilder } from "./registry/index.js";
|
export { asset, text, semantics, SemanticsBuilder } from "./registry/index.js";
|
||||||
export { ASSETS as Assets, UNITS_OF_MEASURE as Units, ACTIONS as Actions, STATES as States, DISPLAY_CATEGORIES as DisplayCategories, THERMOSTAT_MODES as ThermostatModes, } from "./registry/index.js";
|
export { ASSETS as Assets, UNITS_OF_MEASURE as Units, ACTIONS as Actions, STATES as States, DISPLAY_CATEGORIES as DisplayCategories, THERMOSTAT_MODES as ThermostatModes, } from "./registry/index.js";
|
||||||
|
|
|
||||||
3
dist/types/registry/index.d.ts
vendored
3
dist/types/registry/index.d.ts
vendored
|
|
@ -12,6 +12,7 @@ import { PercentageController } from "./interfaces/PercentageController.js";
|
||||||
import { PowerController } from "./interfaces/PowerController.js";
|
import { PowerController } from "./interfaces/PowerController.js";
|
||||||
import { PowerLevelController } from "./interfaces/PowerLevelController.js";
|
import { PowerLevelController } from "./interfaces/PowerLevelController.js";
|
||||||
import { RangeController } from "./interfaces/RangeController.js";
|
import { RangeController } from "./interfaces/RangeController.js";
|
||||||
|
import { SecurityPanelController } from "./interfaces/SecurityPanelController.js";
|
||||||
import { TemperatureSensor } from "./interfaces/TemperatureSensor.js";
|
import { TemperatureSensor } from "./interfaces/TemperatureSensor.js";
|
||||||
import { ThermostatController } from "./interfaces/ThermostatController.js";
|
import { ThermostatController } from "./interfaces/ThermostatController.js";
|
||||||
import { ThermostatControllerSchedule } from "./interfaces/ThermostatControllerSchedule.js";
|
import { ThermostatControllerSchedule } from "./interfaces/ThermostatControllerSchedule.js";
|
||||||
|
|
@ -25,7 +26,7 @@ export declare const registry: {
|
||||||
/** Every descriptor, ordered by namespace. */
|
/** Every descriptor, ordered by namespace. */
|
||||||
list(): AnyDescriptor[];
|
list(): AnyDescriptor[];
|
||||||
};
|
};
|
||||||
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerController, PowerLevelController, RangeController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, };
|
export { Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, HumiditySensor, LockController, ModeController, MotionSensor, PercentageController, PowerController, PowerLevelController, RangeController, SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController, };
|
||||||
export type { Mode } from "./interfaces/ModeController.js";
|
export type { Mode } from "./interfaces/ModeController.js";
|
||||||
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
|
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
|
||||||
export type { ThermostatModeName } from "./interfaces/ThermostatController.js";
|
export type { ThermostatModeName } from "./interfaces/ThermostatController.js";
|
||||||
|
|
|
||||||
67
dist/types/registry/interfaces/SecurityPanelController.d.ts
vendored
Normal file
67
dist/types/registry/interfaces/SecurityPanelController.d.ts
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* A security panel. Arm is answered with Arm.Response, Disarm with a Response, an error of errorTypes under the
|
||||||
|
* namespace of the interface (alexa-securitypanelcontroller-errorresponse.html).
|
||||||
|
*
|
||||||
|
* Alexa sends Disarm only when the user switched on disarming by voice in the Alexa app. A panel declared with
|
||||||
|
* supportedAuthorizationTypes gets the PIN the user said in payload.authorization and has to check it: a wrong PIN
|
||||||
|
* is answered with UNAUTHORIZED. Without authorization in the payload Alexa checked a voice code of its own.
|
||||||
|
*
|
||||||
|
* A panel that is ARMED_AWAY is disarmed before it is armed another way: Arm to ARMED_STAY or ARMED_NIGHT is
|
||||||
|
* then answered with AUTHORIZATION_REQUIRED. Arm to the state the panel is in is answered with Arm.Response.
|
||||||
|
*/
|
||||||
|
export declare const SecurityPanelController: import("../types.js").InterfaceDescriptor<{
|
||||||
|
armState: {
|
||||||
|
name: string;
|
||||||
|
value: import("../schema.js").EnumSchema<"ARMED_AWAY" | "ARMED_STAY" | "ARMED_NIGHT" | "DISARMED">;
|
||||||
|
};
|
||||||
|
burglaryAlarm: {
|
||||||
|
name: string;
|
||||||
|
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
value: import("../schema.js").EnumSchema<"OK" | "ALARM">;
|
||||||
|
}>>;
|
||||||
|
};
|
||||||
|
fireAlarm: {
|
||||||
|
name: string;
|
||||||
|
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
value: import("../schema.js").EnumSchema<"OK" | "ALARM">;
|
||||||
|
}>>;
|
||||||
|
};
|
||||||
|
carbonMonoxideAlarm: {
|
||||||
|
name: string;
|
||||||
|
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
value: import("../schema.js").EnumSchema<"OK" | "ALARM">;
|
||||||
|
}>>;
|
||||||
|
};
|
||||||
|
waterAlarm: {
|
||||||
|
name: string;
|
||||||
|
value: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
value: import("../schema.js").EnumSchema<"OK" | "ALARM">;
|
||||||
|
}>>;
|
||||||
|
};
|
||||||
|
}, {
|
||||||
|
Arm: {
|
||||||
|
name: string;
|
||||||
|
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
armState: import("../schema.js").EnumSchema<"ARMED_AWAY" | "ARMED_STAY" | "ARMED_NIGHT" | "DISARMED">;
|
||||||
|
bypassType: import("../schema.js").OptionalSchema<"BYPASS_ALL">;
|
||||||
|
}>>;
|
||||||
|
note: string;
|
||||||
|
};
|
||||||
|
Disarm: {
|
||||||
|
name: string;
|
||||||
|
payload: import("../schema.js").Schema<import("../schema.js").InferShape<{
|
||||||
|
authorization: import("../schema.js").OptionalSchema<import("../schema.js").InferShape<{
|
||||||
|
type: import("../schema.js").EnumSchema<"FOUR_DIGIT_PIN">;
|
||||||
|
value: import("../schema.js").Schema<string>;
|
||||||
|
}>>;
|
||||||
|
}>>;
|
||||||
|
note: string;
|
||||||
|
};
|
||||||
|
}, import("../schema.js").InferShape<{
|
||||||
|
/** Default: Alexa takes the panel to have all four. */
|
||||||
|
supportedArmStates: import("../schema.js").OptionalSchema<("ARMED_AWAY" | "ARMED_STAY" | "ARMED_NIGHT" | "DISARMED")[]>;
|
||||||
|
/** For a panel that has four digit PINs and checks the PIN of a Disarm itself. */
|
||||||
|
supportedAuthorizationTypes: import("../schema.js").OptionalSchema<"FOUR_DIGIT_PIN"[]>;
|
||||||
|
/** The alarms the panel reports. Default: none. */
|
||||||
|
alarms: import("../schema.js").OptionalSchema<("burglaryAlarm" | "fireAlarm" | "carbonMonoxideAlarm" | "waterAlarm")[]>;
|
||||||
|
}>, false>;
|
||||||
|
|
@ -10,8 +10,8 @@ export type { CapabilityJson, CommonOptions, Declaration } from "./device/Capabi
|
||||||
export { registry, DeclarationError, SchemaError } from "./registry/index.js";
|
export { registry, DeclarationError, SchemaError } from "./registry/index.js";
|
||||||
export {
|
export {
|
||||||
Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, HumiditySensor, LockController,
|
Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, HumiditySensor, LockController,
|
||||||
ModeController, MotionSensor, PercentageController, PowerLevelController, RangeController, TemperatureSensor,
|
ModeController, MotionSensor, PercentageController, PowerLevelController, RangeController, SecurityPanelController,
|
||||||
ThermostatController, ThermostatControllerSchedule, ToggleController,
|
TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController,
|
||||||
} from "./registry/index.js";
|
} from "./registry/index.js";
|
||||||
export { PowerController, EndpointHealth } from "./compat/enums.js";
|
export { PowerController, EndpointHealth } from "./compat/enums.js";
|
||||||
export { asset, text, semantics, SemanticsBuilder } from "./registry/index.js";
|
export { asset, text, semantics, SemanticsBuilder } from "./registry/index.js";
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { PercentageController } from "./interfaces/PercentageController.js";
|
||||||
import { PowerController } from "./interfaces/PowerController.js";
|
import { PowerController } from "./interfaces/PowerController.js";
|
||||||
import { PowerLevelController } from "./interfaces/PowerLevelController.js";
|
import { PowerLevelController } from "./interfaces/PowerLevelController.js";
|
||||||
import { RangeController } from "./interfaces/RangeController.js";
|
import { RangeController } from "./interfaces/RangeController.js";
|
||||||
|
import { SecurityPanelController } from "./interfaces/SecurityPanelController.js";
|
||||||
import { TemperatureSensor } from "./interfaces/TemperatureSensor.js";
|
import { TemperatureSensor } from "./interfaces/TemperatureSensor.js";
|
||||||
import { ThermostatController } from "./interfaces/ThermostatController.js";
|
import { ThermostatController } from "./interfaces/ThermostatController.js";
|
||||||
import { ThermostatControllerSchedule } from "./interfaces/ThermostatControllerSchedule.js";
|
import { ThermostatControllerSchedule } from "./interfaces/ThermostatControllerSchedule.js";
|
||||||
|
|
@ -36,6 +37,7 @@ const described: readonly AnyDescriptor[] = [
|
||||||
PowerController,
|
PowerController,
|
||||||
PowerLevelController,
|
PowerLevelController,
|
||||||
RangeController,
|
RangeController,
|
||||||
|
SecurityPanelController,
|
||||||
TemperatureSensor,
|
TemperatureSensor,
|
||||||
ThermostatController,
|
ThermostatController,
|
||||||
ThermostatControllerSchedule,
|
ThermostatControllerSchedule,
|
||||||
|
|
@ -71,7 +73,7 @@ export const registry = {
|
||||||
export {
|
export {
|
||||||
Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, HumiditySensor,
|
Alexa, BrightnessController, ColorController, ColorTemperatureController, ContactSensor, EndpointHealth, HumiditySensor,
|
||||||
LockController, ModeController, MotionSensor, PercentageController, PowerController, PowerLevelController, RangeController,
|
LockController, ModeController, MotionSensor, PercentageController, PowerController, PowerLevelController, RangeController,
|
||||||
TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController,
|
SecurityPanelController, TemperatureSensor, ThermostatController, ThermostatControllerSchedule, ToggleController,
|
||||||
};
|
};
|
||||||
export type { Mode } from "./interfaces/ModeController.js";
|
export type { Mode } from "./interfaces/ModeController.js";
|
||||||
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
|
export { THERMOSTAT_MODES } from "./interfaces/ThermostatController.js";
|
||||||
|
|
|
||||||
91
src/registry/interfaces/SecurityPanelController.ts
Normal file
91
src/registry/interfaces/SecurityPanelController.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { s } from "../schema.js";
|
||||||
|
import { DeclarationError, defineInterface } from "../types.js";
|
||||||
|
|
||||||
|
const ARM_STATES = ["ARMED_AWAY", "ARMED_STAY", "ARMED_NIGHT", "DISARMED"] as const;
|
||||||
|
const ALARMS = ["burglaryAlarm", "fireAlarm", "carbonMonoxideAlarm", "waterAlarm"] as const;
|
||||||
|
|
||||||
|
const alarm = s.object({ value: s.enum("OK", "ALARM") });
|
||||||
|
// A sensor that is left out when the panel arms. The endpointId is for a sensor that is an endpoint of its own.
|
||||||
|
const bypassed = s.object({ friendlyName: s.optional(s.string({ min: 1 })), endpointId: s.optional(s.string({ min: 1 })) });
|
||||||
|
|
||||||
|
const armResponse = s.object({
|
||||||
|
/** How long the occupants have to leave before the panel arms. 0: it arms at once. */
|
||||||
|
exitDelayInSeconds: s.optional(s.number({ min: 0, max: 255, integer: true })),
|
||||||
|
/** For an Arm that came with bypassType only. */
|
||||||
|
bypassedEndpoints: s.optional(s.array(bypassed)),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A security panel. Arm is answered with Arm.Response, Disarm with a Response, an error of errorTypes under the
|
||||||
|
* namespace of the interface (alexa-securitypanelcontroller-errorresponse.html).
|
||||||
|
*
|
||||||
|
* Alexa sends Disarm only when the user switched on disarming by voice in the Alexa app. A panel declared with
|
||||||
|
* supportedAuthorizationTypes gets the PIN the user said in payload.authorization and has to check it: a wrong PIN
|
||||||
|
* is answered with UNAUTHORIZED. Without authorization in the payload Alexa checked a voice code of its own.
|
||||||
|
*
|
||||||
|
* A panel that is ARMED_AWAY is disarmed before it is armed another way: Arm to ARMED_STAY or ARMED_NIGHT is
|
||||||
|
* then answered with AUTHORIZATION_REQUIRED. Arm to the state the panel is in is answered with Arm.Response.
|
||||||
|
*/
|
||||||
|
export const SecurityPanelController = defineInterface({
|
||||||
|
namespace: "Alexa.SecurityPanelController",
|
||||||
|
version: "3",
|
||||||
|
doc: "https://developer.amazon.com/docs/alexaplus/device-apis/alexa-securitypanelcontroller.html",
|
||||||
|
kind: "controller",
|
||||||
|
tier: 2,
|
||||||
|
instanced: false,
|
||||||
|
properties: {
|
||||||
|
armState: { name: "armState", value: s.enum(...ARM_STATES) },
|
||||||
|
burglaryAlarm: { name: "burglaryAlarm", value: alarm },
|
||||||
|
fireAlarm: { name: "fireAlarm", value: alarm },
|
||||||
|
carbonMonoxideAlarm: { name: "carbonMonoxideAlarm", value: alarm },
|
||||||
|
waterAlarm: { name: "waterAlarm", value: alarm },
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
Arm: {
|
||||||
|
name: "Arm",
|
||||||
|
payload: s.object({ armState: s.enum(...ARM_STATES), bypassType: s.optional(s.enum("BYPASS_ALL")) }),
|
||||||
|
note: "bypassType: the user said to arm with the open sensors left out, which BYPASS_NEEDED listed",
|
||||||
|
},
|
||||||
|
Disarm: {
|
||||||
|
name: "Disarm",
|
||||||
|
payload: s.object({
|
||||||
|
authorization: s.optional(s.object({
|
||||||
|
type: s.enum("FOUR_DIGIT_PIN"),
|
||||||
|
value: s.string({ pattern: /^\d{4}$/, expects: "a PIN of four digits" }),
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
note: "authorization: the PIN the user said, for the panel to check",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: s.object({
|
||||||
|
/** Default: Alexa takes the panel to have all four. */
|
||||||
|
supportedArmStates: s.optional(s.array(s.enum(...ARM_STATES), { min: 1 })),
|
||||||
|
/** For a panel that has four digit PINs and checks the PIN of a Disarm itself. */
|
||||||
|
supportedAuthorizationTypes: s.optional(s.array(s.enum("FOUR_DIGIT_PIN"), { min: 1 })),
|
||||||
|
/** The alarms the panel reports. Default: none. */
|
||||||
|
alarms: s.optional(s.array(s.enum(...ALARMS))),
|
||||||
|
}, { unknownKeys: "reject" }),
|
||||||
|
discovery({ options: { supportedArmStates, supportedAuthorizationTypes, alarms = [] } }) {
|
||||||
|
const configuration = {
|
||||||
|
...(supportedArmStates && { supportedArmStates: supportedArmStates.map((value) => ({ value })) }),
|
||||||
|
...(supportedAuthorizationTypes && { supportedAuthorizationTypes: supportedAuthorizationTypes.map((type) => ({ type })) }),
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
supported: ["armState", ...alarms],
|
||||||
|
...(Object.keys(configuration).length > 0 && { configuration }),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
validate(capability) {
|
||||||
|
const { supportedArmStates = [], supportedAuthorizationTypes = [], alarms = [] } = capability.options;
|
||||||
|
const lists: ReadonlyArray<readonly string[]> = [supportedArmStates, supportedAuthorizationTypes, alarms];
|
||||||
|
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`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responseFor: (directive) => (directive === "Arm"
|
||||||
|
? { namespace: "Alexa.SecurityPanelController", name: "Arm.Response", payload: armResponse }
|
||||||
|
: undefined),
|
||||||
|
errorNamespace: "Alexa.SecurityPanelController",
|
||||||
|
errorTypes: ["AUTHORIZATION_REQUIRED", "BYPASS_NEEDED", "NOT_READY", "UNAUTHORIZED", "UNCLEARED_ALARM", "UNCLEARED_TROUBLE"],
|
||||||
|
});
|
||||||
|
|
@ -54,7 +54,6 @@ const TABLE: readonly Row[] = [
|
||||||
["Alexa.RecordController", "3", [], "alexa-recordcontroller.html"],
|
["Alexa.RecordController", "3", [], "alexa-recordcontroller.html"],
|
||||||
["Alexa.RemoteVideoPlayer", "1", [], "alexa-remotevideoplayer.html"],
|
["Alexa.RemoteVideoPlayer", "1", [], "alexa-remotevideoplayer.html"],
|
||||||
["Alexa.SceneController", "3", [], "alexa-scenecontroller.html"],
|
["Alexa.SceneController", "3", [], "alexa-scenecontroller.html"],
|
||||||
["Alexa.SecurityPanelController", "1", [], "alexa-securitypanelcontroller.html"],
|
|
||||||
["Alexa.SecurityPanelController.Alert", "1", [], "alexa-securitypanelcontroller-alert.html"],
|
["Alexa.SecurityPanelController.Alert", "1", [], "alexa-securitypanelcontroller-alert.html"],
|
||||||
["Alexa.SeekController", "3", [], "alexa-seekcontroller.html"],
|
["Alexa.SeekController", "3", [], "alexa-seekcontroller.html"],
|
||||||
["Alexa.SimpleEventSource", "1", [], "alexa-simpleeventsource.html"],
|
["Alexa.SimpleEventSource", "1", [], "alexa-simpleeventsource.html"],
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
const { test } = require("node:test");
|
const { test } = require("node:test");
|
||||||
const assert = require("node:assert/strict");
|
const assert = require("node:assert/strict");
|
||||||
const {
|
const {
|
||||||
Alex2MQTT, AlexaErrors, LockController, MemoryPublisher, PowerLevelController, ThermostatController,
|
Alex2MQTT, AlexaErrors, LockController, MemoryPublisher, PowerLevelController, SecurityPanelController,
|
||||||
ThermostatControllerSchedule, registry,
|
ThermostatController, ThermostatControllerSchedule, registry,
|
||||||
} = require("alex2node");
|
} = require("alex2node");
|
||||||
const interfaces = require("../fixtures/interfaces.js");
|
const interfaces = require("../fixtures/interfaces.js");
|
||||||
const { doc, examples } = require("../helpers/fixtures.js");
|
const { doc, examples } = require("../helpers/fixtures.js");
|
||||||
|
|
@ -33,15 +33,20 @@ for (const { namespace, page, declared, refused } of interfaces.filter((entry) =
|
||||||
test(`${namespace}: the directives of ${page}.html reach the handler, a payload that does not fit is answered with INVALID_VALUE`, async () => {
|
test(`${namespace}: the directives of ${page}.html reach the handler, a payload that does not fit is answered with INVALID_VALUE`, async () => {
|
||||||
const { device, receive, answers } = bridgeWithout();
|
const { device, receive, answers } = bridgeWithout();
|
||||||
const seen = [];
|
const seen = [];
|
||||||
device.add(registry.get(namespace), declared[0].options).on("*", (ctx) => {
|
const descriptor = registry.get(namespace);
|
||||||
|
device.add(descriptor, declared[0].options).on("*", (ctx) => {
|
||||||
seen.push([ctx.name, ctx.payload]);
|
seen.push([ctx.name, ctx.payload]);
|
||||||
return ctx.respond();
|
return ctx.respond();
|
||||||
});
|
});
|
||||||
|
|
||||||
const printed = examples(page, ".directive").map((name) => doc(page, name).directive);
|
const printed = examples(page, ".directive").map((name) => doc(page, name).directive)
|
||||||
|
.filter((example) => example.header.namespace === namespace);
|
||||||
for (const example of printed) await receive(example);
|
for (const example of printed) await receive(example);
|
||||||
assert.deepEqual(seen, printed.map(({ header, payload }) => [header.name, payload]));
|
assert.deepEqual(seen, printed.map(({ header, payload }) => [header.name, payload]));
|
||||||
assert.deepEqual(answers(), printed.map(() => ["alexaResponce", "Alexa", "Response", {}]));
|
assert.deepEqual(answers(), printed.map(({ header }) => {
|
||||||
|
const own = descriptor.responseFor && descriptor.responseFor(header.name);
|
||||||
|
return ["alexaResponce", own ? own.namespace : "Alexa", own ? own.name : "Response", {}];
|
||||||
|
}));
|
||||||
|
|
||||||
for (const [name, payload] of refused) await receive(directive(namespace, name, "device-1", "ct-1", payload));
|
for (const [name, payload] of refused) await receive(directive(namespace, name, "device-1", "ct-1", payload));
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
|
|
@ -117,3 +122,62 @@ test("Alexa.ThermostatController: an error of the thermostat goes under its name
|
||||||
message: "Alexa.ThermostatController.Schedule as device-1 declares it does not take SetAdaptiveRecovery",
|
message: "Alexa.ThermostatController.Schedule as device-1 declares it does not take SetAdaptiveRecovery",
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Alexa.SecurityPanelController: Arm is answered with Arm.Response, Disarm with a Response when the PIN is the one of the panel", async () => {
|
||||||
|
const { device, sent, receive, answers } = bridgeWithout();
|
||||||
|
const page = "alexa-securitypanelcontroller";
|
||||||
|
const errors = "alexa-securitypanelcontroller-errorresponse";
|
||||||
|
const open = doc(errors, "BYPASS_NEEDED.error").event.payload;
|
||||||
|
const bypassed = doc(page, "Arm-bypass.response").event.payload;
|
||||||
|
let armState = "DISARMED";
|
||||||
|
const panel = device.add(SecurityPanelController, { supportedAuthorizationTypes: ["FOUR_DIGIT_PIN"], alarms: ["burglaryAlarm"] });
|
||||||
|
device.state((s) => s.set(panel, "armState", armState).set(panel, "burglaryAlarm", { value: "OK" }));
|
||||||
|
panel.on("Arm", (ctx) => {
|
||||||
|
if (armState === "ARMED_AWAY" && ctx.payload.armState !== "ARMED_AWAY") {
|
||||||
|
throw AlexaErrors.of("AUTHORIZATION_REQUIRED", "You must disarm the system before you can set the requested arm state.");
|
||||||
|
}
|
||||||
|
if (!ctx.payload.bypassType) throw AlexaErrors.bypassNeeded(open.message, open.endpointsNeedingBypass);
|
||||||
|
armState = ctx.payload.armState;
|
||||||
|
return ctx.respond(undefined, { payload: bypassed });
|
||||||
|
});
|
||||||
|
panel.on("Disarm", (ctx) => {
|
||||||
|
const { authorization } = ctx.payload;
|
||||||
|
if (!authorization || authorization.value !== "1234") throw AlexaErrors.of("UNAUTHORIZED", "The PIN code is not correct.");
|
||||||
|
armState = "DISARMED";
|
||||||
|
return ctx.respond();
|
||||||
|
});
|
||||||
|
|
||||||
|
await receive(doc(page, "Arm.directive").directive);
|
||||||
|
await receive(doc(page, "Arm-bypass.directive").directive);
|
||||||
|
await receive(directive(panel.namespace, "Arm", "device-1", "ct", { armState: "ARMED_NIGHT" }));
|
||||||
|
await receive(directive(panel.namespace, "Disarm", "device-1", "ct", { authorization: { type: "FOUR_DIGIT_PIN", value: "4321" } }));
|
||||||
|
await receive(directive(panel.namespace, "Disarm", "device-1", "ct", {}));
|
||||||
|
await receive(doc(page, "Disarm.directive").directive);
|
||||||
|
|
||||||
|
const refused = (type) => {
|
||||||
|
const { header, payload } = doc(errors, `${type}.error`).event;
|
||||||
|
return ["alexaResponce", header.namespace, header.name, payload];
|
||||||
|
};
|
||||||
|
assert.deepEqual(answers(), [
|
||||||
|
refused("BYPASS_NEEDED"),
|
||||||
|
["alexaResponce", "Alexa.SecurityPanelController", "Arm.Response", bypassed],
|
||||||
|
refused("AUTHORIZATION_REQUIRED"),
|
||||||
|
refused("UNAUTHORIZED"),
|
||||||
|
refused("UNAUTHORIZED"),
|
||||||
|
["alexaResponce", "Alexa", "Response", {}],
|
||||||
|
]);
|
||||||
|
assert.equal(refused("UNAUTHORIZED")[1], "Alexa.SecurityPanelController");
|
||||||
|
const states = sent.published.filter(({ message }) => message.context)
|
||||||
|
.map(({ message }) => message.context.properties.find((property) => property.name === "armState").value);
|
||||||
|
assert.deepEqual(states, ["ARMED_AWAY", "DISARMED"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Alexa.SecurityPanelController: a payload that is no Arm.Response is not sent, the directive is answered with INTERNAL_ERROR", async () => {
|
||||||
|
const { device, receive, answers } = bridgeWithout();
|
||||||
|
device.add(SecurityPanelController).on("Arm", (ctx) => ctx.respond(undefined, { payload: { exitDelayInSeconds: 300 } }));
|
||||||
|
await receive(doc("alexa-securitypanelcontroller", "Arm.directive").directive);
|
||||||
|
assert.deepEqual(answers(), [["alexaResponce", "Alexa", "ErrorResponse", {
|
||||||
|
type: "INTERNAL_ERROR",
|
||||||
|
message: "payload.exitDelayInSeconds: expected an integer from 0 to 255, got 300",
|
||||||
|
}]]);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "ErrorResponse",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token that matches the request",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"type": "AUTHORIZATION_REQUIRED",
|
||||||
|
"message": "You must disarm the system before you can set the requested arm state."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
test/fixtures/alexa-docs/alexa-securitypanelcontroller-errorresponse/BYPASS_NEEDED.error.json
vendored
Normal file
35
test/fixtures/alexa-docs/alexa-securitypanelcontroller-errorresponse/BYPASS_NEEDED.error.json
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "ErrorResponse",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token that matches the request",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"type": "BYPASS_NEEDED",
|
||||||
|
"message": "Unable to arm the security panel because there are open sensors.",
|
||||||
|
"endpointsNeedingBypass": [
|
||||||
|
{
|
||||||
|
"friendlyName": "side window sensor",
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"friendlyName": "front door sensor",
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"friendlyName": "water sensor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
test/fixtures/alexa-docs/alexa-securitypanelcontroller-errorresponse/UNAUTHORIZED.error.json
vendored
Normal file
18
test/fixtures/alexa-docs/alexa-securitypanelcontroller-errorresponse/UNAUTHORIZED.error.json
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "ErrorResponse",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token that matches the request",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"type": "UNAUTHORIZED",
|
||||||
|
"message": "The PIN code is not correct."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
test/fixtures/alexa-docs/alexa-securitypanelcontroller-errorresponse/UNCLEARED_ALARM.error.json
vendored
Normal file
22
test/fixtures/alexa-docs/alexa-securitypanelcontroller-errorresponse/UNCLEARED_ALARM.error.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "ErrorResponse",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token that matches the request",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"type": "UNCLEARED_ALARM",
|
||||||
|
"message": "Unable to arm or disarm the security panel because it is in alarm status."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Arm-alarm.response.json
vendored
Normal file
50
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Arm-alarm.response.json
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "Arm.Response",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token that matches the request",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"exitDelayInSeconds": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "burglaryAlarm",
|
||||||
|
"value": {
|
||||||
|
"value": "ALARM"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "armState",
|
||||||
|
"value": "ARMED_AWAY",
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.EndpointHealth",
|
||||||
|
"name": "connectivity",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T09:55:05.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
23
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Arm-bypass.directive.json
vendored
Normal file
23
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Arm-bypass.directive.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"directive": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "Arm",
|
||||||
|
"messageId": "Unique version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID",
|
||||||
|
"cookie": {}
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"armState": "ARMED_AWAY",
|
||||||
|
"bypassType": "BYPASS_ALL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Arm-bypass.response.json
vendored
Normal file
54
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Arm-bypass.response.json
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "Arm.Response",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token that matches the request",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"exitDelayInSeconds": 60,
|
||||||
|
"bypassedEndpoints": [
|
||||||
|
{
|
||||||
|
"friendlyName": "side window sensor",
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"friendlyName": "front door sensor",
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"friendlyName": "water sensor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "armState",
|
||||||
|
"value": "ARMED_AWAY",
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.EndpointHealth",
|
||||||
|
"name": "connectivity",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T09:55:05.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
22
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Arm.directive.json
vendored
Normal file
22
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Arm.directive.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"directive": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "Arm",
|
||||||
|
"messageId": "Unique version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID",
|
||||||
|
"cookie": {}
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"armState": "ARMED_AWAY"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Arm.response.json
vendored
Normal file
41
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Arm.response.json
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "Arm.Response",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token that matches the request",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"exitDelayInSeconds": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "armState",
|
||||||
|
"value": "ARMED_AWAY",
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.EndpointHealth",
|
||||||
|
"name": "connectivity",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T09:55:05.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
64
test/fixtures/alexa-docs/alexa-securitypanelcontroller/ChangeReport-alarm.json
vendored
Normal file
64
test/fixtures/alexa-docs/alexa-securitypanelcontroller/ChangeReport-alarm.json
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa",
|
||||||
|
"name": "ChangeReport",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"change": {
|
||||||
|
"cause": {
|
||||||
|
"type": "RULE_TRIGGER"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "burglaryAlarm",
|
||||||
|
"value": {
|
||||||
|
"value": "ALARM"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "armState",
|
||||||
|
"value": "ARMED_AWAY",
|
||||||
|
"timeOfSample": "2024-07-03T08:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "fireAlarm",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T08:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.EndpointHealth",
|
||||||
|
"name": "connectivity",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T09:55:05.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
64
test/fixtures/alexa-docs/alexa-securitypanelcontroller/ChangeReport.json
vendored
Normal file
64
test/fixtures/alexa-docs/alexa-securitypanelcontroller/ChangeReport.json
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa",
|
||||||
|
"name": "ChangeReport",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"change": {
|
||||||
|
"cause": {
|
||||||
|
"type": "PHYSICAL_INTERACTION"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "armState",
|
||||||
|
"value": "DISARMED",
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "burglaryAlarm",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T09:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "fireAlarm",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T09:20:50.51Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.EndpointHealth",
|
||||||
|
"name": "connectivity",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T10:19:00.00Z",
|
||||||
|
"uncertaintyInMilliseconds": 600000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
48
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Disarm-alarm.response.json
vendored
Normal file
48
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Disarm-alarm.response.json
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa",
|
||||||
|
"name": "Response",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token that matches the request",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "burglaryAlarm",
|
||||||
|
"value": {
|
||||||
|
"value": "ALARM"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "armState",
|
||||||
|
"value": "ARMED_AWAY",
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.EndpointHealth",
|
||||||
|
"name": "connectivity",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T09:55:05.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
25
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Disarm.directive.json
vendored
Normal file
25
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Disarm.directive.json
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"directive": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "Disarm",
|
||||||
|
"messageId": "Unique version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID",
|
||||||
|
"cookie": {}
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"authorization": {
|
||||||
|
"type": "FOUR_DIGIT_PIN",
|
||||||
|
"value": "1234"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Disarm.response.json
vendored
Normal file
39
test/fixtures/alexa-docs/alexa-securitypanelcontroller/Disarm.response.json
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa",
|
||||||
|
"name": "Response",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token that matches the request",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "armState",
|
||||||
|
"value": "DISARMED",
|
||||||
|
"timeOfSample": "2024-07-03T11:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.EndpointHealth",
|
||||||
|
"name": "connectivity",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T09:55:05.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
57
test/fixtures/alexa-docs/alexa-securitypanelcontroller/StateReport.json
vendored
Normal file
57
test/fixtures/alexa-docs/alexa-securitypanelcontroller/StateReport.json
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa",
|
||||||
|
"name": "StateReport",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID",
|
||||||
|
"correlationToken": "Opaque correlation token that matches the request",
|
||||||
|
"payloadVersion": "3"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"scope": {
|
||||||
|
"type": "BearerToken",
|
||||||
|
"token": "OAuth2.0 bearer token"
|
||||||
|
},
|
||||||
|
"endpointId": "Endpoint ID"
|
||||||
|
},
|
||||||
|
"payload": {}
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "armState",
|
||||||
|
"value": "ARMED_AWAY",
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "burglaryAlarm",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.SecurityPanelController",
|
||||||
|
"name": "fireAlarm",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T10:20:50.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "Alexa.EndpointHealth",
|
||||||
|
"name": "connectivity",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
},
|
||||||
|
"timeOfSample": "2024-07-03T09:55:05.52Z",
|
||||||
|
"uncertaintyInMilliseconds": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
6
test/fixtures/alexa-docs/alexa-securitypanelcontroller/burglaryAlarm.property.json
vendored
Normal file
6
test/fixtures/alexa-docs/alexa-securitypanelcontroller/burglaryAlarm.property.json
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "burglaryAlarm",
|
||||||
|
"value": {
|
||||||
|
"value": "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
83
test/fixtures/alexa-docs/alexa-securitypanelcontroller/discovery.json
vendored
Normal file
83
test/fixtures/alexa-docs/alexa-securitypanelcontroller/discovery.json
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"header": {
|
||||||
|
"namespace": "Alexa.Discovery",
|
||||||
|
"name": "Discover.Response",
|
||||||
|
"payloadVersion": "3",
|
||||||
|
"messageId": "Unique identifier, preferably a version 4 UUID"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"endpointId": "Unique ID of the endpoint",
|
||||||
|
"manufacturerName": "Manufacturer of the endpoint",
|
||||||
|
"description": "Description to be shown in the Alexa app",
|
||||||
|
"friendlyName": "Your security panel name, displayed in the Alexa app",
|
||||||
|
"displayCategories": [
|
||||||
|
"SECURITY_PANEL"
|
||||||
|
],
|
||||||
|
"cookie": {},
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"type": "AlexaInterface",
|
||||||
|
"interface": "Alexa.SecurityPanelController",
|
||||||
|
"version": "3",
|
||||||
|
"properties": {
|
||||||
|
"supported": [
|
||||||
|
{
|
||||||
|
"name": "armState"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "burglaryAlarm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fireAlarm"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"proactivelyReported": true,
|
||||||
|
"retrievable": true
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"supportedArmStates": [
|
||||||
|
{
|
||||||
|
"value": "ARMED_AWAY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "ARMED_STAY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "DISARMED"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"supportedAuthorizationTypes": [
|
||||||
|
{
|
||||||
|
"type": "FOUR_DIGIT_PIN"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "AlexaInterface",
|
||||||
|
"interface": "Alexa.EndpointHealth",
|
||||||
|
"version": "3",
|
||||||
|
"properties": {
|
||||||
|
"supported": [
|
||||||
|
{
|
||||||
|
"name": "connectivity"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"proactivelyReported": true,
|
||||||
|
"retrievable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "AlexaInterface",
|
||||||
|
"interface": "Alexa",
|
||||||
|
"version": "3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
test/fixtures/interfaces.js
vendored
23
test/fixtures/interfaces.js
vendored
|
|
@ -15,6 +15,8 @@
|
||||||
// chosen true: a declaration lists the properties the device has, an example some of those of the interface
|
// chosen true: a declaration lists the properties the device has, an example some of those of the interface
|
||||||
// folded the directives whose example the page folds away, so that it was not saved
|
// folded the directives whose example the page folds away, so that it was not saved
|
||||||
//
|
//
|
||||||
|
// A directive is answered with a Response, or with the event responseFor() of its descriptor names.
|
||||||
|
//
|
||||||
// test/registry/descriptors.test.js, test/device/discovery.test.js and test/dispatch/interfaces.test.js run the
|
// test/registry/descriptors.test.js, test/device/discovery.test.js and test/dispatch/interfaces.test.js run the
|
||||||
// entries. The first one fails for a descriptor of tier 1 or 2 that has no entry.
|
// entries. The first one fails for a descriptor of tier 1 or 2 that has no entry.
|
||||||
const { asset, semantics, text } = require("alex2node");
|
const { asset, semantics, text } = require("alex2node");
|
||||||
|
|
@ -109,6 +111,27 @@ module.exports = [
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
namespace: "Alexa.SecurityPanelController",
|
||||||
|
page: "alexa-securitypanelcontroller",
|
||||||
|
chosen: true,
|
||||||
|
declared: [{
|
||||||
|
example: "discovery",
|
||||||
|
options: {
|
||||||
|
...reported,
|
||||||
|
supportedArmStates: ["ARMED_AWAY", "ARMED_STAY", "DISARMED"],
|
||||||
|
supportedAuthorizationTypes: ["FOUR_DIGIT_PIN"],
|
||||||
|
alarms: ["burglaryAlarm", "fireAlarm"],
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
refused: [
|
||||||
|
["Arm", { armState: "ARMED" }, 'payload.armState: expected ARMED_AWAY | ARMED_STAY | ARMED_NIGHT | DISARMED, got "ARMED"'],
|
||||||
|
["Arm", { armState: "ARMED_STAY", bypassType: "BYPASS_SOME" }, 'payload.bypassType: expected BYPASS_ALL, got "BYPASS_SOME"'],
|
||||||
|
["Disarm", { authorization: { type: "FOUR_DIGIT_PIN", value: "12345" } }, 'payload.authorization.value: expected a PIN of four digits, got "12345"'],
|
||||||
|
["Disarm", { authorization: { type: "VOICE", value: "1234" } }, 'payload.authorization.type: expected FOUR_DIGIT_PIN, got "VOICE"'],
|
||||||
|
],
|
||||||
|
unfit: [["armState", "ARMED"], ["burglaryAlarm", "ALARM"], ["fireAlarm", { value: "FIRE" }]],
|
||||||
|
},
|
||||||
{ namespace: "Alexa.TemperatureSensor", page: "alexa-temperaturesensor", declared: [{ options: reported, example: "discovery" }] },
|
{ namespace: "Alexa.TemperatureSensor", page: "alexa-temperaturesensor", declared: [{ options: reported, example: "discovery" }] },
|
||||||
{
|
{
|
||||||
namespace: "Alexa.ThermostatController",
|
namespace: "Alexa.ThermostatController",
|
||||||
|
|
|
||||||
|
|
@ -48,14 +48,15 @@ for (const { namespace, page, declared, unfit = [], chosen = false, folded = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const directives = examples(page, ".directive").map((name) => doc(page, name).directive);
|
// A page also shows the directives of the interface it is paired with: TurnOn on the page of WakeOnLANController
|
||||||
|
const directives = examples(page, ".directive").map((name) => doc(page, name).directive)
|
||||||
|
.filter((directive) => directive.header.namespace === namespace);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
[...new Set(directives.map((directive) => directive.header.name))].sort(),
|
[...new Set(directives.map((directive) => directive.header.name))].sort(),
|
||||||
Object.keys(descriptor.directives).filter((name) => name !== "ReportState" && !folded.includes(name)).sort(),
|
Object.keys(descriptor.directives).filter((name) => name !== "ReportState" && !folded.includes(name)).sort(),
|
||||||
"the page has an example of every directive"
|
"the page has an example of every directive"
|
||||||
);
|
);
|
||||||
for (const { header, payload } of directives) {
|
for (const { header, payload } of directives) {
|
||||||
assert.equal(header.namespace, namespace);
|
|
||||||
assert.deepEqual(descriptor.directives[header.name].payload.parse(payload, "payload"), payload);
|
assert.deepEqual(descriptor.directives[header.name].payload.parse(payload, "payload"), payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,8 +136,10 @@ test("every AlexaInterfaceType is in the registry: described, or a stub with the
|
||||||
});
|
});
|
||||||
|
|
||||||
test("AlexaInterface.getVersion() and getProps() read the registry", () => {
|
test("AlexaInterface.getVersion() and getProps() read the registry", () => {
|
||||||
// What a thermostat lists is for its declaration to say: a 1.x declaration lists what 1.5.2 listed
|
// What a thermostat or a security panel lists is for its declaration to say: a 1.x declaration of the thermostat
|
||||||
|
// lists what 1.5.2 listed, one of the panel the arm state and no alarm
|
||||||
const chosen = {
|
const chosen = {
|
||||||
|
"Alexa.SecurityPanelController": ["armState"],
|
||||||
"Alexa.ThermostatController": ["targetSetpoint", "lowerSetpoint", "upperSetpoint", "thermostatMode"],
|
"Alexa.ThermostatController": ["targetSetpoint", "lowerSetpoint", "upperSetpoint", "thermostatMode"],
|
||||||
"Alexa.ThermostatController.Schedule": ["scheduleEnabled"],
|
"Alexa.ThermostatController.Schedule": ["scheduleEnabled"],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
51
test/registry/security-panel.test.js
Normal file
51
test/registry/security-panel.test.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
"use strict";
|
||||||
|
// Alexa.SecurityPanelController: what the examples of its page do not show. The examples themselves are run by
|
||||||
|
// descriptors.test.js, device/discovery.test.js and dispatch/interfaces.test.js.
|
||||||
|
const { test } = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const { AlexaInterfaceType, DeclarationError, SecurityPanelController } = require("alex2node");
|
||||||
|
const { endpoint } = require("../helpers/endpoint.js");
|
||||||
|
|
||||||
|
const panel = () => endpoint("panel-1", "Panel", ["SECURITY_PANEL"]);
|
||||||
|
|
||||||
|
test("a panel declared without options lists the arm state and no configuration, the 1.x way or with add()", () => {
|
||||||
|
const announced = {
|
||||||
|
type: "AlexaInterface",
|
||||||
|
interface: "Alexa.SecurityPanelController",
|
||||||
|
version: "3",
|
||||||
|
properties: { supported: [{ name: "armState" }], proactivelyReported: false, retrievable: true },
|
||||||
|
};
|
||||||
|
assert.deepEqual(panel().addCapability(AlexaInterfaceType.SECURITY_PANEL_CONTROLLER).getJSON(), announced);
|
||||||
|
const device = panel();
|
||||||
|
assert.deepEqual(device.add(SecurityPanelController).toJSON(), announced);
|
||||||
|
assert.deepEqual(device.check(), []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the alarms and the arm states of a declaration are the ones of the page, each listed once", () => {
|
||||||
|
const { properties, configuration } = panel().add(SecurityPanelController, {
|
||||||
|
supportedArmStates: ["ARMED_NIGHT", "DISARMED"],
|
||||||
|
alarms: ["waterAlarm", "carbonMonoxideAlarm"],
|
||||||
|
}).toJSON();
|
||||||
|
assert.deepEqual(properties.supported, [{ name: "armState" }, { name: "waterAlarm" }, { name: "carbonMonoxideAlarm" }]);
|
||||||
|
assert.deepEqual(configuration, { supportedArmStates: [{ value: "ARMED_NIGHT" }, { value: "DISARMED" }] });
|
||||||
|
|
||||||
|
const refused = (options, message) => assert.throws(
|
||||||
|
() => panel().add(SecurityPanelController, options),
|
||||||
|
(err) => err instanceof DeclarationError && err.message === `panel-1: Alexa.SecurityPanelController: ${message}`
|
||||||
|
);
|
||||||
|
refused({ alarms: ["smokeAlarm"] }, 'alarms[0]: expected burglaryAlarm | fireAlarm | carbonMonoxideAlarm | waterAlarm, got "smokeAlarm"');
|
||||||
|
refused({ alarms: ["fireAlarm", "fireAlarm"] }, "fireAlarm is listed twice");
|
||||||
|
refused({ supportedArmStates: [] }, "supportedArmStates: expected a list with 1 or more entries, got []");
|
||||||
|
refused({ supportedAuthorizationTypes: ["SIX_DIGIT_PIN"] }, 'supportedAuthorizationTypes[0]: expected FOUR_DIGIT_PIN, got "SIX_DIGIT_PIN"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Arm.Response: an exit delay of 0 to 255 seconds, and the sensors that were bypassed", () => {
|
||||||
|
const { namespace, name, payload } = SecurityPanelController.responseFor("Arm");
|
||||||
|
assert.deepEqual([namespace, name], ["Alexa.SecurityPanelController", "Arm.Response"]);
|
||||||
|
for (const fields of [{}, { exitDelayInSeconds: 0 }, { exitDelayInSeconds: 255, bypassedEndpoints: [{ friendlyName: "water sensor" }] }]) {
|
||||||
|
assert.deepEqual(payload.parse(fields, "payload"), fields);
|
||||||
|
}
|
||||||
|
assert.throws(() => payload.parse({ exitDelayInSeconds: 1.5 }, "payload"), /^SchemaError: payload\.exitDelayInSeconds: expected an integer from 0 to 255, got 1\.5$/);
|
||||||
|
assert.throws(() => payload.parse({ bypassedEndpoints: ["side window"] }, "payload"), /^SchemaError: payload\.bypassedEndpoints\[0\]: expected an object/);
|
||||||
|
assert.equal(SecurityPanelController.responseFor("Disarm"), undefined);
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue