homebridge-multiple-switch 1.5.0 → 1.6.0-beta.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 +23 -0
- package/config.schema.json +6 -1
- package/homebridge-ui/public/i18n/ar.json +6 -2
- package/homebridge-ui/public/i18n/de.json +6 -2
- package/homebridge-ui/public/i18n/en.json +6 -2
- package/homebridge-ui/public/i18n/es.json +6 -2
- package/homebridge-ui/public/i18n/fr.json +6 -2
- package/homebridge-ui/public/i18n/it.json +6 -2
- package/homebridge-ui/public/i18n/ja.json +6 -2
- package/homebridge-ui/public/i18n/ko.json +6 -2
- package/homebridge-ui/public/i18n/nl.json +6 -2
- package/homebridge-ui/public/i18n/pl.json +6 -2
- package/homebridge-ui/public/i18n/pt.json +6 -2
- package/homebridge-ui/public/i18n/ru.json +6 -2
- package/homebridge-ui/public/i18n/tr.json +6 -2
- package/homebridge-ui/public/i18n/zh-CN.json +6 -2
- package/homebridge-ui/public/index.html +205 -55
- package/index.js +46 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.0-beta.1] - 2026-03-21
|
|
4
|
+
|
|
5
|
+
### Removed
|
|
6
|
+
- Master switch behavior mode — replaced by a more useful master switch option
|
|
7
|
+
within Single mode
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Switch behavior now only has two modes: Independent and Single
|
|
11
|
+
- Added descriptions to both behavior modes explaining how they work
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- Master Switch option (available in Single mode only): adds an extra switch
|
|
15
|
+
that turns all switches on or off at once
|
|
16
|
+
- New i18n keys for behavior descriptions, master switch label/description
|
|
17
|
+
|
|
18
|
+
## [1.5.1] - 2026-03-21
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Collapsible device cards — click the device header to expand/collapse
|
|
22
|
+
- Collapsible switch cards — click the switch header to expand/collapse
|
|
23
|
+
- Summary info shown when collapsed (switch count for devices, type/delay for switches)
|
|
24
|
+
- Chevron indicator (▶) with rotation animation for open/closed state
|
|
25
|
+
|
|
3
26
|
## [1.5.0] - 2026-03-21
|
|
4
27
|
|
|
5
28
|
### Added
|
package/config.schema.json
CHANGED
|
@@ -31,10 +31,15 @@
|
|
|
31
31
|
"default": "independent",
|
|
32
32
|
"oneOf": [
|
|
33
33
|
{ "title": "Independent", "enum": ["independent"] },
|
|
34
|
-
{ "title": "Master", "enum": ["master"] },
|
|
35
34
|
{ "title": "Single", "enum": ["single"] }
|
|
36
35
|
]
|
|
37
36
|
},
|
|
37
|
+
"masterSwitch": {
|
|
38
|
+
"title": "Master Switch",
|
|
39
|
+
"description": "Add a master switch that turns all switches on or off at once. Only available in Single mode.",
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"default": false
|
|
42
|
+
},
|
|
38
43
|
"switches": {
|
|
39
44
|
"title": "Switches",
|
|
40
45
|
"description": "List of virtual switches in this device.",
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "وضع سلوك المفتاح",
|
|
6
6
|
"switchBehaviorDesc": "يتحكم في كيفية تفاعل المفاتيح مع بعضها البعض.",
|
|
7
7
|
"behaviorIndependent": "مستقل",
|
|
8
|
-
"behaviorMaster": "رئيسي",
|
|
9
8
|
"behaviorSingle": "فردي",
|
|
10
9
|
"switches": "المفاتيح",
|
|
11
10
|
"switchesDesc": "قائمة المفاتيح الافتراضية المراد إنشاؤها.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "جهاز",
|
|
30
29
|
"deviceName": "اسم الجهاز",
|
|
31
30
|
"addDevice": "إضافة جهاز",
|
|
32
|
-
"removeDevice": "إزالة الجهاز"
|
|
31
|
+
"removeDevice": "إزالة الجهاز",
|
|
32
|
+
"behaviorIndependentDesc": "كل مفتاح يعمل بشكل مستقل. تشغيل أو إيقاف أحدها لا يؤثر على الآخرين.",
|
|
33
|
+
"behaviorSingleDesc": "يمكن تشغيل مفتاح واحد فقط في نفس الوقت. تشغيل واحد يقوم بإيقاف جميع الآخرين تلقائياً.",
|
|
34
|
+
"masterSwitch": "المفتاح الرئيسي",
|
|
35
|
+
"masterSwitchDesc": "يضيف مفتاحاً رئيسياً يقوم بتشغيل أو إيقاف جميع المفاتيح دفعة واحدة.",
|
|
36
|
+
"switchSingular": "مفتاح"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "Schaltverhalten",
|
|
6
6
|
"switchBehaviorDesc": "Steuert, wie die Schalter miteinander interagieren.",
|
|
7
7
|
"behaviorIndependent": "Unabhängig",
|
|
8
|
-
"behaviorMaster": "Master",
|
|
9
8
|
"behaviorSingle": "Einzeln",
|
|
10
9
|
"switches": "Schalter",
|
|
11
10
|
"switchesDesc": "Liste der zu erstellenden virtuellen Schalter.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "Gerät",
|
|
30
29
|
"deviceName": "Gerätename",
|
|
31
30
|
"addDevice": "Gerät hinzufügen",
|
|
32
|
-
"removeDevice": "Gerät entfernen"
|
|
31
|
+
"removeDevice": "Gerät entfernen",
|
|
32
|
+
"behaviorIndependentDesc": "Jeder Schalter arbeitet unabhängig. Das Ein- oder Ausschalten eines Schalters hat keinen Einfluss auf die anderen.",
|
|
33
|
+
"behaviorSingleDesc": "Es kann nur ein Schalter gleichzeitig eingeschaltet sein. Das Einschalten eines Schalters schaltet alle anderen automatisch aus.",
|
|
34
|
+
"masterSwitch": "Hauptschalter",
|
|
35
|
+
"masterSwitchDesc": "Fügt einen Hauptschalter hinzu, der alle Schalter auf einmal ein- oder ausschaltet.",
|
|
36
|
+
"switchSingular": "Schalter"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "Switch Behavior Mode",
|
|
6
6
|
"switchBehaviorDesc": "Controls how switches interact with each other.",
|
|
7
7
|
"behaviorIndependent": "Independent",
|
|
8
|
-
"behaviorMaster": "Master",
|
|
9
8
|
"behaviorSingle": "Single",
|
|
10
9
|
"switches": "Switches",
|
|
11
10
|
"switchesDesc": "List of virtual switches to create.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "Device",
|
|
30
29
|
"deviceName": "Device Name",
|
|
31
30
|
"addDevice": "Add Device",
|
|
32
|
-
"removeDevice": "Remove Device"
|
|
31
|
+
"removeDevice": "Remove Device",
|
|
32
|
+
"behaviorIndependentDesc": "Each switch works independently. Turning one on or off has no effect on the others.",
|
|
33
|
+
"behaviorSingleDesc": "Only one switch can be on at a time. Turning one on automatically turns off all others.",
|
|
34
|
+
"masterSwitch": "Master Switch",
|
|
35
|
+
"masterSwitchDesc": "Adds a master switch that turns all switches on or off at once.",
|
|
36
|
+
"switchSingular": "switch"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "Modo de comportamiento",
|
|
6
6
|
"switchBehaviorDesc": "Controla cómo interactúan los interruptores entre sí.",
|
|
7
7
|
"behaviorIndependent": "Independiente",
|
|
8
|
-
"behaviorMaster": "Maestro",
|
|
9
8
|
"behaviorSingle": "Único",
|
|
10
9
|
"switches": "Interruptores",
|
|
11
10
|
"switchesDesc": "Lista de interruptores virtuales a crear.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "Dispositivo",
|
|
30
29
|
"deviceName": "Nombre del dispositivo",
|
|
31
30
|
"addDevice": "Agregar dispositivo",
|
|
32
|
-
"removeDevice": "Eliminar dispositivo"
|
|
31
|
+
"removeDevice": "Eliminar dispositivo",
|
|
32
|
+
"behaviorIndependentDesc": "Cada interruptor funciona de forma independiente. Encender o apagar uno no afecta a los demás.",
|
|
33
|
+
"behaviorSingleDesc": "Solo un interruptor puede estar encendido a la vez. Encender uno apaga automáticamente todos los demás.",
|
|
34
|
+
"masterSwitch": "Interruptor maestro",
|
|
35
|
+
"masterSwitchDesc": "Agrega un interruptor maestro que enciende o apaga todos los interruptores a la vez.",
|
|
36
|
+
"switchSingular": "interruptor"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "Mode de comportement",
|
|
6
6
|
"switchBehaviorDesc": "Contrôle la façon dont les interrupteurs interagissent entre eux.",
|
|
7
7
|
"behaviorIndependent": "Indépendant",
|
|
8
|
-
"behaviorMaster": "Maître",
|
|
9
8
|
"behaviorSingle": "Unique",
|
|
10
9
|
"switches": "Interrupteurs",
|
|
11
10
|
"switchesDesc": "Liste des interrupteurs virtuels à créer.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "Appareil",
|
|
30
29
|
"deviceName": "Nom de l'appareil",
|
|
31
30
|
"addDevice": "Ajouter un appareil",
|
|
32
|
-
"removeDevice": "Supprimer l'appareil"
|
|
31
|
+
"removeDevice": "Supprimer l'appareil",
|
|
32
|
+
"behaviorIndependentDesc": "Chaque interrupteur fonctionne indépendamment. Activer ou désactiver l'un n'affecte pas les autres.",
|
|
33
|
+
"behaviorSingleDesc": "Un seul interrupteur peut être activé à la fois. En activer un désactive automatiquement tous les autres.",
|
|
34
|
+
"masterSwitch": "Interrupteur principal",
|
|
35
|
+
"masterSwitchDesc": "Ajoute un interrupteur principal qui active ou désactive tous les interrupteurs en une seule fois.",
|
|
36
|
+
"switchSingular": "interrupteur"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "Modalità di comportamento",
|
|
6
6
|
"switchBehaviorDesc": "Controlla come gli interruttori interagiscono tra loro.",
|
|
7
7
|
"behaviorIndependent": "Indipendente",
|
|
8
|
-
"behaviorMaster": "Master",
|
|
9
8
|
"behaviorSingle": "Singolo",
|
|
10
9
|
"switches": "Interruttori",
|
|
11
10
|
"switchesDesc": "Elenco degli interruttori virtuali da creare.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "Dispositivo",
|
|
30
29
|
"deviceName": "Nome del dispositivo",
|
|
31
30
|
"addDevice": "Aggiungi dispositivo",
|
|
32
|
-
"removeDevice": "Rimuovi dispositivo"
|
|
31
|
+
"removeDevice": "Rimuovi dispositivo",
|
|
32
|
+
"behaviorIndependentDesc": "Ogni interruttore funziona in modo indipendente. Accendere o spegnere uno non influisce sugli altri.",
|
|
33
|
+
"behaviorSingleDesc": "Solo un interruttore può essere acceso alla volta. Accenderne uno spegne automaticamente tutti gli altri.",
|
|
34
|
+
"masterSwitch": "Interruttore principale",
|
|
35
|
+
"masterSwitchDesc": "Aggiunge un interruttore principale che accende o spegne tutti gli interruttori contemporaneamente.",
|
|
36
|
+
"switchSingular": "interruttore"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "スイッチ動作モード",
|
|
6
6
|
"switchBehaviorDesc": "スイッチ同士の相互作用を制御します。",
|
|
7
7
|
"behaviorIndependent": "独立",
|
|
8
|
-
"behaviorMaster": "マスター",
|
|
9
8
|
"behaviorSingle": "シングル",
|
|
10
9
|
"switches": "スイッチ",
|
|
11
10
|
"switchesDesc": "作成する仮想スイッチのリスト。",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "デバイス",
|
|
30
29
|
"deviceName": "デバイス名",
|
|
31
30
|
"addDevice": "デバイスを追加",
|
|
32
|
-
"removeDevice": "デバイスを削除"
|
|
31
|
+
"removeDevice": "デバイスを削除",
|
|
32
|
+
"behaviorIndependentDesc": "各スイッチは独立して動作します。1つのオン/オフは他に影響しません。",
|
|
33
|
+
"behaviorSingleDesc": "同時に1つのスイッチのみオンにできます。1つをオンにすると他はすべて自動的にオフになります。",
|
|
34
|
+
"masterSwitch": "マスタースイッチ",
|
|
35
|
+
"masterSwitchDesc": "すべてのスイッチを一度にオン/オフするマスタースイッチを追加します。",
|
|
36
|
+
"switchSingular": "スイッチ"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "스위치 동작 모드",
|
|
6
6
|
"switchBehaviorDesc": "스위치 간의 상호 작용 방식을 제어합니다.",
|
|
7
7
|
"behaviorIndependent": "독립",
|
|
8
|
-
"behaviorMaster": "마스터",
|
|
9
8
|
"behaviorSingle": "단일",
|
|
10
9
|
"switches": "스위치",
|
|
11
10
|
"switchesDesc": "생성할 가상 스위치 목록.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "장치",
|
|
30
29
|
"deviceName": "장치 이름",
|
|
31
30
|
"addDevice": "장치 추가",
|
|
32
|
-
"removeDevice": "장치 삭제"
|
|
31
|
+
"removeDevice": "장치 삭제",
|
|
32
|
+
"behaviorIndependentDesc": "각 스위치는 독립적으로 작동합니다. 하나를 켜거나 끄는 것이 다른 것에 영향을 주지 않습니다.",
|
|
33
|
+
"behaviorSingleDesc": "한 번에 하나의 스위치만 켤 수 있습니다. 하나를 켜면 다른 모든 것이 자동으로 꺼집니다.",
|
|
34
|
+
"masterSwitch": "마스터 스위치",
|
|
35
|
+
"masterSwitchDesc": "모든 스위치를 한 번에 켜거나 끄는 마스터 스위치를 추가합니다.",
|
|
36
|
+
"switchSingular": "스위치"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "Schakelaargedrag",
|
|
6
6
|
"switchBehaviorDesc": "Bepaalt hoe de schakelaars met elkaar omgaan.",
|
|
7
7
|
"behaviorIndependent": "Onafhankelijk",
|
|
8
|
-
"behaviorMaster": "Master",
|
|
9
8
|
"behaviorSingle": "Enkelvoudig",
|
|
10
9
|
"switches": "Schakelaars",
|
|
11
10
|
"switchesDesc": "Lijst van virtuele schakelaars om aan te maken.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "Apparaat",
|
|
30
29
|
"deviceName": "Apparaatnaam",
|
|
31
30
|
"addDevice": "Apparaat toevoegen",
|
|
32
|
-
"removeDevice": "Apparaat verwijderen"
|
|
31
|
+
"removeDevice": "Apparaat verwijderen",
|
|
32
|
+
"behaviorIndependentDesc": "Elke schakelaar werkt onafhankelijk. Het in- of uitschakelen van één heeft geen invloed op de andere.",
|
|
33
|
+
"behaviorSingleDesc": "Er kan slechts één schakelaar tegelijk aan staan. Het inschakelen van één schakelaar schakelt alle andere automatisch uit.",
|
|
34
|
+
"masterSwitch": "Hoofdschakelaar",
|
|
35
|
+
"masterSwitchDesc": "Voegt een hoofdschakelaar toe die alle schakelaars in één keer in- of uitschakelt.",
|
|
36
|
+
"switchSingular": "schakelaar"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "Tryb zachowania przełączników",
|
|
6
6
|
"switchBehaviorDesc": "Kontroluje sposób interakcji przełączników ze sobą.",
|
|
7
7
|
"behaviorIndependent": "Niezależny",
|
|
8
|
-
"behaviorMaster": "Główny",
|
|
9
8
|
"behaviorSingle": "Pojedynczy",
|
|
10
9
|
"switches": "Przełączniki",
|
|
11
10
|
"switchesDesc": "Lista wirtualnych przełączników do utworzenia.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "Urządzenie",
|
|
30
29
|
"deviceName": "Nazwa urządzenia",
|
|
31
30
|
"addDevice": "Dodaj urządzenie",
|
|
32
|
-
"removeDevice": "Usuń urządzenie"
|
|
31
|
+
"removeDevice": "Usuń urządzenie",
|
|
32
|
+
"behaviorIndependentDesc": "Każdy przełącznik działa niezależnie. Włączenie lub wyłączenie jednego nie wpływa na pozostałe.",
|
|
33
|
+
"behaviorSingleDesc": "Tylko jeden przełącznik może być włączony jednocześnie. Włączenie jednego automatycznie wyłącza wszystkie pozostałe.",
|
|
34
|
+
"masterSwitch": "Przełącznik główny",
|
|
35
|
+
"masterSwitchDesc": "Dodaje przełącznik główny, który włącza lub wyłącza wszystkie przełączniki jednocześnie.",
|
|
36
|
+
"switchSingular": "przełącznik"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "Modo de comportamento",
|
|
6
6
|
"switchBehaviorDesc": "Controla como os interruptores interagem entre si.",
|
|
7
7
|
"behaviorIndependent": "Independente",
|
|
8
|
-
"behaviorMaster": "Mestre",
|
|
9
8
|
"behaviorSingle": "Único",
|
|
10
9
|
"switches": "Interruptores",
|
|
11
10
|
"switchesDesc": "Lista de interruptores virtuais a criar.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "Dispositivo",
|
|
30
29
|
"deviceName": "Nome do dispositivo",
|
|
31
30
|
"addDevice": "Adicionar dispositivo",
|
|
32
|
-
"removeDevice": "Remover dispositivo"
|
|
31
|
+
"removeDevice": "Remover dispositivo",
|
|
32
|
+
"behaviorIndependentDesc": "Cada interruptor funciona de forma independente. Ligar ou desligar um não afeta os outros.",
|
|
33
|
+
"behaviorSingleDesc": "Apenas um interruptor pode estar ligado por vez. Ligar um desliga automaticamente todos os outros.",
|
|
34
|
+
"masterSwitch": "Interruptor principal",
|
|
35
|
+
"masterSwitchDesc": "Adiciona um interruptor principal que liga ou desliga todos os interruptores de uma só vez.",
|
|
36
|
+
"switchSingular": "interruptor"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "Режим поведения",
|
|
6
6
|
"switchBehaviorDesc": "Управляет взаимодействием переключателей друг с другом.",
|
|
7
7
|
"behaviorIndependent": "Независимый",
|
|
8
|
-
"behaviorMaster": "Мастер",
|
|
9
8
|
"behaviorSingle": "Одиночный",
|
|
10
9
|
"switches": "Переключатели",
|
|
11
10
|
"switchesDesc": "Список виртуальных переключателей для создания.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "Устройство",
|
|
30
29
|
"deviceName": "Название устройства",
|
|
31
30
|
"addDevice": "Добавить устройство",
|
|
32
|
-
"removeDevice": "Удалить устройство"
|
|
31
|
+
"removeDevice": "Удалить устройство",
|
|
32
|
+
"behaviorIndependentDesc": "Каждый переключатель работает независимо. Включение или выключение одного не влияет на остальные.",
|
|
33
|
+
"behaviorSingleDesc": "Одновременно может быть включён только один переключатель. Включение одного автоматически выключает все остальные.",
|
|
34
|
+
"masterSwitch": "Главный переключатель",
|
|
35
|
+
"masterSwitchDesc": "Добавляет главный переключатель, который включает или выключает все переключатели одновременно.",
|
|
36
|
+
"switchSingular": "переключатель"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "Anahtar Davranış Modu",
|
|
6
6
|
"switchBehaviorDesc": "Anahtarların birbirleriyle nasıl etkileşime gireceğini kontrol eder.",
|
|
7
7
|
"behaviorIndependent": "Bağımsız",
|
|
8
|
-
"behaviorMaster": "Ana Kontrol",
|
|
9
8
|
"behaviorSingle": "Tekli",
|
|
10
9
|
"switches": "Anahtarlar",
|
|
11
10
|
"switchesDesc": "Oluşturulacak sanal anahtarların listesi.",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "Cihaz",
|
|
30
29
|
"deviceName": "Cihaz Adı",
|
|
31
30
|
"addDevice": "Cihaz Ekle",
|
|
32
|
-
"removeDevice": "Cihazı Kaldır"
|
|
31
|
+
"removeDevice": "Cihazı Kaldır",
|
|
32
|
+
"behaviorIndependentDesc": "Her anahtar bağımsız çalışır. Birini açmak veya kapatmak diğerlerini etkilemez.",
|
|
33
|
+
"behaviorSingleDesc": "Aynı anda yalnızca bir anahtar açık olabilir. Birini açmak diğerlerini otomatik kapatır.",
|
|
34
|
+
"masterSwitch": "Ana Anahtar",
|
|
35
|
+
"masterSwitchDesc": "Tüm anahtarları tek seferde açan veya kapatan bir ana anahtar ekler.",
|
|
36
|
+
"switchSingular": "anahtar"
|
|
33
37
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"switchBehavior": "开关行为模式",
|
|
6
6
|
"switchBehaviorDesc": "控制开关之间的交互方式。",
|
|
7
7
|
"behaviorIndependent": "独立",
|
|
8
|
-
"behaviorMaster": "主控",
|
|
9
8
|
"behaviorSingle": "单选",
|
|
10
9
|
"switches": "开关",
|
|
11
10
|
"switchesDesc": "要创建的虚拟开关列表。",
|
|
@@ -29,5 +28,10 @@
|
|
|
29
28
|
"device": "设备",
|
|
30
29
|
"deviceName": "设备名称",
|
|
31
30
|
"addDevice": "添加设备",
|
|
32
|
-
"removeDevice": "删除设备"
|
|
31
|
+
"removeDevice": "删除设备",
|
|
32
|
+
"behaviorIndependentDesc": "每个开关独立工作。打开或关闭一个不会影响其他开关。",
|
|
33
|
+
"behaviorSingleDesc": "同一时间只能有一个开关处于打开状态。打开一个会自动关闭所有其他开关。",
|
|
34
|
+
"masterSwitch": "主开关",
|
|
35
|
+
"masterSwitchDesc": "添加一个主开关,可以一次性打开或关闭所有开关。",
|
|
36
|
+
"switchSingular": "开关"
|
|
33
37
|
}
|
|
@@ -52,29 +52,58 @@
|
|
|
52
52
|
}
|
|
53
53
|
.device-card {
|
|
54
54
|
border: 2px solid var(--ui-device-border);
|
|
55
|
-
border-radius: 10px;
|
|
55
|
+
border-radius: 10px; margin-bottom: 20px;
|
|
56
56
|
background: var(--ui-device-bg);
|
|
57
|
+
overflow: hidden;
|
|
57
58
|
}
|
|
58
59
|
.device-header {
|
|
59
60
|
display: flex; justify-content: space-between; align-items: center;
|
|
60
|
-
|
|
61
|
-
border-bottom: 1px solid var(--ui-border);
|
|
61
|
+
padding: 14px 20px; cursor: pointer; user-select: none;
|
|
62
62
|
}
|
|
63
|
-
.device-header
|
|
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 {
|
|
64
68
|
font-size: 16px; color: var(--ui-accent);
|
|
65
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; }
|
|
66
79
|
.switch-card {
|
|
67
80
|
border: 1px solid var(--ui-border);
|
|
68
|
-
border-radius: 8px;
|
|
81
|
+
border-radius: 8px; margin-bottom: 12px;
|
|
69
82
|
background: var(--ui-card-bg);
|
|
70
|
-
position: relative;
|
|
83
|
+
position: relative; overflow: hidden;
|
|
71
84
|
}
|
|
72
|
-
.switch-
|
|
73
|
-
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;
|
|
74
88
|
}
|
|
75
|
-
.switch-
|
|
76
|
-
|
|
77
|
-
|
|
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;
|
|
78
107
|
}
|
|
79
108
|
.btn {
|
|
80
109
|
padding: 8px 16px; border: none; border-radius: 6px;
|
|
@@ -82,6 +111,7 @@
|
|
|
82
111
|
}
|
|
83
112
|
.btn-danger { background: #dc3545; color: #fff; }
|
|
84
113
|
.btn-danger:hover { background: #c82333; }
|
|
114
|
+
.btn-sm { padding: 4px 10px; font-size: 12px; }
|
|
85
115
|
.btn-primary { background: var(--ui-accent); color: #fff; }
|
|
86
116
|
.btn-primary:hover { opacity: 0.85; }
|
|
87
117
|
.btn-outline {
|
|
@@ -103,6 +133,11 @@
|
|
|
103
133
|
background: var(--ui-input-bg);
|
|
104
134
|
color: var(--ui-text);
|
|
105
135
|
}
|
|
136
|
+
.master-option {
|
|
137
|
+
margin-top: 12px; padding: 12px 14px;
|
|
138
|
+
border: 1px dashed var(--ui-border);
|
|
139
|
+
border-radius: 8px; background: var(--ui-card-bg);
|
|
140
|
+
}
|
|
106
141
|
</style>
|
|
107
142
|
|
|
108
143
|
<div id="app"></div>
|
|
@@ -150,6 +185,15 @@
|
|
|
150
185
|
config.devices = [];
|
|
151
186
|
}
|
|
152
187
|
|
|
188
|
+
// Track expand/collapse state
|
|
189
|
+
const expandedDevices = new Set(config.devices.map((_, i) => i));
|
|
190
|
+
const expandedSwitches = new Set();
|
|
191
|
+
config.devices.forEach((dev, di) => {
|
|
192
|
+
(dev.switches || []).forEach((_, si) => {
|
|
193
|
+
expandedSwitches.add(`${di}_${si}`);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
153
197
|
const app = document.getElementById('app');
|
|
154
198
|
|
|
155
199
|
function render() {
|
|
@@ -178,28 +222,76 @@
|
|
|
178
222
|
});
|
|
179
223
|
|
|
180
224
|
document.getElementById('btn-add-device').addEventListener('click', () => {
|
|
225
|
+
const di = config.devices.length;
|
|
181
226
|
config.devices.push({
|
|
182
227
|
name: '',
|
|
183
228
|
switchBehavior: 'independent',
|
|
184
229
|
switches: [{ name: '', type: 'outlet', defaultState: false, delayOff: 0 }],
|
|
185
230
|
});
|
|
231
|
+
expandedDevices.add(di);
|
|
232
|
+
expandedSwitches.add(`${di}_0`);
|
|
186
233
|
render();
|
|
187
234
|
save();
|
|
188
235
|
});
|
|
189
236
|
|
|
237
|
+
// Device toggle
|
|
238
|
+
document.querySelectorAll('.device-toggle').forEach(el => {
|
|
239
|
+
el.addEventListener('click', (e) => {
|
|
240
|
+
if (e.target.closest('.btn')) return;
|
|
241
|
+
const di = parseInt(el.dataset.dev);
|
|
242
|
+
if (expandedDevices.has(di)) {
|
|
243
|
+
expandedDevices.delete(di);
|
|
244
|
+
} else {
|
|
245
|
+
expandedDevices.add(di);
|
|
246
|
+
}
|
|
247
|
+
render();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Switch toggle
|
|
252
|
+
document.querySelectorAll('.switch-toggle').forEach(el => {
|
|
253
|
+
el.addEventListener('click', (e) => {
|
|
254
|
+
if (e.target.closest('.btn')) return;
|
|
255
|
+
const key = `${el.dataset.dev}_${el.dataset.sw}`;
|
|
256
|
+
if (expandedSwitches.has(key)) {
|
|
257
|
+
expandedSwitches.delete(key);
|
|
258
|
+
} else {
|
|
259
|
+
expandedSwitches.add(key);
|
|
260
|
+
}
|
|
261
|
+
render();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
190
265
|
// Device-level events
|
|
191
266
|
document.querySelectorAll('.dev-field').forEach(input => {
|
|
192
267
|
input.addEventListener('change', () => {
|
|
193
268
|
const di = parseInt(input.dataset.dev);
|
|
194
269
|
const field = input.dataset.field;
|
|
195
270
|
config.devices[di][field] = input.value;
|
|
271
|
+
// If switching away from single, disable masterSwitch
|
|
272
|
+
if (field === 'switchBehavior' && input.value !== 'single') {
|
|
273
|
+
delete config.devices[di].masterSwitch;
|
|
274
|
+
}
|
|
275
|
+
render();
|
|
276
|
+
save();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Master switch toggle
|
|
281
|
+
document.querySelectorAll('.master-toggle').forEach(input => {
|
|
282
|
+
input.addEventListener('change', () => {
|
|
283
|
+
const di = parseInt(input.dataset.dev);
|
|
284
|
+
config.devices[di].masterSwitch = input.checked;
|
|
196
285
|
save();
|
|
197
286
|
});
|
|
198
287
|
});
|
|
199
288
|
|
|
200
289
|
document.querySelectorAll('.btn-remove-device').forEach(btn => {
|
|
201
|
-
btn.addEventListener('click', () => {
|
|
202
|
-
|
|
290
|
+
btn.addEventListener('click', (e) => {
|
|
291
|
+
e.stopPropagation();
|
|
292
|
+
const di = parseInt(btn.dataset.dev);
|
|
293
|
+
config.devices.splice(di, 1);
|
|
294
|
+
rebuildExpandState();
|
|
203
295
|
render();
|
|
204
296
|
save();
|
|
205
297
|
});
|
|
@@ -209,17 +301,21 @@
|
|
|
209
301
|
document.querySelectorAll('.btn-add-switch').forEach(btn => {
|
|
210
302
|
btn.addEventListener('click', () => {
|
|
211
303
|
const di = parseInt(btn.dataset.dev);
|
|
304
|
+
const si = config.devices[di].switches.length;
|
|
212
305
|
config.devices[di].switches.push({ name: '', type: 'outlet', defaultState: false, delayOff: 0 });
|
|
306
|
+
expandedSwitches.add(`${di}_${si}`);
|
|
213
307
|
render();
|
|
214
308
|
save();
|
|
215
309
|
});
|
|
216
310
|
});
|
|
217
311
|
|
|
218
312
|
document.querySelectorAll('.btn-remove-switch').forEach(btn => {
|
|
219
|
-
btn.addEventListener('click', () => {
|
|
313
|
+
btn.addEventListener('click', (e) => {
|
|
314
|
+
e.stopPropagation();
|
|
220
315
|
const di = parseInt(btn.dataset.dev);
|
|
221
316
|
const si = parseInt(btn.dataset.sw);
|
|
222
317
|
config.devices[di].switches.splice(si, 1);
|
|
318
|
+
rebuildExpandState();
|
|
223
319
|
render();
|
|
224
320
|
save();
|
|
225
321
|
});
|
|
@@ -242,69 +338,123 @@
|
|
|
242
338
|
});
|
|
243
339
|
}
|
|
244
340
|
|
|
341
|
+
function rebuildExpandState() {
|
|
342
|
+
const newDevices = new Set();
|
|
343
|
+
config.devices.forEach((_, i) => {
|
|
344
|
+
if (expandedDevices.has(i)) newDevices.add(i);
|
|
345
|
+
});
|
|
346
|
+
expandedDevices.clear();
|
|
347
|
+
newDevices.forEach(i => expandedDevices.add(i));
|
|
348
|
+
|
|
349
|
+
const newSwitches = new Set();
|
|
350
|
+
config.devices.forEach((dev, di) => {
|
|
351
|
+
(dev.switches || []).forEach((_, si) => {
|
|
352
|
+
if (expandedSwitches.has(`${di}_${si}`)) newSwitches.add(`${di}_${si}`);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
expandedSwitches.clear();
|
|
356
|
+
newSwitches.forEach(k => expandedSwitches.add(k));
|
|
357
|
+
}
|
|
358
|
+
|
|
245
359
|
function renderDevice(dev, di) {
|
|
246
360
|
const switches = dev.switches || [];
|
|
361
|
+
const isOpen = expandedDevices.has(di);
|
|
362
|
+
const devLabel = dev.name || `${t.device || 'Device'} #${di + 1}`;
|
|
363
|
+
const switchCount = switches.length;
|
|
364
|
+
const isSingle = dev.switchBehavior === 'single';
|
|
365
|
+
|
|
247
366
|
return `
|
|
248
367
|
<div class="device-card">
|
|
249
|
-
<div class="device-header">
|
|
250
|
-
<
|
|
251
|
-
|
|
368
|
+
<div class="device-header device-toggle" data-dev="${di}">
|
|
369
|
+
<div class="device-header-left">
|
|
370
|
+
<span class="chevron ${isOpen ? 'open' : ''}">▶</span>
|
|
371
|
+
<strong>${esc(devLabel)}</strong>
|
|
372
|
+
${!isOpen ? `<span class="switch-summary">(${switchCount} ${switchCount === 1 ? (t.switchSingular || 'switch') : (t.switches || 'switches').toLowerCase()})</span>` : ''}
|
|
373
|
+
</div>
|
|
374
|
+
<div class="device-header-right">
|
|
375
|
+
<button class="btn btn-danger btn-sm btn-remove-device" data-dev="${di}">${t.removeDevice || 'Remove Device'}</button>
|
|
376
|
+
</div>
|
|
252
377
|
</div>
|
|
253
378
|
|
|
254
|
-
<div class="
|
|
255
|
-
<div class="
|
|
256
|
-
<
|
|
257
|
-
|
|
379
|
+
<div class="device-body ${isOpen ? '' : 'collapsed'}">
|
|
380
|
+
<div class="inline-row">
|
|
381
|
+
<div class="form-group">
|
|
382
|
+
<label>${t.deviceName || 'Device Name'}</label>
|
|
383
|
+
<input type="text" class="dev-field" data-dev="${di}" data-field="name" value="${esc(dev.name || '')}">
|
|
384
|
+
</div>
|
|
385
|
+
<div class="form-group">
|
|
386
|
+
<label>${t.switchBehavior || 'Switch Behavior Mode'}</label>
|
|
387
|
+
<div class="desc">${isSingle ? (t.behaviorSingleDesc || '') : (t.behaviorIndependentDesc || '')}</div>
|
|
388
|
+
<select class="dev-field" data-dev="${di}" data-field="switchBehavior">
|
|
389
|
+
<option value="independent" ${dev.switchBehavior === 'independent' || !dev.switchBehavior ? 'selected' : ''}>${t.behaviorIndependent || 'Independent'}</option>
|
|
390
|
+
<option value="single" ${isSingle ? 'selected' : ''}>${t.behaviorSingle || 'Single'}</option>
|
|
391
|
+
</select>
|
|
392
|
+
</div>
|
|
258
393
|
</div>
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
<
|
|
264
|
-
<
|
|
265
|
-
|
|
394
|
+
|
|
395
|
+
${isSingle ? `
|
|
396
|
+
<div class="master-option">
|
|
397
|
+
<div class="toggle-wrap">
|
|
398
|
+
<input type="checkbox" class="master-toggle" data-dev="${di}" ${dev.masterSwitch ? 'checked' : ''}>
|
|
399
|
+
<div>
|
|
400
|
+
<label style="margin-bottom:0">${t.masterSwitch || 'Master Switch'}</label>
|
|
401
|
+
<div class="desc" style="margin-bottom:0">${t.masterSwitchDesc || ''}</div>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
266
404
|
</div>
|
|
267
|
-
|
|
405
|
+
` : ''}
|
|
268
406
|
|
|
269
|
-
|
|
407
|
+
<div class="section-title" style="margin-top:16px">${t.switches || 'Switches'}</div>
|
|
270
408
|
|
|
271
|
-
|
|
409
|
+
${switches.map((sw, si) => renderSwitch(sw, di, si)).join('')}
|
|
272
410
|
|
|
273
|
-
|
|
411
|
+
<button class="btn btn-primary btn-add-switch" data-dev="${di}">+ ${t.addSwitch || 'Add Switch'}</button>
|
|
412
|
+
</div>
|
|
274
413
|
</div>
|
|
275
414
|
`;
|
|
276
415
|
}
|
|
277
416
|
|
|
278
417
|
function renderSwitch(sw, di, si) {
|
|
418
|
+
const isOpen = expandedSwitches.has(`${di}_${si}`);
|
|
419
|
+
const swLabel = sw.name || `#${si + 1}`;
|
|
420
|
+
|
|
279
421
|
return `
|
|
280
422
|
<div class="switch-card">
|
|
281
|
-
<div class="switch-header">
|
|
282
|
-
<
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
<div class="form-group">
|
|
287
|
-
<label>${t.switchName || 'Switch Name'}</label>
|
|
288
|
-
<input type="text" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="name" value="${esc(sw.name || '')}">
|
|
423
|
+
<div class="switch-header switch-toggle" data-dev="${di}" data-sw="${si}">
|
|
424
|
+
<div class="switch-header-left">
|
|
425
|
+
<span class="chevron ${isOpen ? 'open' : ''}">▶</span>
|
|
426
|
+
<strong>${esc(swLabel)}</strong>
|
|
427
|
+
${!isOpen ? `<span class="switch-summary">${sw.type || 'outlet'}${sw.delayOff ? ` / ${sw.delayOff}ms` : ''}</span>` : ''}
|
|
289
428
|
</div>
|
|
290
|
-
<div class="
|
|
291
|
-
<
|
|
292
|
-
<select class="sw-field" data-dev="${di}" data-sw="${si}" data-field="type">
|
|
293
|
-
<option value="switch" ${sw.type === 'switch' ? 'selected' : ''}>${t.typeSwitch || 'Switch'}</option>
|
|
294
|
-
<option value="outlet" ${sw.type === 'outlet' || !sw.type ? 'selected' : ''}>${t.typeOutlet || 'Outlet'}</option>
|
|
295
|
-
</select>
|
|
429
|
+
<div class="switch-header-right">
|
|
430
|
+
<button class="btn btn-danger btn-sm btn-remove-switch" data-dev="${di}" data-sw="${si}">${t.removeSwitch || 'Remove'}</button>
|
|
296
431
|
</div>
|
|
297
432
|
</div>
|
|
298
|
-
<div class="
|
|
299
|
-
<div class="
|
|
300
|
-
<
|
|
301
|
-
|
|
433
|
+
<div class="switch-body ${isOpen ? '' : 'collapsed'}">
|
|
434
|
+
<div class="inline-row">
|
|
435
|
+
<div class="form-group">
|
|
436
|
+
<label>${t.switchName || 'Switch Name'}</label>
|
|
437
|
+
<input type="text" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="name" value="${esc(sw.name || '')}">
|
|
438
|
+
</div>
|
|
439
|
+
<div class="form-group">
|
|
440
|
+
<label>${t.switchType || 'Switch Type'}</label>
|
|
441
|
+
<select class="sw-field" data-dev="${di}" data-sw="${si}" data-field="type">
|
|
442
|
+
<option value="switch" ${sw.type === 'switch' ? 'selected' : ''}>${t.typeSwitch || 'Switch'}</option>
|
|
443
|
+
<option value="outlet" ${sw.type === 'outlet' || !sw.type ? 'selected' : ''}>${t.typeOutlet || 'Outlet'}</option>
|
|
444
|
+
</select>
|
|
445
|
+
</div>
|
|
302
446
|
</div>
|
|
303
|
-
<div class="
|
|
304
|
-
<
|
|
305
|
-
|
|
306
|
-
<input type="
|
|
307
|
-
|
|
447
|
+
<div class="inline-row">
|
|
448
|
+
<div class="form-group">
|
|
449
|
+
<label>${t.delayOff || 'Auto Turn Off (ms)'}</label>
|
|
450
|
+
<input type="number" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="delayOff" min="0" value="${sw.delayOff || 0}">
|
|
451
|
+
</div>
|
|
452
|
+
<div class="form-group">
|
|
453
|
+
<label>${t.defaultState || 'Default State'}</label>
|
|
454
|
+
<div class="toggle-wrap" style="margin-top:6px">
|
|
455
|
+
<input type="checkbox" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="defaultState" ${sw.defaultState ? 'checked' : ''}>
|
|
456
|
+
<span>${sw.defaultState ? (t.on || 'On') : (t.off || 'Off')}</span>
|
|
457
|
+
</div>
|
|
308
458
|
</div>
|
|
309
459
|
</div>
|
|
310
460
|
</div>
|
package/index.js
CHANGED
|
@@ -8,6 +8,8 @@ const SERVICE_TYPES = {
|
|
|
8
8
|
outlet: 'Outlet',
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
const MASTER_SUBTYPE = 'master_switch';
|
|
12
|
+
|
|
11
13
|
module.exports = (api) => {
|
|
12
14
|
api.registerPlatform(PLATFORM_NAME, MultipleSwitchPlatform);
|
|
13
15
|
};
|
|
@@ -73,6 +75,7 @@ class MultipleSwitchPlatform {
|
|
|
73
75
|
|
|
74
76
|
const name = device.name || 'Multiple Switch Panel';
|
|
75
77
|
const behavior = device.switchBehavior || 'independent';
|
|
78
|
+
const hasMaster = behavior === 'single' && device.masterSwitch === true;
|
|
76
79
|
const uuid = this.api.hap.uuid.generate(name);
|
|
77
80
|
|
|
78
81
|
let accessory = this.cachedAccessories.get(uuid);
|
|
@@ -83,12 +86,13 @@ class MultipleSwitchPlatform {
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
accessory.context.switchBehavior = behavior;
|
|
89
|
+
accessory.context.hasMaster = hasMaster;
|
|
86
90
|
accessory.context.switchStates = accessory.context.switchStates || {};
|
|
87
91
|
|
|
88
92
|
const services = new Map();
|
|
89
93
|
this.deviceServices.set(uuid, services);
|
|
90
94
|
|
|
91
|
-
this.reconcileServices(accessory, switches, services);
|
|
95
|
+
this.reconcileServices(accessory, switches, services, hasMaster);
|
|
92
96
|
|
|
93
97
|
if (isNew) {
|
|
94
98
|
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
@@ -97,9 +101,10 @@ class MultipleSwitchPlatform {
|
|
|
97
101
|
this.cachedAccessories.delete(uuid);
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
reconcileServices(accessory, switches, services) {
|
|
104
|
+
reconcileServices(accessory, switches, services, hasMaster) {
|
|
101
105
|
const activeSubtypes = new Set();
|
|
102
106
|
|
|
107
|
+
// Create regular switches
|
|
103
108
|
switches.forEach((sw, index) => {
|
|
104
109
|
const subtype = `switch_${index}`;
|
|
105
110
|
activeSubtypes.add(subtype);
|
|
@@ -121,6 +126,26 @@ class MultipleSwitchPlatform {
|
|
|
121
126
|
}
|
|
122
127
|
});
|
|
123
128
|
|
|
129
|
+
// Create master switch if enabled
|
|
130
|
+
if (hasMaster) {
|
|
131
|
+
activeSubtypes.add(MASTER_SUBTYPE);
|
|
132
|
+
|
|
133
|
+
let masterService = accessory.getServiceById(this.Service.Switch, MASTER_SUBTYPE);
|
|
134
|
+
if (!masterService) {
|
|
135
|
+
masterService = accessory.addService(this.Service.Switch, 'Master', MASTER_SUBTYPE);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
masterService.setCharacteristic(this.Characteristic.Name, 'Master');
|
|
139
|
+
this.configureMasterHandler(accessory, masterService, services);
|
|
140
|
+
|
|
141
|
+
services.set(MASTER_SUBTYPE, masterService);
|
|
142
|
+
|
|
143
|
+
if (accessory.context.switchStates[MASTER_SUBTYPE] === undefined) {
|
|
144
|
+
accessory.context.switchStates[MASTER_SUBTYPE] = false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Remove stale services
|
|
124
149
|
const servicesToRemove = accessory.services.filter((s) => {
|
|
125
150
|
return s.subtype && !activeSubtypes.has(s.subtype);
|
|
126
151
|
});
|
|
@@ -140,32 +165,39 @@ class MultipleSwitchPlatform {
|
|
|
140
165
|
this.turnOffOthers(accessory, subtype, services);
|
|
141
166
|
}
|
|
142
167
|
|
|
143
|
-
if (behavior === 'master') {
|
|
144
|
-
this.setAll(accessory, value, services);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
168
|
if (value && sw.delayOff > 0) {
|
|
148
169
|
this.scheduleAutoOff(accessory, service, sw, subtype);
|
|
149
170
|
}
|
|
150
171
|
});
|
|
151
172
|
}
|
|
152
173
|
|
|
174
|
+
configureMasterHandler(accessory, masterService, services) {
|
|
175
|
+
masterService.getCharacteristic(this.Characteristic.On)
|
|
176
|
+
.onGet(() => accessory.context.switchStates[MASTER_SUBTYPE] ?? false)
|
|
177
|
+
.onSet((value) => {
|
|
178
|
+
accessory.context.switchStates[MASTER_SUBTYPE] = value;
|
|
179
|
+
this.log.info(`[Master] ${value ? 'ON' : 'OFF'}`);
|
|
180
|
+
|
|
181
|
+
// Master ON → all regular switches ON
|
|
182
|
+
// Master OFF → all regular switches OFF
|
|
183
|
+
for (const [key, svc] of services) {
|
|
184
|
+
if (key !== MASTER_SUBTYPE) {
|
|
185
|
+
accessory.context.switchStates[key] = value;
|
|
186
|
+
svc.updateCharacteristic(this.Characteristic.On, value);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
153
192
|
turnOffOthers(accessory, excludeSubtype, services) {
|
|
154
193
|
for (const [key, svc] of services) {
|
|
155
|
-
if (key !== excludeSubtype) {
|
|
194
|
+
if (key !== excludeSubtype && key !== MASTER_SUBTYPE) {
|
|
156
195
|
accessory.context.switchStates[key] = false;
|
|
157
196
|
svc.updateCharacteristic(this.Characteristic.On, false);
|
|
158
197
|
}
|
|
159
198
|
}
|
|
160
199
|
}
|
|
161
200
|
|
|
162
|
-
setAll(accessory, value, services) {
|
|
163
|
-
for (const [key, svc] of services) {
|
|
164
|
-
accessory.context.switchStates[key] = value;
|
|
165
|
-
svc.updateCharacteristic(this.Characteristic.On, value);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
201
|
scheduleAutoOff(accessory, service, sw, subtype) {
|
|
170
202
|
setTimeout(() => {
|
|
171
203
|
if (accessory.context.switchStates[subtype]) {
|
package/package.json
CHANGED