UNPKG

homebridge-virtual-accessories

Version:
165 lines 8.75 kB
import { Accessory } from './accessory.js'; import { Timer } from '../utils/timer.js'; /** * OpeningAccessory - Abstract accessory */ export class OpeningAccessory extends Accessory { static CLOSED = 0; // 0% static OPEN = 100; // 100% static DECREASING = 0; // Characteristic.PositionState.DECREASING -> CLOSING static INCREASING = 1; // Characteristic.PositionState.INCREASING -> OPENING static STOPPED = 2; // Characteristic.PositionState.STOPPED -> OPEN or CLOSED static MIN_TIMEOUT_SECS = 1; static DEFAULT_TIMEOUT_SECS = 3; stateStorageKey = 'Position'; transitionTimer; transitionSteps = 0; openingAccessoryConfiguration; states = { CurrentPosition: OpeningAccessory.CLOSED, // % TargetPosition: OpeningAccessory.CLOSED, // % PositionState: OpeningAccessory.STOPPED, }; constructor(platform, accessory, accessoryConfiguration) { super(platform, accessory, accessoryConfiguration); // First configure the device based on the accessory details this.openingAccessoryConfiguration = this.getOpeningAccessoryConfiguration(); this.defaultState = this.openingAccessoryConfiguration.defaultState === 'open' ? OpeningAccessory.OPEN : OpeningAccessory.CLOSED; this.states.CurrentPosition = this.defaultState; // 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.CurrentPosition = cachedState; } } this.states.TargetPosition = this.states.CurrentPosition; const timerIsResettable = true; this.transitionTimer = new Timer(this.accessoryConfiguration.accessoryName, this.log, timerIsResettable); // set accessory information const service = this.getOpeningAccessoryService(); this.service = this.accessory.getService(service) || this.accessory.addService(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 Window Covering Current Position: ${OpeningAccessory.getStateName(this.states.CurrentPosition)}`); this.service.updateCharacteristic(this.platform.Characteristic.CurrentPosition, (this.states.CurrentPosition)); this.service.updateCharacteristic(this.platform.Characteristic.TargetPosition, (this.states.TargetPosition)); this.service.updateCharacteristic(this.platform.Characteristic.PositionState, (this.states.PositionState)); // register handlers this.service.getCharacteristic(this.platform.Characteristic.CurrentPosition) .onGet(this.getCurrentPosition.bind(this)); this.service.getCharacteristic(this.platform.Characteristic.TargetPosition) .onSet(this.setTargetPosition.bind(this)) .onGet(this.getTargetPosition.bind(this)); this.service.getCharacteristic(this.platform.Characteristic.PositionState) .onGet(this.getPositionState.bind(this)); } // Handlers async getCurrentPosition() { // If timer is running, then blinds are moving, so calculate the interim position if (this.transitionTimer.isTimerRunning()) { const runtimeMillis = this.transitionTimer.getRuntime() * 1000; const remainingSteps = Math.ceil(this.transitionTimer.getRemainingDurationMillis() / runtimeMillis * this.transitionSteps); this.states.CurrentPosition = this.states.TargetPosition - remainingSteps; } const currentPosition = this.states.CurrentPosition; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Current Position: ${OpeningAccessory.getStateName(currentPosition)}`); return currentPosition; } async setTargetPosition(value) { this.states.TargetPosition = value; this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Target Position: ${OpeningAccessory.getStateName(this.states.TargetPosition)}`); this.states.PositionState = (this.states.TargetPosition > this.states.CurrentPosition) ? OpeningAccessory.INCREASING : OpeningAccessory.DECREASING; this.service.setCharacteristic(this.platform.Characteristic.PositionState, (this.states.PositionState)); this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Position State: ${OpeningAccessory.getPositionName(this.states.PositionState)}`); const transitionDuration = this.openingAccessoryConfiguration.transitionDuration; const transitionDelay = (transitionDuration ? transitionDuration : OpeningAccessory.DEFAULT_TIMEOUT_SECS); this.transitionSteps = this.states.TargetPosition - this.states.CurrentPosition; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Transition Steps: ${this.transitionSteps}`); const proportionalTransitionDelay = Math.max( // Round up to the nearest second Math.ceil(transitionDelay / 100 * Math.abs(this.transitionSteps)), OpeningAccessory.MIN_TIMEOUT_SECS); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Proportional Delay: ${proportionalTransitionDelay}/(${transitionDelay})`); const updateIntervalMillis = 100; // Stop transition timer, if running this.transitionTimer.stop(); this.transitionTimer.start(() => { this.states.PositionState = OpeningAccessory.STOPPED; this.service.setCharacteristic(this.platform.Characteristic.PositionState, (this.states.PositionState)); this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Position State: ${OpeningAccessory.getPositionName(this.states.PositionState)}`); this.states.CurrentPosition = this.states.TargetPosition; this.service.setCharacteristic(this.platform.Characteristic.CurrentPosition, (this.states.CurrentPosition)); this.transitionSteps = 0; this.storeState(); this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Current Position: ${OpeningAccessory.getStateName(this.states.CurrentPosition)}`); }, proportionalTransitionDelay, updateIntervalMillis); } async getTargetPosition() { const targetPosition = this.states.TargetPosition; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Target Position: ${OpeningAccessory.getStateName(targetPosition)}`); return targetPosition; } async getPositionState() { const positionState = this.states.PositionState; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Position State: ${OpeningAccessory.getPositionName(positionState)}`); return positionState; } getJsonState() { const json = JSON.stringify({ [this.stateStorageKey]: this.states.CurrentPosition, }); return json; } static getStateName(position) { let positionName; switch (position) { case undefined: { positionName = 'undefined'; break; } case OpeningAccessory.CLOSED: { positionName = 'CLOSED'; break; } case OpeningAccessory.OPEN: { positionName = 'OPEN'; break; } default: { positionName = `POSITION: ${position.toString()}%`; } } if (position > OpeningAccessory.OPEN) { positionName = `INVALID ${positionName}%`; } return positionName; } static getPositionName(state) { let stateName; switch (state) { case undefined: { stateName = 'undefined'; break; } case OpeningAccessory.DECREASING: { stateName = 'DECREASING'; break; } case OpeningAccessory.INCREASING: { stateName = 'INCREASING'; break; } case OpeningAccessory.STOPPED: { stateName = 'STOPPED'; break; } default: { stateName = state.toString(); } } return stateName; } } //# sourceMappingURL=openingAccessory.js.map