mirror of
https://github.com/dtwardow/homebridge-multi-tap-switch.git
synced 2026-11-02 05:18:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
251c4ed5ea | ||
|
|
41375599a2 | ||
|
|
b5ddbf488f | ||
|
|
3ef3435417 |
No files matched your search
+5
@@ -1,6 +1,11 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<Languages>
|
||||
<language minSize="50" name="TypeScript" />
|
||||
</Languages>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
+19
-3
@@ -43,12 +43,19 @@
|
||||
"required": true,
|
||||
"default": 10,
|
||||
"minimum": 0,
|
||||
"maximum": 30
|
||||
"maximum": 60
|
||||
},
|
||||
"resetAfterSwitchOff": {
|
||||
"title": "Reset when switch turned `Off`",
|
||||
"title": "Reset when switch is turned `Off`",
|
||||
"type": "boolean",
|
||||
"description": "Reset the switch and trigger index, when the trigger switch is turned <code>Off</code> (independent from timeout)",
|
||||
"description": "Reset the switch and trigger index, when the stateless switch is turned <code>Off</code>",
|
||||
"required": true,
|
||||
"default": false
|
||||
},
|
||||
"triggerSceneAfterSwitchOff": {
|
||||
"title": "Trigger scene, when switch is turned `Off`",
|
||||
"type": "boolean",
|
||||
"description": "Trigger scene <code>0</code> (so called <code>Off</code>-scene), when the stateless switch is turned <code>Off</code> (independent from timeout).<br><b>Note:</b> If this is enabled, rotation starts from switch <code>1</code>, otherwise <code>0</code>.",
|
||||
"required": true,
|
||||
"default": false
|
||||
},
|
||||
@@ -57,6 +64,15 @@
|
||||
"type": "boolean",
|
||||
"required": true,
|
||||
"default": false
|
||||
},
|
||||
"debounceTimerMs": {
|
||||
"title": "Debounce Timer (in milliseconds)",
|
||||
"type": "number",
|
||||
"description": "Time to wait before the next switch trigger is accepted.<br>Only change this value, if you encounter problems using the HomeKit slider in the Home App",
|
||||
"required": true,
|
||||
"minimum": 10,
|
||||
"maximum": 1000,
|
||||
"default": 150
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"private": false,
|
||||
"displayName": "Homebridge Multi-Tap-Switch",
|
||||
"name": "homebridge-multi-tap-switch",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"description": "A configurable multi-tap switch for i.e. switching scenes",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -3,14 +3,19 @@ import { PlatformConfig } from 'homebridge';
|
||||
const DEFAULT_NUM_OF_CONFIGURED_SCENES = 5;
|
||||
const DEFAULT_TRIGGER_TIMEOUT = 10;
|
||||
const DEFAULT_RESET_AFTER_SWITCH_OFF = false;
|
||||
const DEFAULT_TRIGGER_SCENE_AFTER_SWITCH_OFF = false;
|
||||
const DEFAULT_TRIGGER_SCENE_OFF = 0;
|
||||
const DEFAULT_LOGGING = false;
|
||||
const DEFAULT_DEBOUNCE_TIMEOUT_MS = 150;
|
||||
|
||||
export interface PluginDeviceConfig {
|
||||
readonly name: string;
|
||||
readonly numberConfiguredScenes?: number;
|
||||
readonly triggerTimeout?: number;
|
||||
readonly resetAfterSwitchOff?: boolean;
|
||||
readonly triggerSceneAfterSwitchOff?: boolean;
|
||||
readonly logging?: boolean;
|
||||
readonly debounceTimerMs?: number;
|
||||
}
|
||||
|
||||
export class PluginConfig {
|
||||
@@ -50,10 +55,29 @@ export class DeviceConfig {
|
||||
|| DEFAULT_RESET_AFTER_SWITCH_OFF;
|
||||
}
|
||||
|
||||
isTriggerSceneWhenOff(): boolean {
|
||||
return this.device.triggerSceneAfterSwitchOff
|
||||
|| DEFAULT_TRIGGER_SCENE_AFTER_SWITCH_OFF;
|
||||
}
|
||||
|
||||
// When triggerSceneAfterSwitchOff is 'true', start at 1, otherwise 0 (normal behavior)
|
||||
FirstTriggerScene(): number {
|
||||
return this.isTriggerSceneWhenOff() ? 1 : 0;
|
||||
}
|
||||
|
||||
TriggerOffSceneIndex(): number {
|
||||
return DEFAULT_TRIGGER_SCENE_OFF;
|
||||
}
|
||||
|
||||
isLogging(): boolean {
|
||||
return this.device.logging
|
||||
|| DEFAULT_LOGGING;
|
||||
}
|
||||
|
||||
DebounceTimeoutMs(): number {
|
||||
return this.device.debounceTimerMs
|
||||
|| DEFAULT_DEBOUNCE_TIMEOUT_MS;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AccessoryState {
|
||||
|
||||
@@ -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
@@ -6,6 +6,7 @@ import { VERSION } from './settings';
|
||||
import { attachCharacteristic_ConfiguredScenes } from './types/characteristicConfiguredScenes';
|
||||
import { attachCharacteristic_TriggerTimeout } from './types/characteristicTimeout';
|
||||
import { attachCharacteristic_CurrentScene } from './types/characteristicCurrentScene';
|
||||
import TimeoutHandler from './helper/timeoutHandler';
|
||||
|
||||
/**
|
||||
* Platform Accessory
|
||||
@@ -23,6 +24,7 @@ export class DeviceAccessory {
|
||||
private SwitchResetTimer: NodeJS.Timeout | undefined;
|
||||
private SwitchOn: boolean = false;
|
||||
private NextItemToTrigger: number = 0;
|
||||
private LastTriggerReceived: TimeoutHandler;
|
||||
|
||||
private Log: Logging;
|
||||
|
||||
@@ -51,6 +53,8 @@ export class DeviceAccessory {
|
||||
this.Log = new Logging(platform.log, this.Config.Name(), this.Config.isLogging());
|
||||
this.Log.log('Initialize Accessory ->', this.Config.Name());
|
||||
|
||||
this.LastTriggerReceived = new TimeoutHandler(this.Config.DebounceTimeoutMs(), this.Log);
|
||||
|
||||
/**
|
||||
* Accessory information
|
||||
*/
|
||||
@@ -106,13 +110,13 @@ export class DeviceAccessory {
|
||||
// Register handlers for the CurrentScene characteristic & restore previous value
|
||||
attachCharacteristic_CurrentScene(this.serviceIn, this.api)
|
||||
.setProps({
|
||||
'minValue': 1,
|
||||
'minValue': 0,
|
||||
'maxValue': this.Config.NumOfScenes(),
|
||||
'minStep': 1,
|
||||
})
|
||||
.onSet(this.setCurrentScene.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
|
||||
this.serviceIn.setCharacteristic(this.platform.Characteristic.Name, this.Config.Name());
|
||||
@@ -165,14 +169,13 @@ export class DeviceAccessory {
|
||||
/**
|
||||
* Trigger/Fire the next programmable switch event
|
||||
*/
|
||||
triggerNext() {
|
||||
if (this.NextItemToTrigger >= this.State.numberConfiguredScenes) {
|
||||
this.NextItemToTrigger = 0;
|
||||
trigger(index: number) {
|
||||
if ((index >= 0) && (index < this.State.numberConfiguredScenes)) {
|
||||
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() {
|
||||
this.Log.log('Reset Scene Trigger');
|
||||
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
|
||||
* This function does all the necessary work.
|
||||
* @param value Target switch state from Automation or manual state change
|
||||
*/
|
||||
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;
|
||||
|
||||
if (this.SwitchOn) {
|
||||
if (this.SwitchOn) { // Switch is set to 'ON'
|
||||
// Disable an active reset timer, before starting a new one.
|
||||
if (this.SwitchResetTimer) {
|
||||
clearTimeout(this.SwitchResetTimer);
|
||||
}
|
||||
@@ -204,17 +213,31 @@ export class DeviceAccessory {
|
||||
this.SwitchResetTimer = setTimeout(this.resetTrigger.bind(this), this.State.triggerTimeout * 1000);
|
||||
}
|
||||
|
||||
// Trigger Scene
|
||||
this.triggerNext();
|
||||
} else if (this.Config.isResetWhenOff() && !this.SwitchOn) {
|
||||
// Determine next item to trigger
|
||||
if (this.NextItemToTrigger >= this.State.numberConfiguredScenes) {
|
||||
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) {
|
||||
clearTimeout(this.SwitchResetTimer);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report state of the input switch back to HomeKit (for visualization)
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"numberConfiguredScenes": 5,
|
||||
"triggerTimeout": 10,
|
||||
"resetAfterSwitchOff": false,
|
||||
"triggerSceneAfterSwitchOff": true,
|
||||
"logging": true
|
||||
}
|
||||
]
|
||||
|
||||
Reference in new issue
Block a user