UNPKG

homebridge-virtual-accessories

Version:
262 lines 13.6 kB
/* eslint-disable brace-style */ import { Accessory } from './accessory.js'; import { InvalidSensorValueType, SensorValueUpdateNotAllowed } from '../errors.js'; import { SecuritySystemArmedMode, SecuritySystemState } from '../configuration/schema.js'; import { Timer } from '../utils/timer.js'; /** * SecuritySystem - Accessory implementation */ export class SecuritySystem extends Accessory { static ACCESSORY_TYPE_NAME = 'SecuritySystem'; static STAY_ARM = 0; // Characteristic.SecuritySystemCurrentState.STAY_ARM static AWAY_ARM = 1; // Characteristic.SecuritySystemCurrentState.AWAY_ARM static NIGHT_ARM = 2; // Characteristic.SecuritySystemCurrentState.NIGHT_ARM static DISARMED = 3; // Characteristic.SecuritySystemCurrentState.DISARMED static ALARM_TRIGGERED = 4; // Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED stateStorageKey = 'SecuritySystemState'; armingDelayTimer; states = { SecuritySystemCurrentState: SecuritySystem.DISARMED, SecuritySystemTargetState: SecuritySystem.DISARMED, }; constructor(platform, accessory, accessoryConfiguration) { super(platform, accessory, accessoryConfiguration); // First configure the device based on the accessory details switch (this.accessoryConfiguration.securitySystem.defaultState) { case SecuritySystemState.ArmedStay: this.defaultState = SecuritySystem.STAY_ARM; break; case SecuritySystemState.ArmedAway: this.defaultState = SecuritySystem.AWAY_ARM; break; case SecuritySystemState.ArmedNight: this.defaultState = SecuritySystem.NIGHT_ARM; break; case SecuritySystemState.Disarmed: this.defaultState = SecuritySystem.DISARMED; break; case SecuritySystemState.AlarmTriggered: this.defaultState = SecuritySystem.ALARM_TRIGGERED; break; default: this.defaultState = SecuritySystem.DISARMED; } this.states.SecuritySystemCurrentState = this.defaultState; // Timer is not resettable const timerIsResettable = false; this.armingDelayTimer = new Timer(this.accessoryConfiguration.accessoryName, this.log, timerIsResettable); // If the accessory is stateful retrieve stored state if (this.accessoryConfiguration.accessoryIsStateful) { const accessoryState = this.loadAccessoryState(this.storagePath); const cachedState = accessoryState[this.stateStorageKey]; if (cachedState !== undefined) { this.states.SecuritySystemCurrentState = cachedState; } } this.states.SecuritySystemTargetState = this.states.SecuritySystemCurrentState; this.service = this.accessory.getService(this.platform.Service.SecuritySystem) || this.accessory.addService(this.platform.Service.SecuritySystem); this.setSecurityServiceProperties(this.service); this.service.setCharacteristic(this.platform.Characteristic.Name, this.accessoryConfiguration.accessoryName); // Update the initial state of the accessory // eslint-disable-next-line max-len this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Setting Security System Current State: ${SecuritySystem.getStateName(this.states.SecuritySystemCurrentState)}`); this.service.updateCharacteristic(this.platform.Characteristic.SecuritySystemCurrentState, (this.states.SecuritySystemCurrentState)); this.service.updateCharacteristic(this.platform.Characteristic.SecuritySystemTargetState, (this.states.SecuritySystemTargetState)); // register handlers this.service.getCharacteristic(this.platform.Characteristic.SecuritySystemCurrentState) .onGet(this.getSecuritySystemCurrentState.bind(this)); this.service.getCharacteristic(this.platform.Characteristic.SecuritySystemTargetState) .onSet(this.setSecuritySystemTargetState.bind(this)) .onGet(this.getSecuritySystemTargetState.bind(this)); } // Handlers async getSecuritySystemCurrentState() { const securitySystemState = this.states.SecuritySystemCurrentState; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Current State: ${SecuritySystem.getStateName(securitySystemState)}`); return securitySystemState; } async setSecuritySystemTargetState(value) { this.states.SecuritySystemTargetState = value; this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Target State: ${SecuritySystem.getStateName(this.states.SecuritySystemTargetState)}`); // No delay when disarming or switching betweem armed modes const delayTime = (this.states.SecuritySystemTargetState === SecuritySystem.DISARMED || (this.states.SecuritySystemTargetState !== SecuritySystem.DISARMED && this.states.SecuritySystemCurrentState !== SecuritySystem.DISARMED)) ? 0 : this.accessoryConfiguration.securitySystem.armingDelay; this.armingDelayTimer.start(() => { this.states.SecuritySystemCurrentState = this.states.SecuritySystemTargetState; this.service.setCharacteristic(this.platform.Characteristic.SecuritySystemCurrentState, (this.states.SecuritySystemCurrentState)); // eslint-disable-next-line max-len this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Current State: ${SecuritySystem.getStateName(this.states.SecuritySystemCurrentState)}`); this.storeState(); }, delayTime); } async getSecuritySystemTargetState() { const securitySystemState = this.states.SecuritySystemTargetState; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Target State: ${SecuritySystem.getStateName(securitySystemState)}`); return securitySystemState; } getJsonState() { const json = JSON.stringify({ [this.stateStorageKey]: this.states.SecuritySystemCurrentState, }); return json; } getAccessoryTypeName() { return SecuritySystem.ACCESSORY_TYPE_NAME; } static getStateName(state) { let stateName; switch (state) { case undefined: { stateName = 'undefined'; break; } case SecuritySystem.STAY_ARM: { stateName = 'STAY_ARM'; break; } case SecuritySystem.AWAY_ARM: { stateName = 'AWAY_ARM'; break; } case SecuritySystem.NIGHT_ARM: { stateName = 'NIGHT_ARM'; break; } case SecuritySystem.DISARMED: { stateName = 'DISARMED'; break; } case SecuritySystem.ALARM_TRIGGERED: { stateName = 'ALARM_TRIGGERED'; break; } default: { stateName = state.toString(); } } return stateName; } /** * Ensure all the property values are set, then remove as required */ setSecurityServiceProperties(service) { const SecuritySystemCurrentState = this.platform.Characteristic.SecuritySystemCurrentState; const SecuritySystemTargetState = this.platform.Characteristic.SecuritySystemTargetState; const currentStateValues = new Set([ SecuritySystemCurrentState.STAY_ARM, SecuritySystemCurrentState.AWAY_ARM, SecuritySystemCurrentState.NIGHT_ARM, SecuritySystemCurrentState.DISARMED, SecuritySystemCurrentState.ALARM_TRIGGERED, // 5, ... 255 Reserved ]); const targetStateValues = new Set([ SecuritySystemTargetState.STAY_ARM, SecuritySystemTargetState.AWAY_ARM, SecuritySystemTargetState.NIGHT_ARM, SecuritySystemTargetState.DISARM, // 4, ... 255 Reserved ]); const armedModes = this.accessoryConfiguration.securitySystem.armedModes; if (!armedModes.includes(SecuritySystemArmedMode.ArmedAway)) { currentStateValues.delete(SecuritySystemCurrentState.AWAY_ARM); targetStateValues.delete(SecuritySystemTargetState.AWAY_ARM); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Away is not in armed modes`); } if (!armedModes.includes(SecuritySystemArmedMode.ArmedNight)) { currentStateValues.delete(SecuritySystemCurrentState.NIGHT_ARM); targetStateValues.delete(SecuritySystemTargetState.NIGHT_ARM); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Night is not in armed modes`); } if (!armedModes.includes(SecuritySystemArmedMode.ArmedStay)) { currentStateValues.delete(SecuritySystemCurrentState.STAY_ARM); targetStateValues.delete(SecuritySystemTargetState.STAY_ARM); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Home is not in armed modes`); } if (currentStateValues.size > 0) { this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Setting Current State values: ${this.generatePropertyValueList(currentStateValues)}`); service.getCharacteristic(SecuritySystemCurrentState) .setProps({ validValues: Array.from(currentStateValues), }); // eslint-disable-next-line max-len this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Current State Props: ${JSON.stringify(service.getCharacteristic(SecuritySystemCurrentState).props)}`); } if (targetStateValues.size > 0) { this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Setting Target State values: ${this.generatePropertyValueList(targetStateValues)}`); service.getCharacteristic(SecuritySystemTargetState) .setProps({ validValues: Array.from(targetStateValues), }); // eslint-disable-next-line max-len this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Target State Props: ${JSON.stringify(service.getCharacteristic(SecuritySystemTargetState).props)}`); } } generatePropertyValueList(values) { const names = new Set(); values.forEach((value) => { names.add(SecuritySystem.getStateName(value)); }); return Array.from(names).join(', '); } // Triggerable Alarm interface triggerAlarm(value, accessoryId) { this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Request update triggered state to ${SecurityServiceTriggerType.getName(value)}`); if (accessoryId !== this.accessoryConfiguration.accessoryID) { this.log.error(`[${this.accessoryConfiguration.accessoryName}] Accessory Id ${accessoryId} is not valid for this accessory`); throw new SensorValueUpdateNotAllowed(`Invalid accessory id: ${accessoryId}`); } else if (typeof value !== 'number' || !SecurityServiceTriggerType.isValid(value)) { this.log.error(`[${this.accessoryConfiguration.accessoryName}] Value ${value} is not valid for a Security System triggered state`); throw new InvalidSensorValueType(`Invalid sensor value: ${value}`); } if (value === SecurityServiceTriggerType.TriggerPanic || (value === SecurityServiceTriggerType.TriggerAlarm && this.states.SecuritySystemCurrentState !== SecuritySystem.DISARMED)) { this.states.SecuritySystemCurrentState = SecuritySystem.ALARM_TRIGGERED; this.service.setCharacteristic(this.platform.Characteristic.SecuritySystemCurrentState, (this.states.SecuritySystemCurrentState)); this.log.info(`[${this.accessoryConfiguration.accessoryName}] Updating triggered state to ${SecurityServiceTriggerType.getName(value)}`); } else { this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Current state: ${SecuritySystem.getStateName(this.states.SecuritySystemCurrentState)}`); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Not updating triggered state to ${SecurityServiceTriggerType.getName(value)}`); } } } export class SecurityServiceTriggerType { static None = 0; static TriggerAlarm = 1; static TriggerPanic = 2; static isValid(value) { return (value === SecurityServiceTriggerType.None || value === SecurityServiceTriggerType.TriggerAlarm || value === SecurityServiceTriggerType.TriggerPanic); } static getName(state) { let name; switch (state) { case undefined: { name = 'undefined'; break; } case SecurityServiceTriggerType.None: { name = 'NONE'; break; } case SecurityServiceTriggerType.TriggerAlarm: { name = 'TRIGGER ALARM'; break; } case SecurityServiceTriggerType.TriggerPanic: { name = 'TRIGGER PANIC'; break; } default: { name = state.toString(); } } return name; } } //# sourceMappingURL=virtualAccessorySecuritySystem.js.map