Introduce new switch-off trigger and debounce timer.

This commit is contained in:
dennis committed 2026-03-09 21:30:29 +01:00
1 parent 3ef3435417
commit b5ddbf488f
2 files changed
+83 -15

No files matched your search

+45
View File
@@ -0,0 +1,45 @@
import { Logging } from './logger';
import { clearTimeout } from 'node:timers';
export default class TimeoutHandler {
private timeoutObject: NodeJS.Timeout | undefined;
public timeoutMs: number = 0;
private log: Logging | undefined;
private isRunning: boolean = false;
constructor(timeoutMs: number, logger: Logging) {
this.log = logger;
this.timeoutMs = timeoutMs;
}
public trigger(): void {
if (!this.isRunning) {
this.timeoutStarted();
this.timeoutObject = setTimeout(this.timeoutTriggered.bind(this), this.timeoutMs);
}
}
public cancel(): void {
if (!this.isRunning) {
clearTimeout(this.timeoutObject);
this.isRunning = false;
}
}
public elapsedAndTrigger(): boolean {
if (!this.isRunning) {
this.trigger();
return true;
}
return false;
}
private timeoutStarted(): void {
this.isRunning = true;
}
async timeoutTriggered(): Promise<void> {
this.isRunning = false;
}
}
+38 -15
View File
@@ -6,6 +6,7 @@ import { VERSION } from './settings';
import { attachCharacteristic_ConfiguredScenes } from './types/characteristicConfiguredScenes'; import { attachCharacteristic_ConfiguredScenes } from './types/characteristicConfiguredScenes';
import { attachCharacteristic_TriggerTimeout } from './types/characteristicTimeout'; import { attachCharacteristic_TriggerTimeout } from './types/characteristicTimeout';
import { attachCharacteristic_CurrentScene } from './types/characteristicCurrentScene'; import { attachCharacteristic_CurrentScene } from './types/characteristicCurrentScene';
import TimeoutHandler from './helper/timeoutHandler';
/** /**
* Platform Accessory * Platform Accessory
@@ -23,6 +24,7 @@ export class DeviceAccessory {
private SwitchResetTimer: NodeJS.Timeout | undefined; private SwitchResetTimer: NodeJS.Timeout | undefined;
private SwitchOn: boolean = false; private SwitchOn: boolean = false;
private NextItemToTrigger: number = 0; private NextItemToTrigger: number = 0;
private LastTriggerReceived: TimeoutHandler;
private Log: Logging; private Log: Logging;
@@ -51,6 +53,8 @@ export class DeviceAccessory {
this.Log = new Logging(platform.log, this.Config.Name(), this.Config.isLogging()); this.Log = new Logging(platform.log, this.Config.Name(), this.Config.isLogging());
this.Log.log('Initialize Accessory ->', this.Config.Name()); this.Log.log('Initialize Accessory ->', this.Config.Name());
this.LastTriggerReceived = new TimeoutHandler(this.Config.DebounceTimeoutMs(), this.Log);
/** /**
* Accessory information * Accessory information
*/ */
@@ -106,13 +110,13 @@ export class DeviceAccessory {
// Register handlers for the CurrentScene characteristic & restore previous value // Register handlers for the CurrentScene characteristic & restore previous value
attachCharacteristic_CurrentScene(this.serviceIn, this.api) attachCharacteristic_CurrentScene(this.serviceIn, this.api)
.setProps({ .setProps({
'minValue': 1, 'minValue': 0,
'maxValue': this.Config.NumOfScenes(), 'maxValue': this.Config.NumOfScenes(),
'minStep': 1, 'minStep': 1,
}) })
.onSet(this.setCurrentScene.bind(this)) .onSet(this.setCurrentScene.bind(this))
.onGet(this.getCurrentScene.bind(this)) .onGet(this.getCurrentScene.bind(this))
.updateValue(this.NextItemToTrigger); .updateValue(this.NextItemToTrigger = this.Config.FirstTriggerScene());
// Set the service name, this is what is displayed as the default name on the Home app // Set the service name, this is what is displayed as the default name on the Home app
this.serviceIn.setCharacteristic(this.platform.Characteristic.Name, this.Config.Name()); this.serviceIn.setCharacteristic(this.platform.Characteristic.Name, this.Config.Name());
@@ -165,14 +169,13 @@ export class DeviceAccessory {
/** /**
* Trigger/Fire the next programmable switch event * Trigger/Fire the next programmable switch event
*/ */
triggerNext() { trigger(index: number) {
if (this.NextItemToTrigger >= this.State.numberConfiguredScenes) { if ((index >= 0) && (index < this.State.numberConfiguredScenes)) {
this.NextItemToTrigger = 0; this.Log.log('Trigger Event -> ', index);
this.TriggerButtons[index].updateCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent, 0);
} else {
this.Log.error('Trigger Event -> Index ', index, ' out of bounds (min = 0, max = ', this.State.numberConfiguredScenes - 1, ')');
} }
// Trigger Scene Button
this.Log.log('Trigger Event -> ', this.NextItemToTrigger);
this.TriggerButtons[this.NextItemToTrigger++].updateCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent, 0);
} }
/** /**
@@ -181,19 +184,25 @@ export class DeviceAccessory {
resetTrigger() { resetTrigger() {
this.Log.log('Reset Scene Trigger'); this.Log.log('Reset Scene Trigger');
this.serviceIn.updateCharacteristic(this.platform.Characteristic.On, this.SwitchOn = false); this.serviceIn.updateCharacteristic(this.platform.Characteristic.On, this.SwitchOn = false);
this.NextItemToTrigger = 0; this.NextItemToTrigger = this.Config.FirstTriggerScene();
} }
// =====================
// = Handler Functions =
// =====================
/** /**
* Handle state changes of the input switch * Handle state changes of the input switch
* This function does all the necessary work. * This function does all the necessary work.
* @param value Target switch state from Automation or manual state change * @param value Target switch state from Automation or manual state change
*/ */
async setOn(value: CharacteristicValue) { async setOn(value: CharacteristicValue) {
// implement your own code to turn your device on/off // Check if debounce timer has elapsed. If not, ignore the command.
if (this.LastTriggerReceived.elapsedAndTrigger()) {
this.SwitchOn = value as boolean; this.SwitchOn = value as boolean;
if (this.SwitchOn) { if (this.SwitchOn) { // Switch is set to 'ON'
// Disable an active reset timer, before starting a new one.
if (this.SwitchResetTimer) { if (this.SwitchResetTimer) {
clearTimeout(this.SwitchResetTimer); clearTimeout(this.SwitchResetTimer);
} }
@@ -204,17 +213,31 @@ export class DeviceAccessory {
this.SwitchResetTimer = setTimeout(this.resetTrigger.bind(this), this.State.triggerTimeout * 1000); this.SwitchResetTimer = setTimeout(this.resetTrigger.bind(this), this.State.triggerTimeout * 1000);
} }
// Trigger Scene // Determine next item to trigger
this.triggerNext(); if (this.NextItemToTrigger >= this.State.numberConfiguredScenes) {
} else if (this.Config.isResetWhenOff() && !this.SwitchOn) { this.NextItemToTrigger = this.Config.FirstTriggerScene();
}
// Trigger Switch
this.trigger(this.NextItemToTrigger++);
} else { // Switch is set to 'OFF'
// If configured: Disable any active reset timer, when the switch is manually turned off
if (this.Config.isResetWhenOff()) {
if (this.SwitchResetTimer) { if (this.SwitchResetTimer) {
clearTimeout(this.SwitchResetTimer); clearTimeout(this.SwitchResetTimer);
} }
this.resetTrigger(); this.resetTrigger();
} }
// If configured: Trigger stateless switch / scene when manually switched off
if (this.Config.isTriggerSceneWhenOff()) {
// Trigger scene 0
this.trigger(this.Config.TriggerOffSceneIndex());
}
}
this.Log.debug('Set Switch State ->', value); this.Log.debug('Set Switch State ->', value);
} }
}
/** /**
* Report state of the input switch back to HomeKit (for visualization) * Report state of the input switch back to HomeKit (for visualization)