homebridge-multiple-switch 1.6.0 → 1.7.0-beta.2
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/CHANGELOG.md +15 -0
- package/README.md +70 -35
- package/banner.png +0 -0
- package/config.schema.json +10 -10
- package/homebridge-ui/public/i18n/ar.json +1 -1
- package/homebridge-ui/public/i18n/de.json +1 -1
- package/homebridge-ui/public/i18n/en.json +1 -1
- package/homebridge-ui/public/i18n/es.json +1 -1
- package/homebridge-ui/public/i18n/fr.json +1 -1
- package/homebridge-ui/public/i18n/it.json +1 -1
- package/homebridge-ui/public/i18n/ja.json +1 -1
- package/homebridge-ui/public/i18n/ko.json +1 -1
- package/homebridge-ui/public/i18n/nl.json +1 -1
- package/homebridge-ui/public/i18n/pl.json +1 -1
- package/homebridge-ui/public/i18n/pt.json +1 -1
- package/homebridge-ui/public/i18n/ru.json +1 -1
- package/homebridge-ui/public/i18n/tr.json +1 -1
- package/homebridge-ui/public/i18n/zh-CN.json +1 -1
- package/homebridge-ui/public/index.html +31 -19
- package/index.js +2 -1
- package/logo.png +0 -0
- package/logo.svg +102 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.7.0-beta.2] - 2026-05-17
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Switch Type description now appears below the dropdown, consistent with Switch Behavior Mode
|
|
7
|
+
|
|
8
|
+
## [1.7.0-beta.1] - 2026-05-17
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Switch type (Switch / Outlet) is now configured per device instead of per individual switch
|
|
12
|
+
- All switches within a device share the same type, set once in the device settings
|
|
13
|
+
- Switch cards no longer show a type selector — type is shown in the collapsed summary from the device level
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Automatic migration: existing configs with per-switch `type` are migrated to device-level `switchType` on first UI save (first switch's type is used as the device type)
|
|
17
|
+
|
|
3
18
|
## [1.6.0] - 2026-03-22
|
|
4
19
|
|
|
5
20
|
### Added
|
package/README.md
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/azadaydinli/homebridge-multiple-switch/master/banner.png" width="600">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<span align="center">
|
|
6
|
+
|
|
7
|
+
# Homebridge Multiple Switch
|
|
8
|
+
|
|
9
|
+
A lightweight Homebridge plugin that lets you create multiple customizable dummy switches in HomeKit. Supports multi-device, master switch, and 14 languages.
|
|
10
|
+
|
|
11
|
+
[](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
|
|
4
12
|
[](https://www.npmjs.com/package/homebridge-multiple-switch)
|
|
5
|
-
[](https://www.npmjs.com/package/homebridge-multiple-switch)
|
|
6
14
|
[](https://github.com/azadaydinli/homebridge-multiple-switch/blob/master/LICENSE)
|
|
7
15
|
|
|
8
|
-
|
|
9
|
-
Supports `Independent`, `Master`, and `Single` switch modes.
|
|
16
|
+
</span>
|
|
10
17
|
|
|
11
18
|
---
|
|
12
19
|
|
|
13
20
|
## Features
|
|
14
21
|
|
|
15
|
-
-
|
|
16
|
-
- Accessory type: `switch
|
|
22
|
+
- **Multi-device support** — create multiple separate HomeKit accessories
|
|
23
|
+
- Accessory type: `switch` or `outlet`
|
|
17
24
|
- **Independent Mode** – All switches operate separately
|
|
18
|
-
- **Master Mode** – One switch controls all others
|
|
19
25
|
- **Single Mode** – Only one switch can be active at a time
|
|
26
|
+
- **Master Switch** (Independent mode) — one switch controls all others
|
|
20
27
|
- Per-switch config support (type, auto-off delay, default state)
|
|
21
|
-
-
|
|
22
|
-
-
|
|
28
|
+
- Collapsible config UI with dark mode support
|
|
29
|
+
- i18n localization (14 languages)
|
|
30
|
+
- Homebridge v2 compatible
|
|
31
|
+
- Switch states preserved across restarts via cached accessories
|
|
23
32
|
- Compatible with HomeKit and Siri
|
|
24
33
|
|
|
25
34
|
---
|
|
@@ -47,23 +56,40 @@ Configure from Homebridge UI or manually edit `config.json`:
|
|
|
47
56
|
```json
|
|
48
57
|
{
|
|
49
58
|
"platform": "MultipleSwitchPlatform",
|
|
50
|
-
"name": "Multiple
|
|
51
|
-
"
|
|
52
|
-
"switches": [
|
|
59
|
+
"name": "Multiple Switch Platform",
|
|
60
|
+
"devices": [
|
|
53
61
|
{
|
|
54
|
-
"name": "
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
62
|
+
"name": "Living Room",
|
|
63
|
+
"switchBehavior": "independent",
|
|
64
|
+
"masterSwitch": true,
|
|
65
|
+
"masterSwitchType": "switch",
|
|
66
|
+
"switches": [
|
|
67
|
+
{
|
|
68
|
+
"name": "Lamp",
|
|
69
|
+
"type": "outlet",
|
|
70
|
+
"defaultState": false,
|
|
71
|
+
"delayOff": 0
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "Heater",
|
|
75
|
+
"type": "switch",
|
|
76
|
+
"delayOff": 10000
|
|
77
|
+
}
|
|
78
|
+
]
|
|
58
79
|
},
|
|
59
80
|
{
|
|
60
|
-
"name": "
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
"name": "Bedroom",
|
|
82
|
+
"switchBehavior": "single",
|
|
83
|
+
"switches": [
|
|
84
|
+
{
|
|
85
|
+
"name": "Scene 1",
|
|
86
|
+
"type": "switch"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "Scene 2",
|
|
90
|
+
"type": "switch"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
67
93
|
}
|
|
68
94
|
]
|
|
69
95
|
}
|
|
@@ -73,19 +99,28 @@ Configure from Homebridge UI or manually edit `config.json`:
|
|
|
73
99
|
|
|
74
100
|
### Platform Options
|
|
75
101
|
|
|
76
|
-
| Field
|
|
77
|
-
|
|
78
|
-
| `name`
|
|
79
|
-
| `
|
|
80
|
-
|
|
102
|
+
| Field | Type | Required | Description |
|
|
103
|
+
|-----------|--------|----------|-------------------------------|
|
|
104
|
+
| `name` | string | Yes | Name of the platform instance |
|
|
105
|
+
| `devices` | array | Yes | List of devices to create |
|
|
106
|
+
|
|
107
|
+
### Device Options
|
|
108
|
+
|
|
109
|
+
| Field | Type | Required | Description |
|
|
110
|
+
|--------------------|---------|----------|-------------------------------------------------|
|
|
111
|
+
| `name` | string | Yes | Device name (becomes HomeKit accessory name) |
|
|
112
|
+
| `switchBehavior` | string | No | `independent` or `single` (default: independent) |
|
|
113
|
+
| `masterSwitch` | boolean | No | Enable master switch (Independent mode only) |
|
|
114
|
+
| `masterSwitchType` | string | No | `switch` or `outlet` (default: switch) |
|
|
115
|
+
| `switches` | array | Yes | List of switches for this device |
|
|
81
116
|
|
|
82
117
|
### Per-Switch Options
|
|
83
118
|
|
|
84
|
-
| Field | Type | Required | Description
|
|
85
|
-
|
|
86
|
-
| `name` | string | Yes | Name of the switch
|
|
87
|
-
| `type` | string | No | `switch
|
|
88
|
-
| `defaultState` | boolean | No | Initial power state (default: `false`)
|
|
119
|
+
| Field | Type | Required | Description |
|
|
120
|
+
|----------------|---------|----------|---------------------------------------------------|
|
|
121
|
+
| `name` | string | Yes | Name of the switch |
|
|
122
|
+
| `type` | string | No | `switch` or `outlet` (default: outlet) |
|
|
123
|
+
| `defaultState` | boolean | No | Initial power state (default: `false`) |
|
|
89
124
|
| `delayOff` | number | No | Auto turn off after N milliseconds (default: `0`) |
|
|
90
125
|
|
|
91
126
|
---
|
|
@@ -95,7 +130,7 @@ Configure from Homebridge UI or manually edit `config.json`:
|
|
|
95
130
|
- Simulate smart plugs for automation testing
|
|
96
131
|
- Trigger HomeKit scenes manually
|
|
97
132
|
- Create virtual switches for non-HomeKit devices
|
|
98
|
-
-
|
|
133
|
+
- Group several virtual accessories under one device
|
|
99
134
|
|
|
100
135
|
---
|
|
101
136
|
|
package/banner.png
ADDED
|
Binary file
|
package/config.schema.json
CHANGED
|
@@ -34,6 +34,16 @@
|
|
|
34
34
|
{ "title": "Single", "enum": ["single"] }
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
|
+
"switchType": {
|
|
38
|
+
"title": "Switch Type",
|
|
39
|
+
"description": "The HomeKit accessory type for all switches in this device.",
|
|
40
|
+
"type": "string",
|
|
41
|
+
"default": "outlet",
|
|
42
|
+
"oneOf": [
|
|
43
|
+
{ "title": "Switch", "enum": ["switch"] },
|
|
44
|
+
{ "title": "Outlet", "enum": ["outlet"] }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
37
47
|
"masterSwitch": {
|
|
38
48
|
"title": "Master Switch",
|
|
39
49
|
"description": "Add a master switch that turns all switches on or off at once. Only available in Single mode.",
|
|
@@ -52,16 +62,6 @@
|
|
|
52
62
|
"description": "Display name of the switch in HomeKit.",
|
|
53
63
|
"type": "string"
|
|
54
64
|
},
|
|
55
|
-
"type": {
|
|
56
|
-
"title": "Switch Type",
|
|
57
|
-
"description": "The HomeKit accessory type for this switch.",
|
|
58
|
-
"type": "string",
|
|
59
|
-
"default": "outlet",
|
|
60
|
-
"oneOf": [
|
|
61
|
-
{ "title": "Switch", "enum": ["switch"] },
|
|
62
|
-
{ "title": "Outlet", "enum": ["outlet"] }
|
|
63
|
-
]
|
|
64
|
-
},
|
|
65
65
|
"defaultState": {
|
|
66
66
|
"title": "Default State",
|
|
67
67
|
"description": "Initial power state when Homebridge starts.",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "اسم المفتاح",
|
|
12
12
|
"switchNameDesc": "اسم العرض للمفتاح في HomeKit.",
|
|
13
13
|
"switchType": "نوع المفتاح",
|
|
14
|
-
"switchTypeDesc": "نوع ملحق HomeKit
|
|
14
|
+
"switchTypeDesc": "نوع ملحق HomeKit لجميع المفاتيح في هذا الجهاز.",
|
|
15
15
|
"typeSwitch": "مفتاح",
|
|
16
16
|
"typeOutlet": "مقبس",
|
|
17
17
|
"defaultState": "الحالة الافتراضية",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "Schaltername",
|
|
12
12
|
"switchNameDesc": "Anzeigename des Schalters in HomeKit.",
|
|
13
13
|
"switchType": "Schaltertyp",
|
|
14
|
-
"switchTypeDesc": "Der HomeKit-Zubehörtyp für
|
|
14
|
+
"switchTypeDesc": "Der HomeKit-Zubehörtyp für alle Schalter in diesem Gerät.",
|
|
15
15
|
"typeSwitch": "Schalter",
|
|
16
16
|
"typeOutlet": "Steckdose",
|
|
17
17
|
"defaultState": "Standardzustand",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "Switch Name",
|
|
12
12
|
"switchNameDesc": "Display name of the switch in HomeKit.",
|
|
13
13
|
"switchType": "Switch Type",
|
|
14
|
-
"switchTypeDesc": "The HomeKit accessory type for this
|
|
14
|
+
"switchTypeDesc": "The HomeKit accessory type for all switches in this device.",
|
|
15
15
|
"typeSwitch": "Switch",
|
|
16
16
|
"typeOutlet": "Outlet",
|
|
17
17
|
"defaultState": "Default State",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "Nombre del interruptor",
|
|
12
12
|
"switchNameDesc": "Nombre visible del interruptor en HomeKit.",
|
|
13
13
|
"switchType": "Tipo de interruptor",
|
|
14
|
-
"switchTypeDesc": "El tipo de accesorio HomeKit para este
|
|
14
|
+
"switchTypeDesc": "El tipo de accesorio HomeKit para todos los interruptores de este dispositivo.",
|
|
15
15
|
"typeSwitch": "Interruptor",
|
|
16
16
|
"typeOutlet": "Enchufe",
|
|
17
17
|
"defaultState": "Estado predeterminado",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "Nom de l'interrupteur",
|
|
12
12
|
"switchNameDesc": "Nom affiché de l'interrupteur dans HomeKit.",
|
|
13
13
|
"switchType": "Type d'interrupteur",
|
|
14
|
-
"switchTypeDesc": "Le type d'accessoire HomeKit pour cet
|
|
14
|
+
"switchTypeDesc": "Le type d'accessoire HomeKit pour tous les interrupteurs de cet appareil.",
|
|
15
15
|
"typeSwitch": "Interrupteur",
|
|
16
16
|
"typeOutlet": "Prise",
|
|
17
17
|
"defaultState": "État par défaut",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "Nome dell'interruttore",
|
|
12
12
|
"switchNameDesc": "Nome visualizzato dell'interruttore in HomeKit.",
|
|
13
13
|
"switchType": "Tipo di interruttore",
|
|
14
|
-
"switchTypeDesc": "Il tipo di accessorio HomeKit per questo
|
|
14
|
+
"switchTypeDesc": "Il tipo di accessorio HomeKit per tutti gli interruttori in questo dispositivo.",
|
|
15
15
|
"typeSwitch": "Interruttore",
|
|
16
16
|
"typeOutlet": "Presa",
|
|
17
17
|
"defaultState": "Stato predefinito",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "スイッチ名",
|
|
12
12
|
"switchNameDesc": "HomeKit でのスイッチの表示名。",
|
|
13
13
|
"switchType": "スイッチタイプ",
|
|
14
|
-
"switchTypeDesc": "
|
|
14
|
+
"switchTypeDesc": "このデバイス内のすべてのスイッチの HomeKit アクセサリタイプ。",
|
|
15
15
|
"typeSwitch": "スイッチ",
|
|
16
16
|
"typeOutlet": "コンセント",
|
|
17
17
|
"defaultState": "デフォルト状態",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "스위치 이름",
|
|
12
12
|
"switchNameDesc": "HomeKit에서 스위치의 표시 이름.",
|
|
13
13
|
"switchType": "스위치 유형",
|
|
14
|
-
"switchTypeDesc": "이
|
|
14
|
+
"switchTypeDesc": "이 장치의 모든 스위치에 적용되는 HomeKit 액세서리 유형.",
|
|
15
15
|
"typeSwitch": "스위치",
|
|
16
16
|
"typeOutlet": "콘센트",
|
|
17
17
|
"defaultState": "기본 상태",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "Schakelaarnaam",
|
|
12
12
|
"switchNameDesc": "Weergavenaam van de schakelaar in HomeKit.",
|
|
13
13
|
"switchType": "Schakelaartype",
|
|
14
|
-
"switchTypeDesc": "Het HomeKit-accessoiretype voor
|
|
14
|
+
"switchTypeDesc": "Het HomeKit-accessoiretype voor alle schakelaars in dit apparaat.",
|
|
15
15
|
"typeSwitch": "Schakelaar",
|
|
16
16
|
"typeOutlet": "Stopcontact",
|
|
17
17
|
"defaultState": "Standaardstatus",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "Nazwa przełącznika",
|
|
12
12
|
"switchNameDesc": "Wyświetlana nazwa przełącznika w HomeKit.",
|
|
13
13
|
"switchType": "Typ przełącznika",
|
|
14
|
-
"switchTypeDesc": "Typ akcesorium HomeKit dla
|
|
14
|
+
"switchTypeDesc": "Typ akcesorium HomeKit dla wszystkich przełączników w tym urządzeniu.",
|
|
15
15
|
"typeSwitch": "Przełącznik",
|
|
16
16
|
"typeOutlet": "Gniazdko",
|
|
17
17
|
"defaultState": "Stan domyślny",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "Nome do interruptor",
|
|
12
12
|
"switchNameDesc": "Nome de exibição do interruptor no HomeKit.",
|
|
13
13
|
"switchType": "Tipo de interruptor",
|
|
14
|
-
"switchTypeDesc": "O tipo de acessório HomeKit para
|
|
14
|
+
"switchTypeDesc": "O tipo de acessório HomeKit para todos os interruptores deste dispositivo.",
|
|
15
15
|
"typeSwitch": "Interruptor",
|
|
16
16
|
"typeOutlet": "Tomada",
|
|
17
17
|
"defaultState": "Estado padrão",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "Название переключателя",
|
|
12
12
|
"switchNameDesc": "Отображаемое имя переключателя в HomeKit.",
|
|
13
13
|
"switchType": "Тип переключателя",
|
|
14
|
-
"switchTypeDesc": "Тип аксессуара HomeKit для
|
|
14
|
+
"switchTypeDesc": "Тип аксессуара HomeKit для всех переключателей в этом устройстве.",
|
|
15
15
|
"typeSwitch": "Выключатель",
|
|
16
16
|
"typeOutlet": "Розетка",
|
|
17
17
|
"defaultState": "Состояние по умолчанию",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"switchName": "Anahtar Adı",
|
|
12
12
|
"switchNameDesc": "Anahtarın HomeKit'teki görünen adı.",
|
|
13
13
|
"switchType": "Anahtar Tipi",
|
|
14
|
-
"switchTypeDesc": "Bu
|
|
14
|
+
"switchTypeDesc": "Bu cihazdaki tüm anahtarlar için HomeKit aksesuar tipi.",
|
|
15
15
|
"typeSwitch": "Anahtar",
|
|
16
16
|
"typeOutlet": "Priz",
|
|
17
17
|
"defaultState": "Varsayılan Durum",
|
|
@@ -181,6 +181,18 @@
|
|
|
181
181
|
delete config.switchBehavior;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// Migrate per-switch type to device-level switchType
|
|
185
|
+
if (Array.isArray(config.devices)) {
|
|
186
|
+
config.devices.forEach(dev => {
|
|
187
|
+
if (!dev.switchType && Array.isArray(dev.switches) && dev.switches.length > 0) {
|
|
188
|
+
dev.switchType = dev.switches[0].type || 'outlet';
|
|
189
|
+
}
|
|
190
|
+
if (Array.isArray(dev.switches)) {
|
|
191
|
+
dev.switches.forEach(sw => { delete sw.type; });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
184
196
|
if (!config.devices) {
|
|
185
197
|
config.devices = [];
|
|
186
198
|
}
|
|
@@ -387,7 +399,7 @@
|
|
|
387
399
|
</div>
|
|
388
400
|
|
|
389
401
|
<div class="device-body ${isOpen ? '' : 'collapsed'}">
|
|
390
|
-
<div class="inline-row">
|
|
402
|
+
<div class="inline-row" style="grid-template-columns: 1fr 1fr 1fr">
|
|
391
403
|
<div class="form-group">
|
|
392
404
|
<label>${t.deviceName || 'Device Name'}</label>
|
|
393
405
|
<input type="text" class="dev-field" data-dev="${di}" data-field="name" value="${esc(dev.name || '')}">
|
|
@@ -400,6 +412,14 @@
|
|
|
400
412
|
</select>
|
|
401
413
|
<div class="desc" style="margin-top:6px">${behaviorDesc}</div>
|
|
402
414
|
</div>
|
|
415
|
+
<div class="form-group">
|
|
416
|
+
<label>${t.switchType || 'Switch Type'}</label>
|
|
417
|
+
<select class="dev-field" data-dev="${di}" data-field="switchType">
|
|
418
|
+
<option value="outlet" ${!dev.switchType || dev.switchType === 'outlet' ? 'selected' : ''}>${t.typeOutlet || 'Outlet'}</option>
|
|
419
|
+
<option value="switch" ${dev.switchType === 'switch' ? 'selected' : ''}>${t.typeSwitch || 'Switch'}</option>
|
|
420
|
+
</select>
|
|
421
|
+
<div class="desc" style="margin-top:6px">${t.switchTypeDesc || ''}</div>
|
|
422
|
+
</div>
|
|
403
423
|
</div>
|
|
404
424
|
|
|
405
425
|
${isIndependent ? `
|
|
@@ -424,7 +444,7 @@
|
|
|
424
444
|
|
|
425
445
|
<div class="section-title" style="margin-top:16px">${t.switches || 'Switches'}</div>
|
|
426
446
|
|
|
427
|
-
${switches.map((sw, si) => renderSwitch(sw, di, si)).join('')}
|
|
447
|
+
${switches.map((sw, si) => renderSwitch(sw, di, si, dev.switchType || 'outlet')).join('')}
|
|
428
448
|
|
|
429
449
|
<button class="btn btn-primary btn-add-switch" data-dev="${di}">+ ${t.addSwitch || 'Add Switch'}</button>
|
|
430
450
|
</div>
|
|
@@ -432,9 +452,10 @@
|
|
|
432
452
|
`;
|
|
433
453
|
}
|
|
434
454
|
|
|
435
|
-
function renderSwitch(sw, di, si) {
|
|
455
|
+
function renderSwitch(sw, di, si, deviceSwitchType) {
|
|
436
456
|
const isOpen = expandedSwitches.has(`${di}_${si}`);
|
|
437
457
|
const swLabel = sw.name || `#${si + 1}`;
|
|
458
|
+
const typeLabel = deviceSwitchType === 'switch' ? (t.typeSwitch || 'Switch') : (t.typeOutlet || 'Outlet');
|
|
438
459
|
|
|
439
460
|
return `
|
|
440
461
|
<div class="switch-card">
|
|
@@ -442,7 +463,7 @@
|
|
|
442
463
|
<div class="switch-header-left">
|
|
443
464
|
<span class="chevron ${isOpen ? 'open' : ''}">▶</span>
|
|
444
465
|
<strong>${esc(swLabel)}</strong>
|
|
445
|
-
${!isOpen ? `<span class="switch-summary">${
|
|
466
|
+
${!isOpen ? `<span class="switch-summary">${typeLabel}${sw.delayOff ? ` / ${sw.delayOff}ms` : ''}</span>` : ''}
|
|
446
467
|
</div>
|
|
447
468
|
<div class="switch-header-right">
|
|
448
469
|
<button class="btn btn-danger btn-sm btn-remove-switch" data-dev="${di}" data-sw="${si}">${t.removeSwitch || 'Remove'}</button>
|
|
@@ -454,25 +475,16 @@
|
|
|
454
475
|
<label>${t.switchName || 'Switch Name'}</label>
|
|
455
476
|
<input type="text" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="name" value="${esc(sw.name || '')}">
|
|
456
477
|
</div>
|
|
457
|
-
<div class="form-group">
|
|
458
|
-
<label>${t.switchType || 'Switch Type'}</label>
|
|
459
|
-
<select class="sw-field" data-dev="${di}" data-sw="${si}" data-field="type">
|
|
460
|
-
<option value="switch" ${sw.type === 'switch' ? 'selected' : ''}>${t.typeSwitch || 'Switch'}</option>
|
|
461
|
-
<option value="outlet" ${sw.type === 'outlet' || !sw.type ? 'selected' : ''}>${t.typeOutlet || 'Outlet'}</option>
|
|
462
|
-
</select>
|
|
463
|
-
</div>
|
|
464
|
-
</div>
|
|
465
|
-
<div class="inline-row">
|
|
466
478
|
<div class="form-group">
|
|
467
479
|
<label>${t.delayOff || 'Auto Turn Off (ms)'}</label>
|
|
468
480
|
<input type="number" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="delayOff" min="0" value="${sw.delayOff || 0}">
|
|
469
481
|
</div>
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
</
|
|
482
|
+
</div>
|
|
483
|
+
<div class="form-group">
|
|
484
|
+
<label>${t.defaultState || 'Default State'}</label>
|
|
485
|
+
<div class="toggle-wrap" style="margin-top:6px">
|
|
486
|
+
<input type="checkbox" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="defaultState" ${sw.defaultState ? 'checked' : ''}>
|
|
487
|
+
<span>${sw.defaultState ? (t.on || 'On') : (t.off || 'Off')}</span>
|
|
476
488
|
</div>
|
|
477
489
|
</div>
|
|
478
490
|
</div>
|
package/index.js
CHANGED
|
@@ -133,10 +133,11 @@ class MultipleSwitchPlatform {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// Create regular switches
|
|
136
|
+
const switchType = device.switchType || (switches[0] && switches[0].type) || 'outlet';
|
|
136
137
|
switches.forEach((sw, index) => {
|
|
137
138
|
const subtype = `switch_${index}`;
|
|
138
139
|
|
|
139
|
-
const ServiceClass = this.getServiceClass(
|
|
140
|
+
const ServiceClass = this.getServiceClass(switchType);
|
|
140
141
|
const service = accessory.addService(ServiceClass, sw.name, subtype);
|
|
141
142
|
|
|
142
143
|
this.setServiceName(service, sw.name);
|
package/logo.png
ADDED
|
Binary file
|
package/logo.svg
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#1a1a2e"/>
|
|
5
|
+
<stop offset="50%" style="stop-color:#16213e"/>
|
|
6
|
+
<stop offset="100%" style="stop-color:#0f3460"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<linearGradient id="glowGreen" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
9
|
+
<stop offset="0%" style="stop-color:#00ff88"/>
|
|
10
|
+
<stop offset="100%" style="stop-color:#00cc6a"/>
|
|
11
|
+
</linearGradient>
|
|
12
|
+
<linearGradient id="glowAmber" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
13
|
+
<stop offset="0%" style="stop-color:#ffb347"/>
|
|
14
|
+
<stop offset="100%" style="stop-color:#ff8c00"/>
|
|
15
|
+
</linearGradient>
|
|
16
|
+
<linearGradient id="glowBlue" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
17
|
+
<stop offset="0%" style="stop-color:#00d4ff"/>
|
|
18
|
+
<stop offset="100%" style="stop-color:#0099cc"/>
|
|
19
|
+
</linearGradient>
|
|
20
|
+
<linearGradient id="ringGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
21
|
+
<stop offset="0%" style="stop-color:rgba(255,255,255,0.15)"/>
|
|
22
|
+
<stop offset="100%" style="stop-color:rgba(255,255,255,0.03)"/>
|
|
23
|
+
</linearGradient>
|
|
24
|
+
<filter id="glowFilter1">
|
|
25
|
+
<feGaussianBlur stdDeviation="12" result="blur"/>
|
|
26
|
+
<feMerge>
|
|
27
|
+
<feMergeNode in="blur"/>
|
|
28
|
+
<feMergeNode in="SourceGraphic"/>
|
|
29
|
+
</feMerge>
|
|
30
|
+
</filter>
|
|
31
|
+
<filter id="glowFilter2">
|
|
32
|
+
<feGaussianBlur stdDeviation="8" result="blur"/>
|
|
33
|
+
<feMerge>
|
|
34
|
+
<feMergeNode in="blur"/>
|
|
35
|
+
<feMergeNode in="SourceGraphic"/>
|
|
36
|
+
</feMerge>
|
|
37
|
+
</filter>
|
|
38
|
+
<filter id="softShadow">
|
|
39
|
+
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="#000" flood-opacity="0.4"/>
|
|
40
|
+
</filter>
|
|
41
|
+
</defs>
|
|
42
|
+
|
|
43
|
+
<!-- Background -->
|
|
44
|
+
<rect width="512" height="512" rx="112" fill="url(#bg)"/>
|
|
45
|
+
|
|
46
|
+
<!-- Subtle grid pattern -->
|
|
47
|
+
<line x1="256" y1="80" x2="256" y2="432" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
|
|
48
|
+
<line x1="80" y1="256" x2="432" y2="256" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
|
|
49
|
+
|
|
50
|
+
<!-- Central hub ring -->
|
|
51
|
+
<circle cx="256" cy="256" r="155" fill="none" stroke="url(#ringGrad)" stroke-width="2"/>
|
|
52
|
+
<circle cx="256" cy="256" r="90" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="1.5"/>
|
|
53
|
+
|
|
54
|
+
<!-- Connection lines from center to switches -->
|
|
55
|
+
<line x1="256" y1="256" x2="160" y2="160" stroke="rgba(0,255,136,0.25)" stroke-width="2" stroke-dasharray="4,4"/>
|
|
56
|
+
<line x1="256" y1="256" x2="352" y2="160" stroke="rgba(0,212,255,0.25)" stroke-width="2" stroke-dasharray="4,4"/>
|
|
57
|
+
<line x1="256" y1="256" x2="160" y2="352" stroke="rgba(255,179,71,0.25)" stroke-width="2" stroke-dasharray="4,4"/>
|
|
58
|
+
<line x1="256" y1="256" x2="352" y2="352" stroke="rgba(255,255,255,0.1)" stroke-width="2" stroke-dasharray="4,4"/>
|
|
59
|
+
|
|
60
|
+
<!-- Switch 1 — Top Left — Green (ON) -->
|
|
61
|
+
<g filter="url(#glowFilter1)">
|
|
62
|
+
<circle cx="160" cy="160" r="8" fill="#00ff88" opacity="0.3"/>
|
|
63
|
+
</g>
|
|
64
|
+
<circle cx="160" cy="160" r="38" fill="rgba(0,255,136,0.08)" stroke="rgba(0,255,136,0.4)" stroke-width="2"/>
|
|
65
|
+
<circle cx="160" cy="160" r="22" fill="rgba(0,255,136,0.12)" stroke="rgba(0,255,136,0.6)" stroke-width="1.5"/>
|
|
66
|
+
<!-- Power icon -->
|
|
67
|
+
<line x1="160" y1="146" x2="160" y2="155" stroke="#00ff88" stroke-width="3" stroke-linecap="round"/>
|
|
68
|
+
<path d="M 149 153 A 14 14 0 1 0 171 153" fill="none" stroke="#00ff88" stroke-width="2.5" stroke-linecap="round"/>
|
|
69
|
+
|
|
70
|
+
<!-- Switch 2 — Top Right — Blue (ON) -->
|
|
71
|
+
<g filter="url(#glowFilter1)">
|
|
72
|
+
<circle cx="352" cy="160" r="8" fill="#00d4ff" opacity="0.3"/>
|
|
73
|
+
</g>
|
|
74
|
+
<circle cx="352" cy="160" r="38" fill="rgba(0,212,255,0.08)" stroke="rgba(0,212,255,0.4)" stroke-width="2"/>
|
|
75
|
+
<circle cx="352" cy="160" r="22" fill="rgba(0,212,255,0.12)" stroke="rgba(0,212,255,0.6)" stroke-width="1.5"/>
|
|
76
|
+
<!-- Power icon -->
|
|
77
|
+
<line x1="352" y1="146" x2="352" y2="155" stroke="#00d4ff" stroke-width="3" stroke-linecap="round"/>
|
|
78
|
+
<path d="M 341 153 A 14 14 0 1 0 363 153" fill="none" stroke="#00d4ff" stroke-width="2.5" stroke-linecap="round"/>
|
|
79
|
+
|
|
80
|
+
<!-- Switch 3 — Bottom Left — Amber (ON) -->
|
|
81
|
+
<g filter="url(#glowFilter1)">
|
|
82
|
+
<circle cx="160" cy="352" r="8" fill="#ffb347" opacity="0.3"/>
|
|
83
|
+
</g>
|
|
84
|
+
<circle cx="160" cy="352" r="38" fill="rgba(255,179,71,0.08)" stroke="rgba(255,179,71,0.4)" stroke-width="2"/>
|
|
85
|
+
<circle cx="160" cy="352" r="22" fill="rgba(255,179,71,0.12)" stroke="rgba(255,179,71,0.6)" stroke-width="1.5"/>
|
|
86
|
+
<!-- Power icon -->
|
|
87
|
+
<line x1="160" y1="338" x2="160" y2="347" stroke="#ffb347" stroke-width="3" stroke-linecap="round"/>
|
|
88
|
+
<path d="M 149 345 A 14 14 0 1 0 171 345" fill="none" stroke="#ffb347" stroke-width="2.5" stroke-linecap="round"/>
|
|
89
|
+
|
|
90
|
+
<!-- Switch 4 — Bottom Right — OFF -->
|
|
91
|
+
<circle cx="352" cy="352" r="38" fill="rgba(255,255,255,0.03)" stroke="rgba(255,255,255,0.12)" stroke-width="2"/>
|
|
92
|
+
<circle cx="352" cy="352" r="22" fill="rgba(255,255,255,0.04)" stroke="rgba(255,255,255,0.15)" stroke-width="1.5"/>
|
|
93
|
+
<!-- Power icon (dimmed) -->
|
|
94
|
+
<line x1="352" y1="338" x2="352" y2="347" stroke="rgba(255,255,255,0.2)" stroke-width="3" stroke-linecap="round"/>
|
|
95
|
+
<path d="M 341 345 A 14 14 0 1 0 363 345" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="2.5" stroke-linecap="round"/>
|
|
96
|
+
|
|
97
|
+
<!-- Center hub -->
|
|
98
|
+
<circle cx="256" cy="256" r="32" fill="rgba(255,255,255,0.06)" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
|
|
99
|
+
<circle cx="256" cy="256" r="18" fill="rgba(255,255,255,0.08)" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
|
|
100
|
+
<!-- HomeKit house icon in center -->
|
|
101
|
+
<path d="M 245 260 L 256 249 L 267 260 L 267 270 L 261 270 L 261 263 L 251 263 L 251 270 L 245 270 Z" fill="rgba(255,255,255,0.7)" filter="url(#softShadow)"/>
|
|
102
|
+
</svg>
|
package/package.json
CHANGED