homebridge-multiple-switch 1.1.6 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +9 -20
- package/.github/workflows/release.yml +7 -7
- package/CHANGELOG.md +32 -0
- package/README.md +80 -38
- package/index.js +116 -65
- package/package.json +7 -3
package/.github/workflows/ci.yml
CHANGED
|
@@ -11,31 +11,20 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
|
|
13
13
|
steps:
|
|
14
|
-
- name:
|
|
15
|
-
uses: actions/checkout@
|
|
14
|
+
- name: Checkout
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
16
|
|
|
17
|
-
- name:
|
|
18
|
-
uses: actions/setup-node@
|
|
17
|
+
- name: Setup Node.js
|
|
18
|
+
uses: actions/setup-node@v4
|
|
19
19
|
with:
|
|
20
20
|
node-version: '18'
|
|
21
21
|
cache: 'npm'
|
|
22
22
|
|
|
23
|
-
- name:
|
|
23
|
+
- name: Install dependencies
|
|
24
24
|
run: npm install
|
|
25
25
|
|
|
26
|
-
- name:
|
|
27
|
-
run:
|
|
28
|
-
if [ -f .eslintrc.js ] || [ -f .eslintrc.json ]; then
|
|
29
|
-
npm run lint
|
|
30
|
-
else
|
|
31
|
-
echo "Lint skripti tapılmadı, keçilir..."
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
- name: 🧪 Testləri icra et (əgər varsa)
|
|
35
|
-
run: |
|
|
36
|
-
if [ -f package.json ] && grep -q '\"test\"' package.json; then
|
|
37
|
-
npm test
|
|
38
|
-
else
|
|
39
|
-
echo "Test skripti yoxdur, keçilir..."
|
|
40
|
-
fi
|
|
26
|
+
- name: Lint
|
|
27
|
+
run: npm run lint
|
|
41
28
|
|
|
29
|
+
- name: Test
|
|
30
|
+
run: npm test
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: Publish to npm
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
@@ -10,19 +10,19 @@ jobs:
|
|
|
10
10
|
runs-on: ubuntu-latest
|
|
11
11
|
|
|
12
12
|
steps:
|
|
13
|
-
- name:
|
|
14
|
-
uses: actions/checkout@
|
|
13
|
+
- name: Checkout
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
15
|
|
|
16
|
-
- name:
|
|
17
|
-
uses: actions/setup-node@
|
|
16
|
+
- name: Setup Node.js
|
|
17
|
+
uses: actions/setup-node@v4
|
|
18
18
|
with:
|
|
19
19
|
node-version: '18'
|
|
20
20
|
registry-url: 'https://registry.npmjs.org/'
|
|
21
21
|
|
|
22
|
-
- name:
|
|
22
|
+
- name: Install dependencies
|
|
23
23
|
run: npm install
|
|
24
24
|
|
|
25
|
-
- name:
|
|
25
|
+
- name: Publish to npm
|
|
26
26
|
run: npm publish
|
|
27
27
|
env:
|
|
28
28
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.3.0] - 2026-03-21
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Replaced global mutable variables with instance properties
|
|
7
|
+
- Refactored accessory setup to reuse cached accessories instead of creating duplicates on restart
|
|
8
|
+
- Extracted switch behavior logic into dedicated methods (`turnOffOthers`, `setAll`, `scheduleAutoOff`)
|
|
9
|
+
- Used `Map` for service and accessory tracking instead of plain objects
|
|
10
|
+
- Service type lookup uses a constant map instead of a switch statement
|
|
11
|
+
- Auto-off now checks current state before turning off (prevents stale timeouts)
|
|
12
|
+
- Updated minimum Node.js version from 14 to 18
|
|
13
|
+
- Updated GitHub Actions from v3 to v4
|
|
14
|
+
- Cleaned up CI workflow
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- `delayOff` not working in `master` mode due to if/else logic bug
|
|
18
|
+
- Cached accessories not being restored on Homebridge restart (caused duplicate accessories)
|
|
19
|
+
- Services stored in `accessory.context` which is not serializable
|
|
20
|
+
- README field names (`autoTurnOff`, `mode`) now match actual config schema (`delayOff`, `switchBehavior`)
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- Automatic removal of stale cached accessories when config changes
|
|
24
|
+
- Service reconciliation: adds new services and removes old ones on config update
|
|
25
|
+
- Validation for empty or missing switches array
|
|
26
|
+
|
|
27
|
+
## [1.2.0] - 2025-01-01
|
|
28
|
+
|
|
29
|
+
- Initial published version with independent, master, and single switch modes
|
|
30
|
+
- Support for switch, outlet, lightbulb, and fan accessory types
|
|
31
|
+
- Per-switch config: type, defaultState, delayOff
|
|
32
|
+
- Homebridge UI config schema
|
package/README.md
CHANGED
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
# homebridge-multiple-switch
|
|
2
2
|
|
|
3
|
+

|
|
3
4
|
[](https://www.npmjs.com/package/homebridge-multiple-switch)
|
|
4
5
|
[](https://github.com/azadaydinli/homebridge-multiple-switch/issues)
|
|
5
6
|
[](https://github.com/azadaydinli/homebridge-multiple-switch/blob/master/LICENSE)
|
|
6
7
|
|
|
7
|
-
A lightweight Homebridge plugin that lets you create multiple customizable dummy switches
|
|
8
|
+
A lightweight Homebridge plugin that lets you create multiple customizable dummy switches under a single accessory — configurable as `Switch`, `Outlet`, `Lightbulb`, or `Fan`.
|
|
9
|
+
Supports `Independent`, `Master`, and `Single` switch modes.
|
|
8
10
|
|
|
9
11
|
---
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Features
|
|
12
14
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- **Independent Mode** –
|
|
16
|
-
- **Master Mode** –
|
|
17
|
-
- **Single Mode** –
|
|
18
|
-
-
|
|
19
|
-
-
|
|
15
|
+
- Grouped multiple switches in one HomeKit tile
|
|
16
|
+
- Accessory type: `switch`, `outlet`, `lightbulb`, or `fan`
|
|
17
|
+
- **Independent Mode** – All switches operate separately
|
|
18
|
+
- **Master Mode** – One switch controls all others
|
|
19
|
+
- **Single Mode** – Only one switch can be active at a time
|
|
20
|
+
- Per-switch config support (type, auto-off delay, default state)
|
|
21
|
+
- Switch states preserved across Homebridge restarts via cached accessories
|
|
22
|
+
- Automatic cleanup of stale accessories on config change
|
|
23
|
+
- Compatible with HomeKit and Siri
|
|
20
24
|
|
|
21
25
|
---
|
|
22
26
|
|
|
23
|
-
##
|
|
27
|
+
## Installation
|
|
24
28
|
|
|
25
|
-
Install
|
|
29
|
+
Install via Homebridge UI:
|
|
26
30
|
|
|
27
|
-
1.
|
|
31
|
+
1. Open **Plugins**
|
|
28
32
|
2. Search for `homebridge-multiple-switch`
|
|
29
33
|
3. Click **Install**
|
|
30
34
|
|
|
31
|
-
Or
|
|
35
|
+
Or install via terminal:
|
|
32
36
|
|
|
33
37
|
```bash
|
|
34
38
|
npm install -g homebridge-multiple-switch
|
|
@@ -36,35 +40,73 @@ npm install -g homebridge-multiple-switch
|
|
|
36
40
|
|
|
37
41
|
---
|
|
38
42
|
|
|
39
|
-
##
|
|
43
|
+
## Configuration
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
Configure from Homebridge UI or manually edit `config.json`:
|
|
42
46
|
|
|
43
|
-
```
|
|
47
|
+
```json
|
|
44
48
|
{
|
|
45
|
-
"
|
|
46
|
-
"name": "
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{
|
|
49
|
+
"platform": "MultipleSwitchPlatform",
|
|
50
|
+
"name": "Multiple Switches",
|
|
51
|
+
"switchBehavior": "single",
|
|
52
|
+
"switches": [
|
|
53
|
+
{
|
|
54
|
+
"name": "Heater",
|
|
55
|
+
"type": "outlet",
|
|
56
|
+
"defaultState": true,
|
|
57
|
+
"delayOff": 10000
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "Fan",
|
|
61
|
+
"type": "fan"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "Light",
|
|
65
|
+
"type": "lightbulb",
|
|
66
|
+
"delayOff": 5000
|
|
67
|
+
}
|
|
56
68
|
]
|
|
57
69
|
}
|
|
58
70
|
```
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
|
65
|
-
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### Platform Options
|
|
75
|
+
|
|
76
|
+
| Field | Type | Required | Description |
|
|
77
|
+
|------------------|--------|----------|--------------------------------------|
|
|
78
|
+
| `name` | string | Yes | Name of the platform instance |
|
|
79
|
+
| `switchBehavior` | string | No | `independent`, `master`, or `single` |
|
|
80
|
+
| `switches` | array | Yes | List of switches to create |
|
|
81
|
+
|
|
82
|
+
### Per-Switch Options
|
|
83
|
+
|
|
84
|
+
| Field | Type | Required | Description |
|
|
85
|
+
|----------------|---------|----------|--------------------------------------------------|
|
|
86
|
+
| `name` | string | Yes | Name of the switch |
|
|
87
|
+
| `type` | string | No | `switch`, `outlet`, `lightbulb`, or `fan` |
|
|
88
|
+
| `defaultState` | boolean | No | Initial power state (default: `false`) |
|
|
89
|
+
| `delayOff` | number | No | Auto turn off after N milliseconds (default: `0`) |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Example Use Cases
|
|
94
|
+
|
|
95
|
+
- Simulate smart plugs for automation testing
|
|
96
|
+
- Trigger HomeKit scenes manually
|
|
97
|
+
- Create virtual switches for non-HomeKit devices
|
|
98
|
+
- Combine several virtual accessories under one tile
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Links
|
|
103
|
+
|
|
104
|
+
- [NPM Package](https://www.npmjs.com/package/homebridge-multiple-switch)
|
|
105
|
+
- [Homebridge](https://homebridge.io/)
|
|
106
|
+
- [Plugin Issues](https://github.com/azadaydinli/homebridge-multiple-switch/issues)
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT © [Azad Aydınlı](https://github.com/azadaydinli)
|
package/index.js
CHANGED
|
@@ -1,109 +1,160 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const PLUGIN_NAME = 'homebridge-multiple-switch';
|
|
4
|
+
const PLATFORM_NAME = 'MultipleSwitchPlatform';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const SERVICE_TYPES = {
|
|
7
|
+
switch: 'Switch',
|
|
8
|
+
lightbulb: 'Lightbulb',
|
|
9
|
+
fan: 'Fan',
|
|
10
|
+
outlet: 'Outlet',
|
|
11
|
+
};
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
module.exports = (api) => {
|
|
14
|
+
api.registerPlatform(PLATFORM_NAME, MultipleSwitchPlatform);
|
|
11
15
|
};
|
|
12
16
|
|
|
13
17
|
class MultipleSwitchPlatform {
|
|
14
18
|
constructor(log, config, api) {
|
|
15
19
|
this.log = log;
|
|
16
|
-
this.config = config;
|
|
20
|
+
this.config = config || {};
|
|
17
21
|
this.api = api;
|
|
18
|
-
this.
|
|
22
|
+
this.Service = api.hap.Service;
|
|
23
|
+
this.Characteristic = api.hap.Characteristic;
|
|
24
|
+
this.cachedAccessories = new Map();
|
|
25
|
+
this.switchServices = new Map();
|
|
19
26
|
|
|
20
27
|
this.api.on('didFinishLaunching', () => {
|
|
21
|
-
this.log('
|
|
28
|
+
this.log.info('MultipleSwitchPlatform started.');
|
|
22
29
|
this.setupAccessories();
|
|
23
30
|
});
|
|
24
31
|
}
|
|
25
32
|
|
|
33
|
+
configureAccessory(accessory) {
|
|
34
|
+
this.cachedAccessories.set(accessory.UUID, accessory);
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
setupAccessories() {
|
|
27
|
-
const switches = this.config.switches
|
|
28
|
-
|
|
38
|
+
const switches = this.config.switches;
|
|
39
|
+
if (!Array.isArray(switches) || switches.length === 0) {
|
|
40
|
+
this.log.warn('No switches configured. Removing stale accessories.');
|
|
41
|
+
this.removeStaleCachedAccessories();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
const name = this.config.name || 'Multiple Switch Panel';
|
|
46
|
+
const behavior = this.config.switchBehavior || 'independent';
|
|
47
|
+
const uuid = this.api.hap.uuid.generate(name);
|
|
48
|
+
|
|
49
|
+
let accessory = this.cachedAccessories.get(uuid);
|
|
50
|
+
const isNew = !accessory;
|
|
30
51
|
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
if (isNew) {
|
|
53
|
+
accessory = new this.api.platformAccessory(name, uuid);
|
|
54
|
+
}
|
|
33
55
|
|
|
34
|
-
accessory.context.switchStates = {};
|
|
35
|
-
accessory.context.switchServices = {};
|
|
36
56
|
accessory.context.switchBehavior = behavior;
|
|
57
|
+
accessory.context.switchStates = accessory.context.switchStates || {};
|
|
58
|
+
|
|
59
|
+
this.reconcileServices(accessory, switches);
|
|
60
|
+
|
|
61
|
+
if (isNew) {
|
|
62
|
+
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.cachedAccessories.delete(uuid);
|
|
66
|
+
this.removeStaleCachedAccessories();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
reconcileServices(accessory, switches) {
|
|
70
|
+
const activeSubtypes = new Set();
|
|
37
71
|
|
|
38
72
|
switches.forEach((sw, index) => {
|
|
39
|
-
const
|
|
40
|
-
|
|
73
|
+
const subtype = `switch_${index}`;
|
|
74
|
+
activeSubtypes.add(subtype);
|
|
41
75
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
76
|
+
const ServiceClass = this.getServiceClass(sw.type);
|
|
77
|
+
let service = accessory.getServiceById(ServiceClass, subtype);
|
|
78
|
+
|
|
79
|
+
if (!service) {
|
|
80
|
+
service = accessory.addService(ServiceClass, sw.name, subtype);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
service.setCharacteristic(this.Characteristic.Name, sw.name);
|
|
84
|
+
this.configureSwitchHandlers(accessory, service, sw, subtype);
|
|
85
|
+
|
|
86
|
+
this.switchServices.set(subtype, service);
|
|
87
|
+
|
|
88
|
+
if (accessory.context.switchStates[subtype] === undefined) {
|
|
89
|
+
accessory.context.switchStates[subtype] = sw.defaultState || false;
|
|
90
|
+
}
|
|
45
91
|
});
|
|
46
92
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
);
|
|
52
|
-
this.accessories.push(accessory);
|
|
93
|
+
const servicesToRemove = accessory.services.filter((s) => {
|
|
94
|
+
return s.subtype && !activeSubtypes.has(s.subtype);
|
|
95
|
+
});
|
|
96
|
+
servicesToRemove.forEach((s) => accessory.removeService(s));
|
|
53
97
|
}
|
|
54
98
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
service.getCharacteristic(Characteristic.On)
|
|
60
|
-
.onGet(() => {
|
|
61
|
-
return accessory.context.switchStates[id];
|
|
62
|
-
})
|
|
99
|
+
configureSwitchHandlers(accessory, service, sw, subtype) {
|
|
100
|
+
service.getCharacteristic(this.Characteristic.On)
|
|
101
|
+
.onGet(() => accessory.context.switchStates[subtype] ?? false)
|
|
63
102
|
.onSet((value) => {
|
|
103
|
+
accessory.context.switchStates[subtype] = value;
|
|
104
|
+
this.log.info(`[${sw.name}] ${value ? 'ON' : 'OFF'}`);
|
|
105
|
+
|
|
64
106
|
const behavior = accessory.context.switchBehavior;
|
|
65
|
-
accessory.context.switchStates[id] = value;
|
|
66
|
-
this.log(`[${sw.name}] → ${value ? 'ON' : 'OFF'}`);
|
|
67
107
|
|
|
68
108
|
if (behavior === 'single' && value) {
|
|
69
|
-
|
|
70
|
-
if (key !== id) {
|
|
71
|
-
accessory.context.switchStates[key] = false;
|
|
72
|
-
accessory.context.switchServices[key].updateCharacteristic(Characteristic.On, false);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
109
|
+
this.turnOffOthers(accessory, subtype);
|
|
75
110
|
}
|
|
76
111
|
|
|
77
112
|
if (behavior === 'master') {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (value && sw.delayOff > 0) {
|
|
84
|
-
setTimeout(() => {
|
|
85
|
-
accessory.context.switchStates[id] = false;
|
|
86
|
-
service.updateCharacteristic(Characteristic.On, false);
|
|
87
|
-
this.log(`[${sw.name}] auto-off after ${sw.delayOff}ms`);
|
|
88
|
-
}, sw.delayOff);
|
|
89
|
-
}
|
|
113
|
+
this.setAll(accessory, value);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (value && sw.delayOff > 0) {
|
|
117
|
+
this.scheduleAutoOff(accessory, service, sw, subtype);
|
|
90
118
|
}
|
|
91
119
|
});
|
|
120
|
+
}
|
|
92
121
|
|
|
93
|
-
|
|
122
|
+
turnOffOthers(accessory, excludeSubtype) {
|
|
123
|
+
for (const [key, svc] of this.switchServices) {
|
|
124
|
+
if (key !== excludeSubtype) {
|
|
125
|
+
accessory.context.switchStates[key] = false;
|
|
126
|
+
svc.updateCharacteristic(this.Characteristic.On, false);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
94
129
|
}
|
|
95
130
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
case 'fan': return Service.Fan;
|
|
101
|
-
case 'outlet':
|
|
102
|
-
default: return Service.Outlet;
|
|
131
|
+
setAll(accessory, value) {
|
|
132
|
+
for (const [key, svc] of this.switchServices) {
|
|
133
|
+
accessory.context.switchStates[key] = value;
|
|
134
|
+
svc.updateCharacteristic(this.Characteristic.On, value);
|
|
103
135
|
}
|
|
104
136
|
}
|
|
105
137
|
|
|
106
|
-
|
|
107
|
-
|
|
138
|
+
scheduleAutoOff(accessory, service, sw, subtype) {
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
if (accessory.context.switchStates[subtype]) {
|
|
141
|
+
accessory.context.switchStates[subtype] = false;
|
|
142
|
+
service.updateCharacteristic(this.Characteristic.On, false);
|
|
143
|
+
this.log.info(`[${sw.name}] auto-off after ${sw.delayOff}ms`);
|
|
144
|
+
}
|
|
145
|
+
}, sw.delayOff);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getServiceClass(type) {
|
|
149
|
+
const key = (type || 'outlet').toLowerCase();
|
|
150
|
+
const name = SERVICE_TYPES[key] || SERVICE_TYPES.outlet;
|
|
151
|
+
return this.Service[name];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
removeStaleCachedAccessories() {
|
|
155
|
+
if (this.cachedAccessories.size === 0) return;
|
|
156
|
+
const stale = [...this.cachedAccessories.values()];
|
|
157
|
+
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, stale);
|
|
158
|
+
this.cachedAccessories.clear();
|
|
108
159
|
}
|
|
109
160
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-multiple-switch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Multiple switch platform for Homebridge",
|
|
5
5
|
"homepage": "https://github.com/azadaydinli/homebridge-multiple-switch",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "Azad Aydınlı",
|
|
8
8
|
"license": "ISC",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"lint": "echo \"No linter configured\"",
|
|
11
|
+
"test": "echo \"No tests configured\""
|
|
12
|
+
},
|
|
9
13
|
"bugs": {
|
|
10
14
|
"url": "https://github.com/azadaydinli/homebridge-multiple-switch/issues"
|
|
11
15
|
},
|
|
@@ -26,7 +30,7 @@
|
|
|
26
30
|
"url": "https://github.com/azadaydinli/homebridge-multiple-switch.git"
|
|
27
31
|
},
|
|
28
32
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
33
|
+
"node": ">=18.0.0",
|
|
30
34
|
"homebridge": ">=1.3.0"
|
|
31
35
|
}
|
|
32
|
-
}
|
|
36
|
+
}
|