UNPKG

@vandubois/homebridge-virtual-switch

Version:

Creation of virtual switches, triggered independently and by keywords appearing in the Homebridge log file.

239 lines 11.6 kB
export class HomebridgeVirtualSwitchesAccessory { platform; accessory; service; switchState = false; timer; timerEndTime; useLogFile; constructor(platform, accessory) { this.platform = platform; this.accessory = accessory; this.useLogFile = accessory.context.device.UseLogFile; // Set up accessory information this.setupAccessoryInformation(); // Attempt to initialize the switch service this.initializeService(); // Initialize state this.initializeState(); } initializeService() { // Assign `service` outside of try-catch for safer error handling this.service = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch); try { // Only wrap code that might throw an error in try-catch this.service.setCharacteristic(this.platform.Characteristic.Name, this.accessory.context.device.Name); this.service.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setOn.bind(this)) .onGet(this.getOn.bind(this)); } catch (error) { if (error instanceof Error) { this.platform.log.error(`Failed to initialize switch characteristics: ${error.message}`); } else { this.platform.log.error('Failed to initialize switch characteristics: Unknown error occurred'); } } } setupAccessoryInformation() { const accessoryInformationService = this.accessory.getService(this.platform.Service.AccessoryInformation); if (accessoryInformationService) { accessoryInformationService .setCharacteristic(this.platform.Characteristic.Manufacturer, 'HomebridgeVirtualSwitchesPlatform') .setCharacteristic(this.platform.Characteristic.Model, 'HomebridgeVirtualSwitchAccessory-Switch') .setCharacteristic(this.platform.Characteristic.Name, this.accessory.context.device.Name) .setCharacteristic(this.platform.Characteristic.SerialNumber, `HomebridgeVirtualSwitchAccessory-${this.accessory.context.device.Name}`) .setCharacteristic(this.platform.Characteristic.FirmwareRevision, '0.0.0'); } } initializeState() { const device = this.accessory.context.device; // Check if UseCustomTime is selected with all custom time fields as 0 const hasZeroCustomTime = device.TimeDays === 0 && device.TimeHours === 0 && device.TimeMinutes === 0 && device.TimeSeconds === 0; if (!device.SwitchStayOn && device.UseCustomTime && hasZeroCustomTime) { throw new Error(`Switch "${device.Name}" cannot be initialized: "Set timer in days/hours/..." is selected and all time fields are 0 in switch config.`); } // Check if Time is 0 when UseCustomTime is not selected if (!device.SwitchStayOn && !device.UseCustomTime && device.Time === 0) { this.platform.log.info(`SwitchStayon "${device.SwitchStayOn}"`); this.platform.log.info(`UseCustomTime: "${device.UseCustomTime}"`); throw new Error(`Switch "${device.Name}" cannot be initialized:"Timer (in ms)" is 0 and "Set timer in day/hours/..." is not selected in switch config.`); } // Check for persistent timer state if (device.TimerPersistent && device.timerState) { const { targetTime, isRunning } = device.timerState; if (isRunning) { if (this.platform.hasReachedTargetTime(targetTime)) { // Timer has expired while system was off this.switchState = device.NormallyClosed; this.platform.clearTimerState(device.Name); this.platform.log.info(`Switch "${device.Name}" turned ${device.NormallyClosed ? 'on' : 'off'} as persistent timer expired during downtime.`); } else { // Resume timer and set correct state const remainingTime = targetTime - Date.now(); this.switchState = !device.NormallyClosed; // Set to triggered state this.startOffTimer({ targetTime: targetTime, duration: remainingTime, }); this.platform.log.info(`Resuming persistent timer for "${device.Name}" with ${remainingTime}ms remaining. Switch state set to ${this.switchState ? 'on' : 'off'}.`); } } // Initialize switch state } else if (device.RememberState && device.lastState !== null) { this.switchState = device.lastState; } else { this.switchState = device.NormallyClosed; } this.updateHomeKitState(); const homeKitState = this.switchState; this.platform.log.info(`Switch "${device.Name}" initialized as ${homeKitState ? 'on' : 'off'} ` + `(Normally ${device.NormallyClosed ? 'Closed' : 'Open'})`); } async setOn(value) { const device = this.accessory.context.device; const newHomeKitState = value; const isOneShotTimer = device.OneShotTimer; if (!device.NormallyClosed) { this.switchState = device.NormallyClosed ? !newHomeKitState : newHomeKitState; } else { this.switchState = newHomeKitState; } if (this.switchState !== device.NormallyClosed) { //this.platform.log.info(`Switch "${device.Name}" turned ${device.NormallyClosed ? 'off' : 'on'}.`); if (!device.SwitchStayOn) { // Only start timer if it's not running or if OneShotTimer is false if (!isOneShotTimer || !this.timer || !this.timerEndTime || Date.now() >= this.timerEndTime) { const timerInfo = this.platform.calculateTargetTime(device); this.startOffTimer(timerInfo); } else { this.platform.log.debug(`DEBUG: "${device.Name}" Ignoring timer restart (OneShot timer is active)`); } } else { this.platform.log.info(`Switch "${device.Name}" turned ${device.NormallyClosed ? 'off' : 'on'}.`); } } else { if (device.SwitchStayOn) { this.platform.log.info(`Switch "${device.Name}" turned ${device.NormallyClosed ? 'on' : 'off'}.`); } this.clearTimer(); } if (device.RememberState) { this.platform.saveSwitchState(device.Name, this.switchState); } } async getOn() { return this.switchState; } triggerSwitch() { const device = this.accessory.context.device; const isTriggered = this.switchState !== device.NormallyClosed; const isOneShotTimer = device.OneShotTimer; // Handle stateful switches with log file monitoring if (isTriggered && device.SwitchStayOn && this.useLogFile) { this.platform.log.debug(`DEBUG: "${device.Name}" Ignoring trigger due to keyword (stateful switch).`); return; } // Handle non-stateful switches with active timers if (isTriggered && !device.SwitchStayOn && this.timer && this.timerEndTime && Date.now() < this.timerEndTime) { if (isOneShotTimer) { this.platform.log.debug(`DEBUG: "${device.Name}" Ignoring trigger as the timer is still active.`); return; } // If not OneShot timer, continue with normal operation (timer will restart) } // Toggle switch state this.switchState = !this.switchState; this.updateHomeKitState(); if (this.switchState !== device.NormallyClosed) { //this.platform.log.info(`Switch "${device.Name}" turned ${device.NormallyClosed ? 'off' : 'on'}.`); if (!device.SwitchStayOn) { // Only start timer if it's not running or if OneShotTimer is false if (!isOneShotTimer || !this.timer || !this.timerEndTime || Date.now() >= this.timerEndTime) { if (device.TimerPersistent) { const timerInfo = this.platform.calculateTargetTime(device); this.startOffTimer(timerInfo); } else { this.startOffTimer({ targetTime: 0, duration: device.Time }); } } else { this.platform.log.debug(`DEBUG: "${device.Name}" Keeping existing timer (OneShot timer is active)`); } } else { this.platform.log.info(`Switch "${device.Name}" turned ${device.NormallyClosed ? 'off' : 'on'}.`); } } else { if (device.SwitchStayOn) { this.platform.log.info(`Switch "${device.Name}" turned ${device.NormallyClosed ? 'on' : 'off'}.`); } this.clearTimer(); } // Save state if required if (device.RememberState) { this.platform.saveSwitchState(device.Name, this.switchState); } } startOffTimer(timerInfo) { const device = this.accessory.context.device; this.clearTimer(); this.timerEndTime = timerInfo.targetTime || (Date.now() + timerInfo.duration); // Log timer information const status = device.NormallyClosed ? 'off' : 'on'; const futureStatus = device.NormallyClosed ? 'on' : 'off'; if (device.TimerPersistent) { this.platform.log.info(`Switch "${device.Name}" turned ${status} and will turn ${futureStatus} at ${new Date(this.timerEndTime).toLocaleString()}`); // Save persistent timer state this.platform.saveTimerState(device.Name, this.timerEndTime, true); } else { this.platform.log.info(`Switch "${device.Name}" turned ${status} and will turn ${futureStatus} after ${timerInfo.duration} milliseconds.`); } this.timer = setTimeout(() => { this.switchState = device.NormallyClosed; this.updateHomeKitState(); if (device.TimerPersistent) { this.platform.clearTimerState(device.Name); } const homeKitState = this.switchState; this.platform.log.info(`Switch "${device.Name}" turned ${homeKitState ? 'on' : 'off'} automatically after timer expired.`); }, timerInfo.duration); } clearTimer() { const device = this.accessory.context.device; if (this.timer) { clearTimeout(this.timer); this.timer = undefined; this.timerEndTime = undefined; if (device.TimerPersistent) { this.platform.clearTimerState(device.Name); } } } updateHomeKitState() { const homeKitState = this.switchState; const service = this.accessory.getService(this.platform.Service.Switch); if (service) { service.updateCharacteristic(this.platform.Characteristic.On, homeKitState); } else { this.platform.log.error(`ERROR: Failed to find the switch service for "${this.accessory.displayName}".`); } } } //# sourceMappingURL=platformAccessory.js.map