diff --git a/src/accessories/virtualAccessory.ts b/src/accessories/virtualAccessory.ts index 02d3f58..20f4dd9 100644 --- a/src/accessories/virtualAccessory.ts +++ b/src/accessories/virtualAccessory.ts @@ -2,7 +2,7 @@ import type { PlatformAccessory, Service } from 'homebridge'; import { VirtualAccessoryPlatform } from '../platform.js'; import { VirtualSensor } from '../sensors/virtualSensor.js'; -import { Timer } from '../timer.js'; +import { ResetTimer } from '../resetTimer.js'; import fs from 'fs'; import { AccessoryConfiguration } from '../configuration/configurationAccessory.js'; @@ -25,7 +25,7 @@ export abstract class Accessory { protected storagePath: string; - protected timer?: Timer; + protected resetTimer?: ResetTimer; protected companionSensor?: VirtualSensor; constructor( diff --git a/src/accessories/virtualAccessoryDoorbell.ts b/src/accessories/virtualAccessoryDoorbell.ts index d3eec40..8dd4a2b 100644 --- a/src/accessories/virtualAccessoryDoorbell.ts +++ b/src/accessories/virtualAccessoryDoorbell.ts @@ -90,7 +90,7 @@ export class Doorbell extends Accessory { // implement your own code to check if the device is on const pressEvent = Doorbell.SINGLE_PRESS; - this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Programmable Switch Event: ${Doorbell.getEventName(pressEvent)}`); + this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Programmable Switch Event: ${this.getEventName(pressEvent)}`); // if you need to return an error to show the device as "Not Responding" in the Home app: // throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); @@ -104,7 +104,7 @@ export class Doorbell extends Accessory { // implement your own code to turn your device on/off this.states.Volume = value as number; - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Volume to ${this.states.Volume}`); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Volume: ${this.states.Volume}`); } /** @@ -170,17 +170,17 @@ export class Doorbell extends Accessory { this.service!.updateCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent, (event)); - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Triggered doorbell event: ${Doorbell.getEventName(event)}`); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Triggered Doorbell Event: ${this.getEventName(event)}`); } - static getEventName(event: number): string { + private getEventName(event: number): string { let eventName: string; switch (event) { case undefined: { eventName = 'undefined'; break; } - case Doorbell.SINGLE_PRESS: { eventName = 'SINGLE_PRESS'; break; } - case Doorbell.DOUBLE_PRESS: { eventName = 'DOUBLE_PRESS'; break; } - case Doorbell.LONG_PRESS: { eventName = 'LONG_PRESS'; break; } + case Doorbell.SINGLE_PRESS: { eventName = 'SINGLE PRESS'; break; } + case Doorbell.DOUBLE_PRESS: { eventName = 'DOUBLE PRESS'; break; } + case Doorbell.LONG_PRESS: { eventName = 'LONG PRESS'; break; } default: { eventName = event.toString(); } } diff --git a/src/accessories/virtualAccessoryGarageDoor.ts b/src/accessories/virtualAccessoryGarageDoor.ts index 917cf8e..a30dd63 100644 --- a/src/accessories/virtualAccessoryGarageDoor.ts +++ b/src/accessories/virtualAccessoryGarageDoor.ts @@ -68,7 +68,6 @@ export class GarageDoor extends Accessory { this.service.setCharacteristic(this.platform.Characteristic.Name, this.accessoryConfiguration.accessoryName); // Update the initial state of the accessory - this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Setting Garage Door Current State: ${this.getStateName(this.states.GarageDoorCurrentState)}`); this.service.updateCharacteristic(this.platform.Characteristic.CurrentDoorState, (this.states.GarageDoorCurrentState)); this.service.updateCharacteristic(this.platform.Characteristic.TargetDoorState, (this.states.GarageDoorTargetState)); @@ -109,7 +108,7 @@ export class GarageDoor extends Accessory { // implement your own code to check if the device is on const garageDoorCurrentState = this.states.GarageDoorCurrentState; - this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Current Garage Door State: ${this.getStateName(garageDoorCurrentState)}`); + this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Current Door State: ${this.getStateName(garageDoorCurrentState)}`); // if you need to return an error to show the device as "Not Responding" in the Home app: // throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); @@ -125,12 +124,12 @@ export class GarageDoor extends Accessory { // implement your own code to turn your device on/off this.states.GarageDoorTargetState = value as number; - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Target Garage Door State: ${this.getStateName(this.states.GarageDoorTargetState)}`); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Target Door State: ${this.getStateName(this.states.GarageDoorTargetState)}`); // CurrentDoorState CLOSING/OPENING - const transitionState: number = (this.states.GarageDoorTargetState === GarageDoor.OPEN) ? GarageDoor.OPENING : GarageDoor.CLOSING; - this.service!.setCharacteristic(this.platform.Characteristic.CurrentDoorState, (transitionState)); - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Curent Garage Door State: ${this.getStateName(transitionState)}`); + this.states.GarageDoorCurrentState = (this.states.GarageDoorTargetState === GarageDoor.OPEN) ? GarageDoor.OPENING : GarageDoor.CLOSING; + this.service!.setCharacteristic(this.platform.Characteristic.CurrentDoorState, (this.states.GarageDoorCurrentState)); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Curent Door State: ${this.getStateName(this.states.GarageDoorCurrentState)}`); // CurrentDoorState CLOSED/OPEN with 3 second delay const transitionDelayMillis: number = 3 * 1000; @@ -140,7 +139,7 @@ export class GarageDoor extends Accessory { this.states.GarageDoorCurrentState = this.states.GarageDoorTargetState; this.service!.setCharacteristic(this.platform.Characteristic.CurrentDoorState, (this.states.GarageDoorCurrentState)); - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Current Garage Door State: ${this.getStateName(this.states.GarageDoorCurrentState)}`); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Current Door State: ${this.getStateName(this.states.GarageDoorCurrentState)}`); // Store device state if stateful if (this.accessoryConfiguration.accessoryIsStateful) { @@ -166,7 +165,7 @@ export class GarageDoor extends Accessory { // implement your own code to check if the device is on const garageDoorTargetState = this.states.GarageDoorTargetState; - this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Target Garage Door State: ${this.getStateName(garageDoorTargetState)}`); + this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Target Door State: ${this.getStateName(garageDoorTargetState)}`); // if you need to return an error to show the device as "Not Responding" in the Home app: // throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); diff --git a/src/accessories/virtualAccessoryLock.ts b/src/accessories/virtualAccessoryLock.ts index 2a3d01b..0d3e974 100644 --- a/src/accessories/virtualAccessoryLock.ts +++ b/src/accessories/virtualAccessoryLock.ts @@ -161,7 +161,7 @@ export class Lock extends Accessory { async handleConfigurationStateGet(): Promise { const configurationState = 0; - this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting NFC Configuration State: ${configurationState}`); + this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Configuration State: ${configurationState}`); return configurationState; } @@ -169,7 +169,7 @@ export class Lock extends Accessory { async handleNFCAccessControlPointSet(value: CharacteristicValue) { const nfcAccessControlPoint = value; - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting NFC Access Control Point to ${nfcAccessControlPoint}`); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting NFC Access Control Point: ${nfcAccessControlPoint}`); } async handleNFCAccessSupportedConfigurationGet(): Promise { diff --git a/src/accessories/virtualAccessorySwitch.ts b/src/accessories/virtualAccessorySwitch.ts index efb9e6e..0b2fe11 100644 --- a/src/accessories/virtualAccessorySwitch.ts +++ b/src/accessories/virtualAccessorySwitch.ts @@ -3,7 +3,7 @@ import type { CharacteristicValue, PlatformAccessory } from 'homebridge'; import { VirtualAccessoryPlatform } from '../platform.js'; import { Accessory } from './virtualAccessory.js'; import { AccessoryFactory } from '../accessoryFactory.js'; -import { Timer } from '../timer.js'; +import { ResetTimer } from '../resetTimer.js'; import { NotCompanionError } from '../errors.js'; /** @@ -62,7 +62,7 @@ export class Switch extends Accessory { this.states.SensorState = this.CLOSED_NORMAL; if (this.accessoryConfiguration.accessoryHasResetTimer) { - this.timer = new Timer(this, this.defaultState, this.platform.Characteristic.On); + this.resetTimer = new ResetTimer(this, this.defaultState, this.platform.Characteristic.On); } } } @@ -133,7 +133,7 @@ export class Switch extends Accessory { this.states.SwitchState = value as boolean; if (this.accessoryConfiguration.accessoryHasResetTimer && this.states.SwitchState !== this.defaultState) { - this.timer!.startTimer(); + this.resetTimer!.startTimer(); } if (this.accessoryConfiguration.accessoryHasCompanionSensor) { @@ -146,7 +146,7 @@ export class Switch extends Accessory { this.saveState(this.storagePath, this.stateStorageKey, this.states.SwitchState); } - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Switch Cureent State: ${Switch.getStateName(this.states.SwitchState)}`); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting State: ${Switch.getStateName(this.states.SwitchState)}`); } setCompanionSwitchState(value: boolean) { @@ -174,7 +174,7 @@ export class Switch extends Accessory { // implement your own code to check if the device is on const switchState = this.states.SwitchState; - this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Switch Current State: ${Switch.getStateName(switchState)}`); + this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting State: ${Switch.getStateName(switchState)}`); // if you need to return an error to show the device as "Not Responding" in the Home app: // throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); diff --git a/src/accessories/virtualAccessoryWindowCovering.ts b/src/accessories/virtualAccessoryWindowCovering.ts index 13667bd..0a5c4c4 100644 --- a/src/accessories/virtualAccessoryWindowCovering.ts +++ b/src/accessories/virtualAccessoryWindowCovering.ts @@ -110,7 +110,7 @@ export class WindowCovering extends Accessory { // implement your own code to check if the device is on const windowCoveringCurrentPosition = this.states.WindowCoveringCurrentPosition; - this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Current Window Covering Position: ${this.getStateName(windowCoveringCurrentPosition)}`); + this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Current Position: ${this.getStateName(windowCoveringCurrentPosition)}`); // if you need to return an error to show the device as "Not Responding" in the Home app: // throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); @@ -126,12 +126,12 @@ export class WindowCovering extends Accessory { // implement your own code to turn your device on/off this.states.WindowCoveringTargetPosition = value as number; - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Target Window Covering Position: ${this.getStateName(this.states.WindowCoveringTargetPosition)}`); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Target Position: ${this.getStateName(this.states.WindowCoveringTargetPosition)}`); // PositionState DECREASING/INCREASING - this.states.WindowCoveringPositionState = (this.states.WindowCoveringCurrentPosition === WindowCovering.OPEN) ? WindowCovering.INCREASING : WindowCovering.DECREASING; + this.states.WindowCoveringPositionState = (this.states.WindowCoveringTargetPosition === WindowCovering.OPEN) ? WindowCovering.INCREASING : WindowCovering.DECREASING; this.service!.setCharacteristic(this.platform.Characteristic.PositionState, (this.states.WindowCoveringPositionState)); - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Curent Window Covering State: ${this.getPositionName(this.states.WindowCoveringPositionState)}`); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Position State: ${this.getPositionName(this.states.WindowCoveringPositionState)}`); // PositionState STOPPED // CurrentPosition OPEN/CLOSED with 3 second delay @@ -142,11 +142,11 @@ export class WindowCovering extends Accessory { this.states.WindowCoveringPositionState = WindowCovering.STOPPED; this.service!.setCharacteristic(this.platform.Characteristic.PositionState, (this.states.WindowCoveringPositionState)); - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Curent Window Covering State: ${this.getPositionName(this.states.WindowCoveringPositionState)}`); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Position State: ${this.getPositionName(this.states.WindowCoveringPositionState)}`); this.states.WindowCoveringCurrentPosition = this.states.WindowCoveringTargetPosition; this.service!.setCharacteristic(this.platform.Characteristic.CurrentPosition, (this.states.WindowCoveringCurrentPosition)); - this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Current Garage Door State: ${this.getStateName(this.states.WindowCoveringCurrentPosition)}`); + this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Current Position: ${this.getStateName(this.states.WindowCoveringCurrentPosition)}`); // Store device state if stateful if (this.accessoryConfiguration.accessoryIsStateful) { @@ -172,7 +172,7 @@ export class WindowCovering extends Accessory { // implement your own code to check if the device is on const windowCoveringTargetPosition = this.states.WindowCoveringTargetPosition; - this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Target Window Covering Position: ${this.getStateName(windowCoveringTargetPosition)}`); + this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Target Position: ${this.getStateName(windowCoveringTargetPosition)}`); // if you need to return an error to show the device as "Not Responding" in the Home app: // throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); @@ -187,7 +187,7 @@ export class WindowCovering extends Accessory { // implement your own code to check if the device is on const windowCoveringPositionState = this.states.WindowCoveringPositionState; - this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Window Covering State: ${this.getPositionName(windowCoveringPositionState)}`); + this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Position State: ${this.getPositionName(windowCoveringPositionState)}`); // if you need to return an error to show the device as "Not Responding" in the Home app: // throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); diff --git a/src/resetTimer.ts b/src/resetTimer.ts new file mode 100644 index 0000000..1b627a3 --- /dev/null +++ b/src/resetTimer.ts @@ -0,0 +1,71 @@ +import { CharacteristicValue } from 'homebridge'; +import { Accessory } from './accessories/virtualAccessory'; + +export class ResetTimer { + + static Units = { + Seconds: 'seconds', + Minutes: 'minutes', + Hours: 'hours', + Days: 'days', + }; + + private timerId: ReturnType | undefined; + + private config; + private virtualAccessory: Accessory; + private resetValue: CharacteristicValue; + private characteristic; + + private duration: number; + + constructor( + virtualAccessory: Accessory, + resetValue: CharacteristicValue, + characteristic, + ) { + this.virtualAccessory = virtualAccessory; + this.config = this.virtualAccessory.accessoryConfiguration.resetTimer; + this.resetValue = resetValue; + this.characteristic = characteristic; + + if (this.config.durationIsRandom) { + const minDuration = this.config.durationRandomMin; + const maxDuration = this.config.durationRandomMax; + this.duration = Math.floor(Math.random() * (maxDuration + 1 - minDuration) + minDuration); + } else { + this.duration = this.config.duration; + } + + const units = this.config.units; + switch (units) { + case ResetTimer.Units.Days: + this.duration *= 24; + // falls through + case ResetTimer.Units.Hours: + this.duration *= 60; + // falls through + case ResetTimer.Units.Minutes: + this.duration *= 60; + // falls through + case ResetTimer.Units.Seconds: + this.duration *= 1000; + } + } + + startTimer() { + if (this.config.isResettable) { + this.endTimer(); + } + + // Start the state reset timer + //const resetValue = (this.states.SwitchState === this.ON) ? this.OFF : this.ON; + this.timerId = setTimeout(() => { + this.virtualAccessory.service!.setCharacteristic(this.characteristic, this.resetValue); + }, this.duration); + } + + endTimer() { + clearTimeout(this.timerId); + } +} diff --git a/src/timer.ts b/src/timer.ts index 1af51d1..06e3c12 100644 --- a/src/timer.ts +++ b/src/timer.ts @@ -1,5 +1,3 @@ -import { CharacteristicValue } from 'homebridge'; -import { Accessory } from './accessories/virtualAccessory'; export class Timer { @@ -11,61 +9,94 @@ export class Timer { }; private timerId: ReturnType | undefined; + private duration: number = 0; // Timer duration in seconds - private config; - private virtualAccessory: Accessory; - private resetValue: CharacteristicValue; - private characteristic; - - private duration: number; + private startTime: number = 0; + constructor(); + constructor( + duration: number, + units: string, + ); constructor( - virtualAccessory: Accessory, - resetValue: CharacteristicValue, - characteristic, + duration?: number, + units?: string, ) { - this.virtualAccessory = virtualAccessory; - this.config = this.virtualAccessory.accessoryConfiguration.resetTimer; - this.resetValue = resetValue; - this.characteristic = characteristic; + this.setDuration(duration, units); + } - if (this.config.durationIsRandom) { - const minDuration = this.config.durationRandomMin; - const maxDuration = this.config.durationRandomMax; - this.duration = Math.floor(Math.random() * (maxDuration + 1 - minDuration) + minDuration); - } else { - this.duration = this.config.duration; - } + start( + callback: () => void, + ): void; + start( + callback: () => void, + duration: number, + units: string, + ): void; + start( + callback: () => void, + duration?: number, + units?: string, + ): void { + this.stop(); - const units = this.config.units; - switch (units) { - case Timer.Units.Days: - this.duration *= 24; - // falls through - case Timer.Units.Hours: - this.duration *= 60; - // falls through - case Timer.Units.Minutes: - this.duration *= 60; - // falls through - case Timer.Units.Seconds: - this.duration *= 1000; + this.setDuration(duration, units); + + if (this.duration > 0) { + this.startTime = (new Date()).getTime(); + + this.timerId = setTimeout(() => { + callback(); + this.timerId = undefined; + }, this.duration * 1000); } } - startTimer() { - if (this.config.isResettable) { - this.endTimer(); - } + stop(): void { + clearTimeout(this.timerId); + this.startTime = 0; + } - // Start the state reset timer - //const resetValue = (this.states.SwitchState === this.ON) ? this.OFF : this.ON; - this.timerId = setTimeout(() => { - this.virtualAccessory.service!.setCharacteristic(this.characteristic, this.resetValue); - }, this.duration); + /** + * Returns duration in seconds + */ + getDuration(): number { + return this.duration; } - endTimer() { - clearTimeout(this.timerId); + /** + * Returns remaining time in seconds + */ + getRemainingTime(): number { + const now: number = (new Date()).getTime(); + const remaining: number = ((this.startTime === 0) ? 0 : now - this.startTime) / 1000; + + return remaining; + } + + private setDuration( + duration?: number, + units?: string, + ) { + this.duration = duration ? duration : 0; + + if (this.duration > 0 && units !== undefined) { + switch (units) { + case Timer.Units.Days: + this.duration *= 24; + // falls through + case Timer.Units.Hours: + this.duration *= 60; + // falls through + case Timer.Units.Minutes: + this.duration *= 60; + // falls through + case Timer.Units.Seconds: + this.duration *= 1000; + break; + default: + this.duration = 0; + } + } } }