homebridge-multiple-switch 1.4.1 → 1.5.1
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 +17 -0
- package/config.schema.json +53 -45
- package/homebridge-ui/public/i18n/ar.json +7 -1
- package/homebridge-ui/public/i18n/de.json +7 -1
- package/homebridge-ui/public/i18n/en.json +7 -1
- package/homebridge-ui/public/i18n/es.json +7 -1
- package/homebridge-ui/public/i18n/fr.json +7 -1
- package/homebridge-ui/public/i18n/it.json +7 -1
- package/homebridge-ui/public/i18n/ja.json +7 -1
- package/homebridge-ui/public/i18n/ko.json +7 -1
- package/homebridge-ui/public/i18n/nl.json +7 -1
- package/homebridge-ui/public/i18n/pl.json +7 -1
- package/homebridge-ui/public/i18n/pt.json +7 -1
- package/homebridge-ui/public/i18n/ru.json +7 -1
- package/homebridge-ui/public/i18n/tr.json +7 -1
- package/homebridge-ui/public/i18n/zh-CN.json +7 -1
- package/homebridge-ui/public/index.html +271 -61
- package/index.js +51 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.5.1] - 2026-03-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Collapsible device cards — click the device header to expand/collapse
|
|
7
|
+
- Collapsible switch cards — click the switch header to expand/collapse
|
|
8
|
+
- Summary info shown when collapsed (switch count for devices, type/delay for switches)
|
|
9
|
+
- Chevron indicator (▶) with rotation animation for open/closed state
|
|
10
|
+
|
|
11
|
+
## [1.5.0] - 2026-03-21
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- Multi-device support: create multiple separate HomeKit accessories, each with
|
|
15
|
+
its own name, switch behavior mode, and set of switches
|
|
16
|
+
- `devices` array in config — each device becomes a separate accessory in HomeKit
|
|
17
|
+
- Full backward compatibility: existing configs with `switches` at root level
|
|
18
|
+
continue to work and are auto-migrated to the new `devices` format in the UI
|
|
19
|
+
|
|
3
20
|
## [1.4.1] - 2026-03-21
|
|
4
21
|
|
|
5
22
|
### Fixed
|
package/config.schema.json
CHANGED
|
@@ -12,65 +12,73 @@
|
|
|
12
12
|
"type": "string",
|
|
13
13
|
"default": "Multiple Switch Platform"
|
|
14
14
|
},
|
|
15
|
-
"
|
|
16
|
-
"title": "
|
|
17
|
-
"description": "
|
|
18
|
-
"type": "string",
|
|
19
|
-
"default": "independent",
|
|
20
|
-
"oneOf": [
|
|
21
|
-
{ "title": "Independent", "enum": ["independent"] },
|
|
22
|
-
{ "title": "Master", "enum": ["master"] },
|
|
23
|
-
{ "title": "Single", "enum": ["single"] }
|
|
24
|
-
]
|
|
25
|
-
},
|
|
26
|
-
"switches": {
|
|
27
|
-
"title": "Switches",
|
|
28
|
-
"description": "List of virtual switches to create.",
|
|
15
|
+
"devices": {
|
|
16
|
+
"title": "Devices",
|
|
17
|
+
"description": "List of switch devices. Each device appears as a separate accessory in HomeKit.",
|
|
29
18
|
"type": "array",
|
|
30
19
|
"items": {
|
|
31
20
|
"type": "object",
|
|
32
21
|
"properties": {
|
|
33
22
|
"name": {
|
|
34
|
-
"title": "
|
|
35
|
-
"description": "Display name of
|
|
23
|
+
"title": "Device Name",
|
|
24
|
+
"description": "Display name of this device in HomeKit.",
|
|
36
25
|
"type": "string"
|
|
37
26
|
},
|
|
38
|
-
"
|
|
39
|
-
"title": "Switch
|
|
40
|
-
"description": "
|
|
27
|
+
"switchBehavior": {
|
|
28
|
+
"title": "Switch Behavior Mode",
|
|
29
|
+
"description": "Controls how switches interact with each other within this device.",
|
|
41
30
|
"type": "string",
|
|
42
|
-
"default": "
|
|
31
|
+
"default": "independent",
|
|
43
32
|
"oneOf": [
|
|
44
|
-
{ "title": "
|
|
45
|
-
{ "title": "
|
|
33
|
+
{ "title": "Independent", "enum": ["independent"] },
|
|
34
|
+
{ "title": "Master", "enum": ["master"] },
|
|
35
|
+
{ "title": "Single", "enum": ["single"] }
|
|
46
36
|
]
|
|
47
37
|
},
|
|
48
|
-
"
|
|
49
|
-
"title": "
|
|
50
|
-
"description": "
|
|
51
|
-
"type": "
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
38
|
+
"switches": {
|
|
39
|
+
"title": "Switches",
|
|
40
|
+
"description": "List of virtual switches in this device.",
|
|
41
|
+
"type": "array",
|
|
42
|
+
"items": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"name": {
|
|
46
|
+
"title": "Switch Name",
|
|
47
|
+
"description": "Display name of the switch in HomeKit.",
|
|
48
|
+
"type": "string"
|
|
49
|
+
},
|
|
50
|
+
"type": {
|
|
51
|
+
"title": "Switch Type",
|
|
52
|
+
"description": "The HomeKit accessory type for this switch.",
|
|
53
|
+
"type": "string",
|
|
54
|
+
"default": "outlet",
|
|
55
|
+
"oneOf": [
|
|
56
|
+
{ "title": "Switch", "enum": ["switch"] },
|
|
57
|
+
{ "title": "Outlet", "enum": ["outlet"] }
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"defaultState": {
|
|
61
|
+
"title": "Default State",
|
|
62
|
+
"description": "Initial power state when Homebridge starts.",
|
|
63
|
+
"type": "boolean",
|
|
64
|
+
"default": false
|
|
65
|
+
},
|
|
66
|
+
"delayOff": {
|
|
67
|
+
"title": "Auto Turn Off (ms)",
|
|
68
|
+
"description": "Automatically turn off after this many milliseconds. Set to 0 to disable.",
|
|
69
|
+
"type": "number",
|
|
70
|
+
"default": 0,
|
|
71
|
+
"minimum": 0
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"required": ["name", "type"]
|
|
75
|
+
}
|
|
60
76
|
}
|
|
61
77
|
},
|
|
62
|
-
"required": ["name", "
|
|
63
|
-
}
|
|
64
|
-
"default": [
|
|
65
|
-
{
|
|
66
|
-
"name": "Switch 1",
|
|
67
|
-
"type": "outlet",
|
|
68
|
-
"defaultState": false,
|
|
69
|
-
"delayOff": 0
|
|
70
|
-
}
|
|
71
|
-
]
|
|
78
|
+
"required": ["name", "switches"]
|
|
79
|
+
}
|
|
72
80
|
}
|
|
73
81
|
},
|
|
74
|
-
"required": ["name"
|
|
82
|
+
"required": ["name"]
|
|
75
83
|
}
|
|
76
84
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "إزالة",
|
|
24
24
|
"save": "حفظ",
|
|
25
25
|
"on": "تشغيل",
|
|
26
|
-
"off": "إيقاف"
|
|
26
|
+
"off": "إيقاف",
|
|
27
|
+
"devices": "الأجهزة",
|
|
28
|
+
"devicesDesc": "يظهر كل جهاز كملحق منفصل في HomeKit مع مفاتيحه الخاصة.",
|
|
29
|
+
"device": "جهاز",
|
|
30
|
+
"deviceName": "اسم الجهاز",
|
|
31
|
+
"addDevice": "إضافة جهاز",
|
|
32
|
+
"removeDevice": "إزالة الجهاز"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "Entfernen",
|
|
24
24
|
"save": "Speichern",
|
|
25
25
|
"on": "Ein",
|
|
26
|
-
"off": "Aus"
|
|
26
|
+
"off": "Aus",
|
|
27
|
+
"devices": "Geräte",
|
|
28
|
+
"devicesDesc": "Jedes Gerät erscheint als separates Zubehör in HomeKit mit eigenen Schaltern.",
|
|
29
|
+
"device": "Gerät",
|
|
30
|
+
"deviceName": "Gerätename",
|
|
31
|
+
"addDevice": "Gerät hinzufügen",
|
|
32
|
+
"removeDevice": "Gerät entfernen"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "Remove",
|
|
24
24
|
"save": "Save",
|
|
25
25
|
"on": "On",
|
|
26
|
-
"off": "Off"
|
|
26
|
+
"off": "Off",
|
|
27
|
+
"devices": "Devices",
|
|
28
|
+
"devicesDesc": "Each device appears as a separate accessory in HomeKit with its own switches.",
|
|
29
|
+
"device": "Device",
|
|
30
|
+
"deviceName": "Device Name",
|
|
31
|
+
"addDevice": "Add Device",
|
|
32
|
+
"removeDevice": "Remove Device"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "Eliminar",
|
|
24
24
|
"save": "Guardar",
|
|
25
25
|
"on": "Encendido",
|
|
26
|
-
"off": "Apagado"
|
|
26
|
+
"off": "Apagado",
|
|
27
|
+
"devices": "Dispositivos",
|
|
28
|
+
"devicesDesc": "Cada dispositivo aparece como un accesorio separado en HomeKit con sus propios interruptores.",
|
|
29
|
+
"device": "Dispositivo",
|
|
30
|
+
"deviceName": "Nombre del dispositivo",
|
|
31
|
+
"addDevice": "Agregar dispositivo",
|
|
32
|
+
"removeDevice": "Eliminar dispositivo"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "Supprimer",
|
|
24
24
|
"save": "Enregistrer",
|
|
25
25
|
"on": "Activé",
|
|
26
|
-
"off": "Désactivé"
|
|
26
|
+
"off": "Désactivé",
|
|
27
|
+
"devices": "Appareils",
|
|
28
|
+
"devicesDesc": "Chaque appareil apparaît comme un accessoire séparé dans HomeKit avec ses propres interrupteurs.",
|
|
29
|
+
"device": "Appareil",
|
|
30
|
+
"deviceName": "Nom de l'appareil",
|
|
31
|
+
"addDevice": "Ajouter un appareil",
|
|
32
|
+
"removeDevice": "Supprimer l'appareil"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "Rimuovi",
|
|
24
24
|
"save": "Salva",
|
|
25
25
|
"on": "Acceso",
|
|
26
|
-
"off": "Spento"
|
|
26
|
+
"off": "Spento",
|
|
27
|
+
"devices": "Dispositivi",
|
|
28
|
+
"devicesDesc": "Ogni dispositivo appare come un accessorio separato in HomeKit con i propri interruttori.",
|
|
29
|
+
"device": "Dispositivo",
|
|
30
|
+
"deviceName": "Nome del dispositivo",
|
|
31
|
+
"addDevice": "Aggiungi dispositivo",
|
|
32
|
+
"removeDevice": "Rimuovi dispositivo"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "削除",
|
|
24
24
|
"save": "保存",
|
|
25
25
|
"on": "オン",
|
|
26
|
-
"off": "オフ"
|
|
26
|
+
"off": "オフ",
|
|
27
|
+
"devices": "デバイス",
|
|
28
|
+
"devicesDesc": "各デバイスは HomeKit で独自のスイッチを持つ個別のアクセサリとして表示されます。",
|
|
29
|
+
"device": "デバイス",
|
|
30
|
+
"deviceName": "デバイス名",
|
|
31
|
+
"addDevice": "デバイスを追加",
|
|
32
|
+
"removeDevice": "デバイスを削除"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "삭제",
|
|
24
24
|
"save": "저장",
|
|
25
25
|
"on": "켜짐",
|
|
26
|
-
"off": "꺼짐"
|
|
26
|
+
"off": "꺼짐",
|
|
27
|
+
"devices": "장치",
|
|
28
|
+
"devicesDesc": "각 장치는 HomeKit에서 자체 스위치를 가진 별도의 액세서리로 표시됩니다.",
|
|
29
|
+
"device": "장치",
|
|
30
|
+
"deviceName": "장치 이름",
|
|
31
|
+
"addDevice": "장치 추가",
|
|
32
|
+
"removeDevice": "장치 삭제"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "Verwijderen",
|
|
24
24
|
"save": "Opslaan",
|
|
25
25
|
"on": "Aan",
|
|
26
|
-
"off": "Uit"
|
|
26
|
+
"off": "Uit",
|
|
27
|
+
"devices": "Apparaten",
|
|
28
|
+
"devicesDesc": "Elk apparaat verschijnt als een apart accessoire in HomeKit met zijn eigen schakelaars.",
|
|
29
|
+
"device": "Apparaat",
|
|
30
|
+
"deviceName": "Apparaatnaam",
|
|
31
|
+
"addDevice": "Apparaat toevoegen",
|
|
32
|
+
"removeDevice": "Apparaat verwijderen"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "Usuń",
|
|
24
24
|
"save": "Zapisz",
|
|
25
25
|
"on": "Wł",
|
|
26
|
-
"off": "Wył"
|
|
26
|
+
"off": "Wył",
|
|
27
|
+
"devices": "Urządzenia",
|
|
28
|
+
"devicesDesc": "Każde urządzenie pojawia się jako osobne akcesorium w HomeKit z własnymi przełącznikami.",
|
|
29
|
+
"device": "Urządzenie",
|
|
30
|
+
"deviceName": "Nazwa urządzenia",
|
|
31
|
+
"addDevice": "Dodaj urządzenie",
|
|
32
|
+
"removeDevice": "Usuń urządzenie"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "Remover",
|
|
24
24
|
"save": "Salvar",
|
|
25
25
|
"on": "Ligado",
|
|
26
|
-
"off": "Desligado"
|
|
26
|
+
"off": "Desligado",
|
|
27
|
+
"devices": "Dispositivos",
|
|
28
|
+
"devicesDesc": "Cada dispositivo aparece como um acessório separado no HomeKit com seus próprios interruptores.",
|
|
29
|
+
"device": "Dispositivo",
|
|
30
|
+
"deviceName": "Nome do dispositivo",
|
|
31
|
+
"addDevice": "Adicionar dispositivo",
|
|
32
|
+
"removeDevice": "Remover dispositivo"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "Удалить",
|
|
24
24
|
"save": "Сохранить",
|
|
25
25
|
"on": "Вкл",
|
|
26
|
-
"off": "Выкл"
|
|
26
|
+
"off": "Выкл",
|
|
27
|
+
"devices": "Устройства",
|
|
28
|
+
"devicesDesc": "Каждое устройство отображается как отдельный аксессуар в HomeKit со своими переключателями.",
|
|
29
|
+
"device": "Устройство",
|
|
30
|
+
"deviceName": "Название устройства",
|
|
31
|
+
"addDevice": "Добавить устройство",
|
|
32
|
+
"removeDevice": "Удалить устройство"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "Kaldır",
|
|
24
24
|
"save": "Kaydet",
|
|
25
25
|
"on": "Açık",
|
|
26
|
-
"off": "Kapalı"
|
|
26
|
+
"off": "Kapalı",
|
|
27
|
+
"devices": "Cihazlar",
|
|
28
|
+
"devicesDesc": "Her cihaz HomeKit'te kendi anahtarlarıyla ayrı bir aksesuar olarak görünür.",
|
|
29
|
+
"device": "Cihaz",
|
|
30
|
+
"deviceName": "Cihaz Adı",
|
|
31
|
+
"addDevice": "Cihaz Ekle",
|
|
32
|
+
"removeDevice": "Cihazı Kaldır"
|
|
27
33
|
}
|
|
@@ -23,5 +23,11 @@
|
|
|
23
23
|
"removeSwitch": "删除",
|
|
24
24
|
"save": "保存",
|
|
25
25
|
"on": "开",
|
|
26
|
-
"off": "关"
|
|
26
|
+
"off": "关",
|
|
27
|
+
"devices": "设备",
|
|
28
|
+
"devicesDesc": "每个设备在 HomeKit 中显示为一个独立的配件,拥有自己的开关。",
|
|
29
|
+
"device": "设备",
|
|
30
|
+
"deviceName": "设备名称",
|
|
31
|
+
"addDevice": "添加设备",
|
|
32
|
+
"removeDevice": "删除设备"
|
|
27
33
|
}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
--ui-card-bg: #f8f9fa;
|
|
8
8
|
--ui-input-bg: #fff;
|
|
9
9
|
--ui-accent: #007bff;
|
|
10
|
+
--ui-device-bg: rgba(0,123,255,0.04);
|
|
11
|
+
--ui-device-border: rgba(0,123,255,0.2);
|
|
10
12
|
}
|
|
11
13
|
@media (prefers-color-scheme: dark) {
|
|
12
14
|
:root {
|
|
@@ -17,6 +19,8 @@
|
|
|
17
19
|
--ui-card-bg: rgba(255,255,255,0.06);
|
|
18
20
|
--ui-input-bg: rgba(255,255,255,0.08);
|
|
19
21
|
--ui-accent: #7b61ff;
|
|
22
|
+
--ui-device-bg: rgba(123,97,255,0.06);
|
|
23
|
+
--ui-device-border: rgba(123,97,255,0.25);
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -46,18 +50,60 @@
|
|
|
46
50
|
border-color: var(--ui-accent);
|
|
47
51
|
outline: none;
|
|
48
52
|
}
|
|
53
|
+
.device-card {
|
|
54
|
+
border: 2px solid var(--ui-device-border);
|
|
55
|
+
border-radius: 10px; margin-bottom: 20px;
|
|
56
|
+
background: var(--ui-device-bg);
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
}
|
|
59
|
+
.device-header {
|
|
60
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
61
|
+
padding: 14px 20px; cursor: pointer; user-select: none;
|
|
62
|
+
}
|
|
63
|
+
.device-header:hover { opacity: 0.85; }
|
|
64
|
+
.device-header-left {
|
|
65
|
+
display: flex; align-items: center; gap: 10px;
|
|
66
|
+
}
|
|
67
|
+
.device-header-left strong {
|
|
68
|
+
font-size: 16px; color: var(--ui-accent);
|
|
69
|
+
}
|
|
70
|
+
.device-header-left .chevron {
|
|
71
|
+
font-size: 12px; color: var(--ui-text-muted);
|
|
72
|
+
transition: transform 0.2s;
|
|
73
|
+
display: inline-block;
|
|
74
|
+
}
|
|
75
|
+
.device-header-left .chevron.open { transform: rotate(90deg); }
|
|
76
|
+
.device-header-right { display: flex; align-items: center; gap: 8px; }
|
|
77
|
+
.device-body { padding: 0 20px 20px; }
|
|
78
|
+
.device-body.collapsed { display: none; }
|
|
49
79
|
.switch-card {
|
|
50
80
|
border: 1px solid var(--ui-border);
|
|
51
|
-
border-radius: 8px;
|
|
81
|
+
border-radius: 8px; margin-bottom: 12px;
|
|
52
82
|
background: var(--ui-card-bg);
|
|
53
|
-
position: relative;
|
|
83
|
+
position: relative; overflow: hidden;
|
|
54
84
|
}
|
|
55
|
-
.switch-
|
|
56
|
-
display: flex; justify-content: space-between; align-items: center;
|
|
85
|
+
.switch-header {
|
|
86
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
87
|
+
padding: 12px 16px; cursor: pointer; user-select: none;
|
|
57
88
|
}
|
|
58
|
-
.switch-
|
|
59
|
-
|
|
60
|
-
|
|
89
|
+
.switch-header:hover { opacity: 0.85; }
|
|
90
|
+
.switch-header-left {
|
|
91
|
+
display: flex; align-items: center; gap: 8px;
|
|
92
|
+
}
|
|
93
|
+
.switch-header-left strong {
|
|
94
|
+
font-size: 14px; color: var(--ui-text);
|
|
95
|
+
}
|
|
96
|
+
.switch-header-left .chevron {
|
|
97
|
+
font-size: 11px; color: var(--ui-text-muted);
|
|
98
|
+
transition: transform 0.2s;
|
|
99
|
+
display: inline-block;
|
|
100
|
+
}
|
|
101
|
+
.switch-header-left .chevron.open { transform: rotate(90deg); }
|
|
102
|
+
.switch-header-right { display: flex; align-items: center; gap: 8px; }
|
|
103
|
+
.switch-body { padding: 0 16px 16px; }
|
|
104
|
+
.switch-body.collapsed { display: none; }
|
|
105
|
+
.switch-summary {
|
|
106
|
+
font-size: 12px; color: var(--ui-text-muted); margin-left: 4px;
|
|
61
107
|
}
|
|
62
108
|
.btn {
|
|
63
109
|
padding: 8px 16px; border: none; border-radius: 6px;
|
|
@@ -65,8 +111,14 @@
|
|
|
65
111
|
}
|
|
66
112
|
.btn-danger { background: #dc3545; color: #fff; }
|
|
67
113
|
.btn-danger:hover { background: #c82333; }
|
|
114
|
+
.btn-sm { padding: 4px 10px; font-size: 12px; }
|
|
68
115
|
.btn-primary { background: var(--ui-accent); color: #fff; }
|
|
69
116
|
.btn-primary:hover { opacity: 0.85; }
|
|
117
|
+
.btn-outline {
|
|
118
|
+
background: transparent; color: var(--ui-accent);
|
|
119
|
+
border: 1px solid var(--ui-accent);
|
|
120
|
+
}
|
|
121
|
+
.btn-outline:hover { background: var(--ui-accent); color: #fff; }
|
|
70
122
|
.actions { margin-top: 16px; display: flex; gap: 10px; }
|
|
71
123
|
.toggle-wrap { display: flex; align-items: center; gap: 8px; }
|
|
72
124
|
.toggle-wrap input[type="checkbox"] { width: 18px; height: 18px; accent-color: var(--ui-accent); }
|
|
@@ -113,10 +165,33 @@
|
|
|
113
165
|
config = configs[0] || {};
|
|
114
166
|
} catch {}
|
|
115
167
|
|
|
168
|
+
// Migrate old format (switches at root) to devices array
|
|
169
|
+
if (Array.isArray(config.switches) && config.switches.length > 0 && !config.devices) {
|
|
170
|
+
config.devices = [{
|
|
171
|
+
name: config.name || 'Multiple Switch Panel',
|
|
172
|
+
switchBehavior: config.switchBehavior || 'independent',
|
|
173
|
+
switches: config.switches,
|
|
174
|
+
}];
|
|
175
|
+
delete config.switches;
|
|
176
|
+
delete config.switchBehavior;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!config.devices) {
|
|
180
|
+
config.devices = [];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Track expand/collapse state
|
|
184
|
+
const expandedDevices = new Set(config.devices.map((_, i) => i));
|
|
185
|
+
const expandedSwitches = new Set();
|
|
186
|
+
config.devices.forEach((dev, di) => {
|
|
187
|
+
(dev.switches || []).forEach((_, si) => {
|
|
188
|
+
expandedSwitches.add(`${di}_${si}`);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
116
192
|
const app = document.getElementById('app');
|
|
117
193
|
|
|
118
194
|
function render() {
|
|
119
|
-
const switches = config.switches || [];
|
|
120
195
|
app.innerHTML = `
|
|
121
196
|
<div class="form-group">
|
|
122
197
|
<label>${t.platformName || 'Platform Name'}</label>
|
|
@@ -124,25 +199,15 @@
|
|
|
124
199
|
<input type="text" id="cfg-name" value="${esc(config.name || 'Multiple Switch Platform')}">
|
|
125
200
|
</div>
|
|
126
201
|
|
|
127
|
-
<div class="
|
|
128
|
-
|
|
129
|
-
<div class="desc">${t.switchBehaviorDesc || ''}</div>
|
|
130
|
-
<select id="cfg-behavior">
|
|
131
|
-
<option value="independent" ${config.switchBehavior === 'independent' || !config.switchBehavior ? 'selected' : ''}>${t.behaviorIndependent || 'Independent'}</option>
|
|
132
|
-
<option value="master" ${config.switchBehavior === 'master' ? 'selected' : ''}>${t.behaviorMaster || 'Master'}</option>
|
|
133
|
-
<option value="single" ${config.switchBehavior === 'single' ? 'selected' : ''}>${t.behaviorSingle || 'Single'}</option>
|
|
134
|
-
</select>
|
|
135
|
-
</div>
|
|
202
|
+
<div class="section-title">${t.devices || 'Devices'}</div>
|
|
203
|
+
<div class="desc" style="margin-bottom:16px">${t.devicesDesc || ''}</div>
|
|
136
204
|
|
|
137
|
-
<div
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<div id="switches-list">
|
|
141
|
-
${switches.map((sw, i) => renderSwitch(sw, i)).join('')}
|
|
205
|
+
<div id="devices-list">
|
|
206
|
+
${config.devices.map((dev, di) => renderDevice(dev, di)).join('')}
|
|
142
207
|
</div>
|
|
143
208
|
|
|
144
209
|
<div class="actions">
|
|
145
|
-
<button class="btn btn-
|
|
210
|
+
<button class="btn btn-outline" id="btn-add-device">+ ${t.addDevice || 'Add Device'}</button>
|
|
146
211
|
</div>
|
|
147
212
|
`;
|
|
148
213
|
|
|
@@ -150,21 +215,90 @@
|
|
|
150
215
|
config.name = e.target.value;
|
|
151
216
|
save();
|
|
152
217
|
});
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
218
|
+
|
|
219
|
+
document.getElementById('btn-add-device').addEventListener('click', () => {
|
|
220
|
+
const di = config.devices.length;
|
|
221
|
+
config.devices.push({
|
|
222
|
+
name: '',
|
|
223
|
+
switchBehavior: 'independent',
|
|
224
|
+
switches: [{ name: '', type: 'outlet', defaultState: false, delayOff: 0 }],
|
|
225
|
+
});
|
|
226
|
+
expandedDevices.add(di);
|
|
227
|
+
expandedSwitches.add(`${di}_0`);
|
|
160
228
|
render();
|
|
161
229
|
save();
|
|
162
230
|
});
|
|
163
231
|
|
|
164
|
-
|
|
232
|
+
// Device toggle
|
|
233
|
+
document.querySelectorAll('.device-toggle').forEach(el => {
|
|
234
|
+
el.addEventListener('click', (e) => {
|
|
235
|
+
// Don't toggle if clicking a button inside header
|
|
236
|
+
if (e.target.closest('.btn')) return;
|
|
237
|
+
const di = parseInt(el.dataset.dev);
|
|
238
|
+
if (expandedDevices.has(di)) {
|
|
239
|
+
expandedDevices.delete(di);
|
|
240
|
+
} else {
|
|
241
|
+
expandedDevices.add(di);
|
|
242
|
+
}
|
|
243
|
+
render();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Switch toggle
|
|
248
|
+
document.querySelectorAll('.switch-toggle').forEach(el => {
|
|
249
|
+
el.addEventListener('click', (e) => {
|
|
250
|
+
if (e.target.closest('.btn')) return;
|
|
251
|
+
const key = `${el.dataset.dev}_${el.dataset.sw}`;
|
|
252
|
+
if (expandedSwitches.has(key)) {
|
|
253
|
+
expandedSwitches.delete(key);
|
|
254
|
+
} else {
|
|
255
|
+
expandedSwitches.add(key);
|
|
256
|
+
}
|
|
257
|
+
render();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Device-level events
|
|
262
|
+
document.querySelectorAll('.dev-field').forEach(input => {
|
|
263
|
+
input.addEventListener('change', () => {
|
|
264
|
+
const di = parseInt(input.dataset.dev);
|
|
265
|
+
const field = input.dataset.field;
|
|
266
|
+
config.devices[di][field] = input.value;
|
|
267
|
+
save();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
document.querySelectorAll('.btn-remove-device').forEach(btn => {
|
|
272
|
+
btn.addEventListener('click', (e) => {
|
|
273
|
+
e.stopPropagation();
|
|
274
|
+
const di = parseInt(btn.dataset.dev);
|
|
275
|
+
config.devices.splice(di, 1);
|
|
276
|
+
// Rebuild expand state
|
|
277
|
+
rebuildExpandState();
|
|
278
|
+
render();
|
|
279
|
+
save();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Switch-level events
|
|
284
|
+
document.querySelectorAll('.btn-add-switch').forEach(btn => {
|
|
165
285
|
btn.addEventListener('click', () => {
|
|
166
|
-
const
|
|
167
|
-
config.switches.
|
|
286
|
+
const di = parseInt(btn.dataset.dev);
|
|
287
|
+
const si = config.devices[di].switches.length;
|
|
288
|
+
config.devices[di].switches.push({ name: '', type: 'outlet', defaultState: false, delayOff: 0 });
|
|
289
|
+
expandedSwitches.add(`${di}_${si}`);
|
|
290
|
+
render();
|
|
291
|
+
save();
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
document.querySelectorAll('.btn-remove-switch').forEach(btn => {
|
|
296
|
+
btn.addEventListener('click', (e) => {
|
|
297
|
+
e.stopPropagation();
|
|
298
|
+
const di = parseInt(btn.dataset.dev);
|
|
299
|
+
const si = parseInt(btn.dataset.sw);
|
|
300
|
+
config.devices[di].switches.splice(si, 1);
|
|
301
|
+
rebuildExpandState();
|
|
168
302
|
render();
|
|
169
303
|
save();
|
|
170
304
|
});
|
|
@@ -172,50 +306,126 @@
|
|
|
172
306
|
|
|
173
307
|
document.querySelectorAll('.sw-field').forEach(input => {
|
|
174
308
|
input.addEventListener('change', () => {
|
|
175
|
-
const
|
|
309
|
+
const di = parseInt(input.dataset.dev);
|
|
310
|
+
const si = parseInt(input.dataset.sw);
|
|
176
311
|
const field = input.dataset.field;
|
|
177
312
|
if (field === 'defaultState') {
|
|
178
|
-
config.switches[
|
|
313
|
+
config.devices[di].switches[si][field] = input.checked;
|
|
179
314
|
} else if (field === 'delayOff') {
|
|
180
|
-
config.switches[
|
|
315
|
+
config.devices[di].switches[si][field] = parseInt(input.value) || 0;
|
|
181
316
|
} else {
|
|
182
|
-
config.switches[
|
|
317
|
+
config.devices[di].switches[si][field] = input.value;
|
|
183
318
|
}
|
|
184
319
|
save();
|
|
185
320
|
});
|
|
186
321
|
});
|
|
187
322
|
}
|
|
188
323
|
|
|
189
|
-
function
|
|
324
|
+
function rebuildExpandState() {
|
|
325
|
+
// Keep devices that still exist expanded
|
|
326
|
+
const newDevices = new Set();
|
|
327
|
+
config.devices.forEach((_, i) => {
|
|
328
|
+
if (expandedDevices.has(i)) newDevices.add(i);
|
|
329
|
+
});
|
|
330
|
+
expandedDevices.clear();
|
|
331
|
+
newDevices.forEach(i => expandedDevices.add(i));
|
|
332
|
+
|
|
333
|
+
const newSwitches = new Set();
|
|
334
|
+
config.devices.forEach((dev, di) => {
|
|
335
|
+
(dev.switches || []).forEach((_, si) => {
|
|
336
|
+
if (expandedSwitches.has(`${di}_${si}`)) newSwitches.add(`${di}_${si}`);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
expandedSwitches.clear();
|
|
340
|
+
newSwitches.forEach(k => expandedSwitches.add(k));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function renderDevice(dev, di) {
|
|
344
|
+
const switches = dev.switches || [];
|
|
345
|
+
const isOpen = expandedDevices.has(di);
|
|
346
|
+
const devLabel = dev.name || `${t.device || 'Device'} #${di + 1}`;
|
|
347
|
+
const switchCount = switches.length;
|
|
348
|
+
|
|
190
349
|
return `
|
|
191
|
-
<div class="
|
|
192
|
-
<div class="
|
|
193
|
-
<
|
|
194
|
-
|
|
350
|
+
<div class="device-card">
|
|
351
|
+
<div class="device-header device-toggle" data-dev="${di}">
|
|
352
|
+
<div class="device-header-left">
|
|
353
|
+
<span class="chevron ${isOpen ? 'open' : ''}">▶</span>
|
|
354
|
+
<strong>${esc(devLabel)}</strong>
|
|
355
|
+
${!isOpen ? `<span class="switch-summary">(${switchCount} ${switchCount === 1 ? (t.switchSingular || 'switch') : (t.switches || 'switches').toLowerCase()})</span>` : ''}
|
|
356
|
+
</div>
|
|
357
|
+
<div class="device-header-right">
|
|
358
|
+
<button class="btn btn-danger btn-sm btn-remove-device" data-dev="${di}">${t.removeDevice || 'Remove Device'}</button>
|
|
359
|
+
</div>
|
|
195
360
|
</div>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
<
|
|
361
|
+
|
|
362
|
+
<div class="device-body ${isOpen ? '' : 'collapsed'}">
|
|
363
|
+
<div class="inline-row">
|
|
364
|
+
<div class="form-group">
|
|
365
|
+
<label>${t.deviceName || 'Device Name'}</label>
|
|
366
|
+
<input type="text" class="dev-field" data-dev="${di}" data-field="name" value="${esc(dev.name || '')}">
|
|
367
|
+
</div>
|
|
368
|
+
<div class="form-group">
|
|
369
|
+
<label>${t.switchBehavior || 'Switch Behavior Mode'}</label>
|
|
370
|
+
<select class="dev-field" data-dev="${di}" data-field="switchBehavior">
|
|
371
|
+
<option value="independent" ${dev.switchBehavior === 'independent' || !dev.switchBehavior ? 'selected' : ''}>${t.behaviorIndependent || 'Independent'}</option>
|
|
372
|
+
<option value="master" ${dev.switchBehavior === 'master' ? 'selected' : ''}>${t.behaviorMaster || 'Master'}</option>
|
|
373
|
+
<option value="single" ${dev.switchBehavior === 'single' ? 'selected' : ''}>${t.behaviorSingle || 'Single'}</option>
|
|
374
|
+
</select>
|
|
375
|
+
</div>
|
|
200
376
|
</div>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
377
|
+
|
|
378
|
+
<div class="section-title">${t.switches || 'Switches'}</div>
|
|
379
|
+
|
|
380
|
+
${switches.map((sw, si) => renderSwitch(sw, di, si)).join('')}
|
|
381
|
+
|
|
382
|
+
<button class="btn btn-primary btn-add-switch" data-dev="${di}">+ ${t.addSwitch || 'Add Switch'}</button>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function renderSwitch(sw, di, si) {
|
|
389
|
+
const isOpen = expandedSwitches.has(`${di}_${si}`);
|
|
390
|
+
const swLabel = sw.name || `#${si + 1}`;
|
|
391
|
+
|
|
392
|
+
return `
|
|
393
|
+
<div class="switch-card">
|
|
394
|
+
<div class="switch-header switch-toggle" data-dev="${di}" data-sw="${si}">
|
|
395
|
+
<div class="switch-header-left">
|
|
396
|
+
<span class="chevron ${isOpen ? 'open' : ''}">▶</span>
|
|
397
|
+
<strong>${esc(swLabel)}</strong>
|
|
398
|
+
${!isOpen ? `<span class="switch-summary">${sw.type || 'outlet'}${sw.delayOff ? ` / ${sw.delayOff}ms` : ''}</span>` : ''}
|
|
399
|
+
</div>
|
|
400
|
+
<div class="switch-header-right">
|
|
401
|
+
<button class="btn btn-danger btn-sm btn-remove-switch" data-dev="${di}" data-sw="${si}">${t.removeSwitch || 'Remove'}</button>
|
|
207
402
|
</div>
|
|
208
403
|
</div>
|
|
209
|
-
<div class="
|
|
210
|
-
<div class="
|
|
211
|
-
<
|
|
212
|
-
|
|
404
|
+
<div class="switch-body ${isOpen ? '' : 'collapsed'}">
|
|
405
|
+
<div class="inline-row">
|
|
406
|
+
<div class="form-group">
|
|
407
|
+
<label>${t.switchName || 'Switch Name'}</label>
|
|
408
|
+
<input type="text" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="name" value="${esc(sw.name || '')}">
|
|
409
|
+
</div>
|
|
410
|
+
<div class="form-group">
|
|
411
|
+
<label>${t.switchType || 'Switch Type'}</label>
|
|
412
|
+
<select class="sw-field" data-dev="${di}" data-sw="${si}" data-field="type">
|
|
413
|
+
<option value="switch" ${sw.type === 'switch' ? 'selected' : ''}>${t.typeSwitch || 'Switch'}</option>
|
|
414
|
+
<option value="outlet" ${sw.type === 'outlet' || !sw.type ? 'selected' : ''}>${t.typeOutlet || 'Outlet'}</option>
|
|
415
|
+
</select>
|
|
416
|
+
</div>
|
|
213
417
|
</div>
|
|
214
|
-
<div class="
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
<input type="
|
|
218
|
-
|
|
418
|
+
<div class="inline-row">
|
|
419
|
+
<div class="form-group">
|
|
420
|
+
<label>${t.delayOff || 'Auto Turn Off (ms)'}</label>
|
|
421
|
+
<input type="number" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="delayOff" min="0" value="${sw.delayOff || 0}">
|
|
422
|
+
</div>
|
|
423
|
+
<div class="form-group">
|
|
424
|
+
<label>${t.defaultState || 'Default State'}</label>
|
|
425
|
+
<div class="toggle-wrap" style="margin-top:6px">
|
|
426
|
+
<input type="checkbox" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="defaultState" ${sw.defaultState ? 'checked' : ''}>
|
|
427
|
+
<span>${sw.defaultState ? (t.on || 'On') : (t.off || 'Off')}</span>
|
|
428
|
+
</div>
|
|
219
429
|
</div>
|
|
220
430
|
</div>
|
|
221
431
|
</div>
|
package/index.js
CHANGED
|
@@ -20,7 +20,7 @@ class MultipleSwitchPlatform {
|
|
|
20
20
|
this.Service = api.hap.Service;
|
|
21
21
|
this.Characteristic = api.hap.Characteristic;
|
|
22
22
|
this.cachedAccessories = new Map();
|
|
23
|
-
this.
|
|
23
|
+
this.deviceServices = new Map();
|
|
24
24
|
|
|
25
25
|
this.api.on('didFinishLaunching', () => {
|
|
26
26
|
this.log.info('MultipleSwitchPlatform started.');
|
|
@@ -32,16 +32,47 @@ class MultipleSwitchPlatform {
|
|
|
32
32
|
this.cachedAccessories.set(accessory.UUID, accessory);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
getDevices() {
|
|
36
|
+
if (Array.isArray(this.config.devices) && this.config.devices.length > 0) {
|
|
37
|
+
return this.config.devices;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (Array.isArray(this.config.switches) && this.config.switches.length > 0) {
|
|
41
|
+
return [{
|
|
42
|
+
name: this.config.name || 'Multiple Switch Panel',
|
|
43
|
+
switchBehavior: this.config.switchBehavior || 'independent',
|
|
44
|
+
switches: this.config.switches,
|
|
45
|
+
}];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
setupAccessories() {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
const devices = this.getDevices();
|
|
53
|
+
|
|
54
|
+
if (devices.length === 0) {
|
|
55
|
+
this.log.warn('No devices configured. Removing stale accessories.');
|
|
39
56
|
this.removeStaleCachedAccessories();
|
|
40
57
|
return;
|
|
41
58
|
}
|
|
42
59
|
|
|
43
|
-
const
|
|
44
|
-
|
|
60
|
+
for (const device of devices) {
|
|
61
|
+
this.setupDevice(device);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.removeStaleCachedAccessories();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setupDevice(device) {
|
|
68
|
+
const switches = device.switches;
|
|
69
|
+
if (!Array.isArray(switches) || switches.length === 0) {
|
|
70
|
+
this.log.warn(`Device "${device.name}" has no switches, skipping.`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const name = device.name || 'Multiple Switch Panel';
|
|
75
|
+
const behavior = device.switchBehavior || 'independent';
|
|
45
76
|
const uuid = this.api.hap.uuid.generate(name);
|
|
46
77
|
|
|
47
78
|
let accessory = this.cachedAccessories.get(uuid);
|
|
@@ -54,17 +85,19 @@ class MultipleSwitchPlatform {
|
|
|
54
85
|
accessory.context.switchBehavior = behavior;
|
|
55
86
|
accessory.context.switchStates = accessory.context.switchStates || {};
|
|
56
87
|
|
|
57
|
-
|
|
88
|
+
const services = new Map();
|
|
89
|
+
this.deviceServices.set(uuid, services);
|
|
90
|
+
|
|
91
|
+
this.reconcileServices(accessory, switches, services);
|
|
58
92
|
|
|
59
93
|
if (isNew) {
|
|
60
94
|
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
61
95
|
}
|
|
62
96
|
|
|
63
97
|
this.cachedAccessories.delete(uuid);
|
|
64
|
-
this.removeStaleCachedAccessories();
|
|
65
98
|
}
|
|
66
99
|
|
|
67
|
-
reconcileServices(accessory, switches) {
|
|
100
|
+
reconcileServices(accessory, switches, services) {
|
|
68
101
|
const activeSubtypes = new Set();
|
|
69
102
|
|
|
70
103
|
switches.forEach((sw, index) => {
|
|
@@ -79,9 +112,9 @@ class MultipleSwitchPlatform {
|
|
|
79
112
|
}
|
|
80
113
|
|
|
81
114
|
service.setCharacteristic(this.Characteristic.Name, sw.name);
|
|
82
|
-
this.configureSwitchHandlers(accessory, service, sw, subtype);
|
|
115
|
+
this.configureSwitchHandlers(accessory, service, sw, subtype, services);
|
|
83
116
|
|
|
84
|
-
|
|
117
|
+
services.set(subtype, service);
|
|
85
118
|
|
|
86
119
|
if (accessory.context.switchStates[subtype] === undefined) {
|
|
87
120
|
accessory.context.switchStates[subtype] = sw.defaultState || false;
|
|
@@ -94,7 +127,7 @@ class MultipleSwitchPlatform {
|
|
|
94
127
|
servicesToRemove.forEach((s) => accessory.removeService(s));
|
|
95
128
|
}
|
|
96
129
|
|
|
97
|
-
configureSwitchHandlers(accessory, service, sw, subtype) {
|
|
130
|
+
configureSwitchHandlers(accessory, service, sw, subtype, services) {
|
|
98
131
|
service.getCharacteristic(this.Characteristic.On)
|
|
99
132
|
.onGet(() => accessory.context.switchStates[subtype] ?? false)
|
|
100
133
|
.onSet((value) => {
|
|
@@ -104,11 +137,11 @@ class MultipleSwitchPlatform {
|
|
|
104
137
|
const behavior = accessory.context.switchBehavior;
|
|
105
138
|
|
|
106
139
|
if (behavior === 'single' && value) {
|
|
107
|
-
this.turnOffOthers(accessory, subtype);
|
|
140
|
+
this.turnOffOthers(accessory, subtype, services);
|
|
108
141
|
}
|
|
109
142
|
|
|
110
143
|
if (behavior === 'master') {
|
|
111
|
-
this.setAll(accessory, value);
|
|
144
|
+
this.setAll(accessory, value, services);
|
|
112
145
|
}
|
|
113
146
|
|
|
114
147
|
if (value && sw.delayOff > 0) {
|
|
@@ -117,8 +150,8 @@ class MultipleSwitchPlatform {
|
|
|
117
150
|
});
|
|
118
151
|
}
|
|
119
152
|
|
|
120
|
-
turnOffOthers(accessory, excludeSubtype) {
|
|
121
|
-
for (const [key, svc] of
|
|
153
|
+
turnOffOthers(accessory, excludeSubtype, services) {
|
|
154
|
+
for (const [key, svc] of services) {
|
|
122
155
|
if (key !== excludeSubtype) {
|
|
123
156
|
accessory.context.switchStates[key] = false;
|
|
124
157
|
svc.updateCharacteristic(this.Characteristic.On, false);
|
|
@@ -126,8 +159,8 @@ class MultipleSwitchPlatform {
|
|
|
126
159
|
}
|
|
127
160
|
}
|
|
128
161
|
|
|
129
|
-
setAll(accessory, value) {
|
|
130
|
-
for (const [key, svc] of
|
|
162
|
+
setAll(accessory, value, services) {
|
|
163
|
+
for (const [key, svc] of services) {
|
|
131
164
|
accessory.context.switchStates[key] = value;
|
|
132
165
|
svc.updateCharacteristic(this.Characteristic.On, value);
|
|
133
166
|
}
|
package/package.json
CHANGED