Compatibility update for Homebridge 2.0

This commit is contained in:
dennis committed 2026-02-08 13:58:38 +01:00
1 parent 08cbdc46b9
commit 904ad68f6d
9 files changed
+3761 -3758

No files matched your search

+3599 -3656
View File
File diff suppressed because it is too large. Load diff
+15 -12
View File
@@ -2,7 +2,7 @@
"private": false,
"displayName": "Homebridge Multi-Tap-Switch",
"name": "homebridge-multi-tap-switch",
"version": "0.3.0",
"version": "0.4.0",
"description": "A configurable multi-tap switch for i.e. switching scenes",
"license": "MIT",
"repository": {
@@ -13,8 +13,8 @@
"url": "https://github.com/dtwardow/homebridge-multi-tap-switch/issues"
},
"engines": {
"node": ">=14.18.1",
"homebridge": ">=1.3.5"
"node": "^20.18.0 || ^22.10.0 || ^24.0.0",
"homebridge": "^1.8.0 || ^2.0.0-beta.0"
},
"main": "dist/index.js",
"scripts": {
@@ -26,16 +26,19 @@
"keywords": [
"homebridge-plugin"
],
"dependencies": {
"homebridge-lib": "^7.1.12"
},
"devDependencies": {
"@types/node": "^16.10.9",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"homebridge": "^1.3.5",
"nodemon": "^2.0.13",
"rimraf": "^3.0.2",
"ts-node": "^10.3.0",
"typescript": "^4.4.4"
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"eslint": "^9.39.1",
"homebridge": "^2.0.0-beta.55",
"nodemon": "^3.1.11",
"rimraf": "^6.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.4"
},
"homepage": "https://github.com/dtwardow/homebridge-multi-tap-switch#readme",
"author": "Dennis Twardowsky <twardowsky@gmail.com>"
+1 -1
View File
@@ -1,7 +1,7 @@
import { API } from 'homebridge';
import { PLATFORM_NAME } from './settings';
import { MultiTapSwitchPlatform } from './platform';
import MultiTapSwitchPlatform from './platform';
/**
* This method registers the platform with Homebridge
+9 -27
View File
@@ -4,24 +4,14 @@ import { PLATFORM_NAME, PLUGIN_NAME } from './settings';
import { DeviceAccessory } from './platformAccessory';
// Necessary definitions for custom characteristics!
import ConfiguredScenes from './types/characteristicConfiguredScenes';
let IConfiguredScenes;
import {initializeAccessoryState, PluginConfig} from './config';
let ITriggerTimeout;
import TriggerTimeout from './types/characteristicTimeout';
/**
* HomebridgePlatform
*/
export class MultiTapSwitchPlatform implements DynamicPlatformPlugin {
public readonly Service: typeof Service = this.api.hap.Service;
public readonly Characteristic:
typeof Characteristic &
typeof IConfiguredScenes &
typeof ITriggerTimeout;
private readonly CharacteristicConfiguredScenes;
private readonly CharacteristicTriggerTimeout;
class MultiTapSwitchPlatform implements DynamicPlatformPlugin {
public readonly Service: typeof Service;
public readonly Characteristic: typeof Characteristic;
// Track restored cached accessories
public readonly accessories: PlatformAccessory[] = [];
@@ -40,25 +30,15 @@ export class MultiTapSwitchPlatform implements DynamicPlatformPlugin {
public readonly api: API,
) {
this.Config = new PluginConfig(this.config);
this.Service = api.hap.Service;
this.Characteristic = api.hap.Characteristic;
this.log.debug('Finished initializing platform:', this.Config.Name);
this.CharacteristicConfiguredScenes = ConfiguredScenes(this.api);
IConfiguredScenes = this.CharacteristicConfiguredScenes;
this.CharacteristicTriggerTimeout = TriggerTimeout(this.api);
ITriggerTimeout = this.CharacteristicTriggerTimeout;
this.api.on('didFinishLaunching', () => {
log.debug('Discover/Register Devices ...');
this.discoverDevices();
});
this.Characteristic = Object.defineProperty(this.api.hap.Characteristic,
'ConfiguredScenes',
{value: this.CharacteristicConfiguredScenes});
this.Characteristic = Object.defineProperty(this.api.hap.Characteristic,
'TriggerTimeout',
{value: this.CharacteristicTriggerTimeout});
}
/**
@@ -110,7 +90,7 @@ export class MultiTapSwitchPlatform implements DynamicPlatformPlugin {
// Create the accessory handler for the restored accessory
// This is imported from `platformAccessory.ts`
new DeviceAccessory(this, existingAccessory);
new DeviceAccessory(this, this.api, existingAccessory);
knownUuids.push(uuid);
} else {
@@ -128,7 +108,7 @@ export class MultiTapSwitchPlatform implements DynamicPlatformPlugin {
// Create the accessory handler for the new accessory
// This is imported from `platformAccessory.ts`
new DeviceAccessory(this, accessory);
new DeviceAccessory(this, this.api, accessory);
// link the accessory to your platform
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
@@ -144,3 +124,5 @@ export class MultiTapSwitchPlatform implements DynamicPlatformPlugin {
}
}
}
export default MultiTapSwitchPlatform
+75 -26
View File
@@ -1,8 +1,11 @@
import {CharacteristicValue, PlatformAccessory, Service} from 'homebridge';
import {MultiTapSwitchPlatform} from './platform';
import {CharacteristicValue, PlatformAccessory, Service, API} from 'homebridge';
import MultiTapSwitchPlatform from './platform';
import {AccessoryState, DeviceConfig} from './config';
import {Logging} from './helper/logger';
import {VERSION} from './settings';
import {attachCharacteristic_ConfiguredScenes} from './types/characteristicConfiguredScenes';
import {attachCharacteristic_TriggerTimeout} from './types/characteristicTimeout';
import {attachCharacteristic_CurrentScene} from './types/characteristicCurrentScene';
/**
* Platform Accessory
@@ -11,7 +14,30 @@ export class DeviceAccessory {
private serviceIn: Service;
private serviceOut: Service;
private readonly singleButtonEvents = {
private readonly singleButtonEvents;
private readonly State: AccessoryState;
private readonly Config: DeviceConfig;
private readonly TriggerButtons: Service[] = [];
private SwitchResetTimer: NodeJS.Timeout | undefined;
private SwitchOn: boolean = false;
private NextItemToTrigger: number = 0;
private Log: Logging;
/**
* Construct the accessory services
* @param platform Parent Homebridge platform
* @param api Parent platform API
* @param accessory Accessory to which these services will be attached
*/
constructor(
private readonly platform: MultiTapSwitchPlatform,
private readonly api: API,
private readonly accessory: PlatformAccessory,
) {
this.singleButtonEvents = {
minValue: this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS,
maxValue: this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS,
validValues: [
@@ -19,25 +45,6 @@ export class DeviceAccessory {
],
};
private readonly State: AccessoryState;
private readonly Config: DeviceConfig;
private readonly TriggerButtons: Service[] = [];
private SwitchResetTimer: NodeJS.Timeout | undefined;
private SwitchOn = false;
private NextItemToTrigger = 0;
private Log: Logging;
/**
* Construct the accessory services
* @param platform Parent Homebridge platform
* @param accessory Accessory to which these services will be attached
*/
constructor(
private readonly platform: MultiTapSwitchPlatform,
private readonly accessory: PlatformAccessory,
) {
this.Config = new DeviceConfig(accessory.context.config);
this.State = this.accessory.context.persistent;
@@ -69,8 +76,7 @@ export class DeviceAccessory {
if (this.State.numberConfiguredScenes > this.Config.NumOfScenes()) {
this.State.numberConfiguredScenes = this.Config.NumOfScenes();
}
(this.serviceIn.getCharacteristic(this.platform.Characteristic.ConfiguredScenes)
|| this.serviceIn.addOptionalCharacteristic(this.platform.Characteristic.ConfiguredScenes))
attachCharacteristic_ConfiguredScenes(this.serviceIn, this.api)
.setProps({
'minValue': 1,
'maxValue': this.Config.NumOfScenes(),
@@ -87,8 +93,7 @@ export class DeviceAccessory {
this.Log.debug('Current Trigger Timeout [' + this.State.triggerTimeout + '] differs from configured value [' +
this.Config.TriggerTimeout() + ']. It won\'t be updated until it\'s equal again.');
}
(this.serviceIn.getCharacteristic(this.platform.Characteristic.TriggerTimeout)
|| this.serviceIn.addOptionalCharacteristic(this.platform.Characteristic.TriggerTimeout))
attachCharacteristic_TriggerTimeout(this.serviceIn, this.api)
.setProps({
'minValue': 0,
'maxValue': 30,
@@ -98,6 +103,17 @@ export class DeviceAccessory {
.onGet(this.getTriggerTimeout.bind(this))
.updateValue(this.State.triggerTimeout);
// Register handlers for the CurrentScene characteristic & restore previous value
attachCharacteristic_CurrentScene(this.serviceIn, this.api)
.setProps({
'minValue': 1,
'maxValue': this.Config.NumOfScenes(),
'minStep': 1,
})
.onSet(this.setCurrentScene.bind(this))
.onGet(this.getCurrentScene.bind(this))
.updateValue(this.NextItemToTrigger);
// 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());
@@ -277,4 +293,37 @@ export class DeviceAccessory {
this.Log.debug('Get Trigger Timeout ->', triggerTimeout);
return triggerTimeout;
}
/**
* Set the number of active switches.
* The final value is limited to the maximum configured number of programmable switches.
* @param value Number of active switches
*/
async setCurrentScene(value: CharacteristicValue) {
let currentScene = value as number;
if ((currentScene > 0) && (currentScene <= this.Config.NumOfScenes())) {
this.Log.log('Set current active scene ->', currentScene);
} else {
this.Log.warn(
'Set current scene ->', currentScene, '-> Out-of-Range / Value must be between 1 and', this.Config.NumOfScenes());
if (currentScene > this.Config.NumOfScenes()) {
currentScene = this.Config.NumOfScenes();
} else {
currentScene = 1;
}
}
// value must be corrected by one, because it's used as array index
this.NextItemToTrigger = currentScene - 1;
}
/**
* Report number of active switches back to HomeKit (for visualization)
*/
async getCurrentScene(): Promise<CharacteristicValue> {
const currentScene = this.NextItemToTrigger + 1;
this.Log.debug('Get Number of current scene ->', currentScene);
return currentScene;
}
}
+13 -12
View File
@@ -1,22 +1,23 @@
import { Formats, Perms } from 'homebridge';
import { API, Characteristic, Formats, Perms, Service } from 'homebridge';
export = (homebridge) => {
const CustomCharacteristic = homebridge.hap.Characteristic;
const characteristicUUID = 'A8E3F4DF-5E5D-6A7F-8FA7-A22FA5AE9F53';
const DISPLAY_NAME = 'Configured Scenes';
const UUID = 'A8E3F4DF-5E5D-6A7F-8FA7-A22FA5AE9F53';
return class ConfiguredScenes extends CustomCharacteristic {
public static readonly UUID: string = characteristicUUID;
export function attachCharacteristic_ConfiguredScenes(target: Service, api: API): Characteristic {
let result: Characteristic;
constructor() {
super('Configured Scenes', ConfiguredScenes.UUID, {
if (target.testCharacteristic(DISPLAY_NAME)) {
result = target.getCharacteristic(DISPLAY_NAME)!; // Already tested it exists
} else {
result = target.addCharacteristic(new api.hap.Characteristic(DISPLAY_NAME, UUID, {
format: Formats.UINT8,
//unit: '',
minValue: 0,
maxValue: 0,
minStep: 1,
perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.NOTIFY],
});
this.value = this.getDefaultValue();
}));
}
};
};
return result;
}
+23
View File
@@ -0,0 +1,23 @@
import { API, Characteristic, Formats, Perms, Service } from 'homebridge';
const DISPLAY_NAME = 'Current Scene';
const UUID = '290E9D33-0EFC-4640-891E-CC25AAD740EA';
export function attachCharacteristic_CurrentScene(target: Service, api: API): Characteristic {
let result: Characteristic;
if (target.testCharacteristic(DISPLAY_NAME)) {
result = target.getCharacteristic(DISPLAY_NAME)!; // Already tested it exists
} else {
result = target.addCharacteristic(new api.hap.Characteristic(DISPLAY_NAME, UUID, {
format: Formats.UINT8,
//unit: '',
minValue: 0,
maxValue: 0,
minStep: 1,
perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.NOTIFY],
}));
}
return result;
}
+13 -12
View File
@@ -1,22 +1,23 @@
import { Formats, Perms } from 'homebridge';
import { API, Characteristic, Formats, Perms, Service } from 'homebridge';
export = (homebridge) => {
const CustomCharacteristic = homebridge.hap.Characteristic;
const characteristicUUID = 'E2C19A02-1D2F-F592-A2BF-BF920014FB18';
const DISPLAY_NAME = 'Trigger Timeout';
const UUID = 'E2C19A02-1D2F-F592-A2BF-BF920014FB18';
return class TriggerTimeout extends CustomCharacteristic {
public static readonly UUID: string = characteristicUUID;
export function attachCharacteristic_TriggerTimeout(target: Service, api: API): Characteristic {
let result: Characteristic;
constructor() {
super('Trigger Timeout', TriggerTimeout.UUID, {
if (target.testCharacteristic(DISPLAY_NAME)) {
result = target.getCharacteristic(DISPLAY_NAME)!; // Already tested it exists
} else {
result = target.addCharacteristic(new api.hap.Characteristic(DISPLAY_NAME, UUID, {
format: Formats.UINT16,
//unit: '',
minValue: 0,
maxValue: 60,
minStep: 1,
perms: [Perms.PAIRED_WRITE, Perms.PAIRED_READ, Perms.NOTIFY],
});
this.value = this.getDefaultValue();
}));
}
};
};
return result;
}
+13 -12
View File
@@ -1,23 +1,24 @@
{
"compilerOptions": {
"target": "ES2018", // ~node10
"module": "commonjs",
"target": "ES2022",
"lib": [
"es2015",
"es2016",
"es2017",
"es2018"
"DOM",
"ES2022"
],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"rootDir": "src",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"declaration": true,
"outDir": "dist",
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"noImplicitAny": false
"forceConsistentCasingInFileNames": true
},
"include": [
"eslint.config.js",
"homebridge-ui",
"src/"
],
"exclude": [