homebridge-virtual-accessories
Version:
Virtual HomeKit accessories for Homebridge.
191 lines • 9.99 kB
JavaScript
import { Accessory } from './accessory.js';
import { CompanionSensor } from '../sensors/companions/companionSensors.js';
import { BinarySensor } from '../sensors/binarySensor.js';
import { Timer } from '../utils/timer.js';
import { Utils } from '../utils/utils.js';
import { Duration } from '@js-joda/core';
/**
* Switch - Accessory implementation
*/
export class Switch extends Accessory {
static ACCESSORY_TYPE_NAME = 'Switch';
static ON = true;
static OFF = false;
stateStorageKey = 'SwitchState';
timerStartTimeStorageKey = 'TimerStartTime';
timerDurationStorageKey = 'TimerDuration';
timerIsRunningStorageKey = 'TimerIsRunning';
resetTimer;
companionSensor;
muteLogging;
states = {
SwitchState: Switch.OFF,
SensorState: BinarySensor.NORMAL,
};
constructor(platform, accessory, accessoryConfiguration) {
super(platform, accessory, accessoryConfiguration);
// First configure the device based on the accessory details
this.defaultState = this.accessoryConfiguration.switch.defaultState === 'on' ? Switch.ON : Switch.OFF;
this.muteLogging = this.accessoryConfiguration.switch.muteLogging;
this.states.SwitchState = this.defaultState;
this.states.SensorState = BinarySensor.NORMAL;
if (this.accessoryConfiguration.switch.hasResetTimer) {
this.setupResetTimer(this.accessoryConfiguration.resetTimer);
}
// If the accessory is stateful retrieve stored state
if (this.accessoryConfiguration.accessoryIsStateful) {
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Switch is stateful`);
const accessoryState = this.loadAccessoryState(this.storagePath);
const cachedState = accessoryState[this.stateStorageKey];
if (cachedState !== undefined) {
this.states.SwitchState = cachedState;
this.states.SensorState = this.determineSensorState();
}
if (this.accessoryConfiguration.switch.hasResetTimer) {
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Switch has reset timer`);
const cachedTimerStartTime = accessoryState[this.timerStartTimeStorageKey];
const cachedTimerDuration = accessoryState[this.timerDurationStorageKey];
const cachedTimerIsRunning = accessoryState[this.timerIsRunningStorageKey];
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Cached Timer Start Time: ${cachedTimerStartTime}`);
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Cached Timer Duration: ${cachedTimerDuration}`);
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Cached Timer Is Running: ${cachedTimerIsRunning}`);
// If the timer was running, calculate elapsed time and set timer for remaining duration
if (cachedTimerIsRunning) {
this.restoreRunningTimer(cachedTimerStartTime, cachedTimerDuration);
this.storeState();
}
}
}
this.service = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch);
this.service.setCharacteristic(this.platform.Characteristic.Name, this.accessoryConfiguration.accessoryName);
// Update the initial state of the accessory
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Setting Switch Current State: ${Switch.getStateName(this.states.SwitchState)}`);
this.service.updateCharacteristic(this.platform.Characteristic.On, (this.states.SwitchState));
// register handlers
this.service.getCharacteristic(this.platform.Characteristic.On)
.onSet(this.setOn.bind(this))
.onGet(this.getOn.bind(this));
/**
* Creating multiple services of the same type.
*
* To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error,
* when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id:
* this.accessory.getService('NAME') || this.accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE_ID');
*
* The USER_DEFINED_SUBTYPE must be unique to the platform accessory (if you platform exposes multiple accessories, each accessory
* can use the same subtype id.)
*/
// Create sensor service
if (this.accessoryConfiguration.switch.hasCompanionSensor) {
this.createCompanionSensor();
}
}
// Handlers
async setOn(value) {
this.states.SwitchState = value;
if (this.accessoryConfiguration.switch.hasResetTimer) {
// switch is reset: turn off timer
if (this.states.SwitchState === this.defaultState) {
this.resetTimer.stop();
}
else {
this.resetTimer.start(this.onTimerExpired.bind(this));
}
}
this.storeState();
this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting State: ${Switch.getStateName(this.states.SwitchState)}`, this.muteLogging);
if (this.accessoryConfiguration.switch.hasCompanionSensor) {
this.states.SensorState = this.determineSensorState();
this.companionSensor.triggerCompanionSensorState(this.states.SensorState, this, this.muteLogging);
}
}
async getOn() {
const switchState = this.states.SwitchState;
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting State: ${Switch.getStateName(switchState)}`);
return switchState;
}
getJsonState() {
const jsonState = {
[this.stateStorageKey]: this.states.SwitchState,
};
if (this.accessoryConfiguration.switch.hasResetTimer) {
const timerStartTime = this.resetTimer.getStartTime().toString();
const timerDuration = (this.resetTimer.getRuntime() > 0) ? this.resetTimer.getRuntime() : this.resetTimer.getDefaultDuration();
const timerIsRunning = this.resetTimer.isTimerRunning();
Object.assign(jsonState, { [this.timerStartTimeStorageKey]: timerStartTime });
Object.assign(jsonState, { [this.timerDurationStorageKey]: timerDuration });
Object.assign(jsonState, { [this.timerIsRunningStorageKey]: timerIsRunning });
}
const json = JSON.stringify(jsonState);
return json;
}
getAccessoryTypeName() {
return Switch.ACCESSORY_TYPE_NAME;
}
static getStateName(state) {
let stateName;
switch (state) {
case undefined: {
stateName = 'undefined';
break;
}
case Switch.ON: {
stateName = 'ON';
break;
}
case Switch.OFF: {
stateName = 'OFF';
break;
}
default: {
stateName = state.toString();
}
}
return stateName;
}
determineSensorState() {
let sensorState;
if (this.defaultState === Switch.OFF) {
sensorState = (this.states.SwitchState === Switch.OFF) ? BinarySensor.NORMAL : BinarySensor.TRIGGERED;
}
else {
sensorState = (this.states.SwitchState === Switch.ON) ? BinarySensor.NORMAL : BinarySensor.TRIGGERED;
}
return sensorState;
}
// Setup stuff
setupResetTimer(timerConfig) {
const duration = timerConfig.durationIsRandom ?
Math.floor(Math.random() *
(timerConfig.durationRandomMax.toSeconds() + 1 - timerConfig.durationRandomMin.toSeconds()) +
timerConfig.durationRandomMin.toSeconds()) :
timerConfig.duration.toSeconds();
this.resetTimer = new Timer(this.accessoryConfiguration.accessoryName, this.log, this.accessoryConfiguration.resetTimer.isResettable, duration);
}
createCompanionSensor() {
this.companionSensor = CompanionSensor.getTriggerableCompanionSensor(this.platform, this.accessory, this.accessoryConfiguration);
// Set initial sensor state
this.companionSensor.triggerCompanionSensorState(this.states.SensorState, this, this.muteLogging);
}
restoreRunningTimer(cachedTimerStartTime, cachedTimerDuration) {
// eslint-disable-next-line max-len
const elapsedTimeSinceTimerStart = Math.trunc(Duration.between(Utils.zonedDateTime(cachedTimerStartTime), Utils.now()).toMillis() / 1000); // seconds
const timeDifferential = (cachedTimerDuration - elapsedTimeSinceTimerStart);
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Elapsed Time Since Timer Start: ${elapsedTimeSinceTimerStart}`);
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Time Differential: ${timeDifferential}`);
// If the timer is expired, set timer to 1 second to issue trigger switch off
const remainingTimerDuration = (timeDifferential <= 0) ? 1 : timeDifferential;
if (remainingTimerDuration === 1) {
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Timer expired. Setting timer to 1 second to trigger switch off`);
}
else {
this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Setting Timer for remaining duration (${remainingTimerDuration} seconds)`);
}
this.resetTimer.debugCountdown();
this.resetTimer.start(this.onTimerExpired.bind(this), remainingTimerDuration);
}
onTimerExpired() {
this.service.setCharacteristic(this.platform.Characteristic.On, this.defaultState);
}
}
//# sourceMappingURL=virtualAccessorySwitch.js.map