homebridge-multiple-switch 1.5.1 → 1.6.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +11 -1
- package/CHANGELOG.md +61 -0
- package/config.schema.json +6 -1
- package/homebridge-ui/public/i18n/ar.json +7 -2
- package/homebridge-ui/public/i18n/de.json +7 -2
- package/homebridge-ui/public/i18n/en.json +7 -2
- package/homebridge-ui/public/i18n/es.json +7 -2
- package/homebridge-ui/public/i18n/fr.json +7 -2
- package/homebridge-ui/public/i18n/it.json +7 -2
- package/homebridge-ui/public/i18n/ja.json +7 -2
- package/homebridge-ui/public/i18n/ko.json +7 -2
- package/homebridge-ui/public/i18n/nl.json +7 -2
- package/homebridge-ui/public/i18n/pl.json +7 -2
- package/homebridge-ui/public/i18n/pt.json +7 -2
- package/homebridge-ui/public/i18n/ru.json +7 -2
- package/homebridge-ui/public/i18n/tr.json +7 -2
- package/homebridge-ui/public/i18n/zh-CN.json +7 -2
- package/homebridge-ui/public/index.html +61 -14
- package/index.js +68 -26
- package/package.json +2 -2
|
@@ -22,7 +22,17 @@ jobs:
|
|
|
22
22
|
- name: Install dependencies
|
|
23
23
|
run: npm install
|
|
24
24
|
|
|
25
|
+
- name: Determine npm tag
|
|
26
|
+
id: npm-tag
|
|
27
|
+
run: |
|
|
28
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
29
|
+
if echo "$VERSION" | grep -q "beta"; then
|
|
30
|
+
echo "tag=beta" >> $GITHUB_OUTPUT
|
|
31
|
+
else
|
|
32
|
+
echo "tag=latest" >> $GITHUB_OUTPUT
|
|
33
|
+
fi
|
|
34
|
+
|
|
25
35
|
- name: Publish to npm
|
|
26
|
-
run: npm publish
|
|
36
|
+
run: npm publish --tag ${{ steps.npm-tag.outputs.tag }}
|
|
27
37
|
env:
|
|
28
38
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.0-beta.10] - 2026-03-22
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Homebridge v2 compatibility — plugin now shows green checkmark in v2 readiness check
|
|
7
|
+
- Switch ordering in HomeKit now follows config order (services recreated in sequence)
|
|
8
|
+
- Removed ServiceLabel/ServiceLabelIndex that caused outlets to render as toggles
|
|
9
|
+
instead of buttons
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Master switch type now defaults to Switch instead of Outlet (both UI and backend)
|
|
13
|
+
|
|
14
|
+
## [1.6.0-beta.5] - 2026-03-21
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Switch names now correctly appear in HomeKit — services are recreated on each
|
|
18
|
+
start with fresh displayName, Name, and ConfiguredName (cached services kept
|
|
19
|
+
stale names from initial creation)
|
|
20
|
+
- Master switch now always appears first in HomeKit — all subtype services are
|
|
21
|
+
removed and recreated in correct order (master first, then switches)
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Master switch type selector now inline with the toggle (same row)
|
|
25
|
+
|
|
26
|
+
## [1.6.0-beta.4] - 2026-03-21
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- Master switch type selection (Switch or Outlet) — appears when master switch is enabled
|
|
30
|
+
- `masterSwitchType` config option
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- All devices and switches now start collapsed when config UI is opened
|
|
34
|
+
- New devices/switches still open expanded when freshly added
|
|
35
|
+
|
|
36
|
+
## [1.6.0-beta.3] - 2026-03-21
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- Switch names now display correctly in HomeKit using `ConfiguredName`
|
|
40
|
+
characteristic (previously all showed the device name)
|
|
41
|
+
- Master switch now always appears first in HomeKit (created before regular switches)
|
|
42
|
+
|
|
43
|
+
## [1.6.0-beta.2] - 2026-03-21
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- Master Switch now available in Independent mode (was incorrectly in Single mode)
|
|
47
|
+
- Behavior description now appears below the select dropdown instead of above
|
|
48
|
+
|
|
49
|
+
## [1.6.0-beta.1] - 2026-03-21
|
|
50
|
+
|
|
51
|
+
### Removed
|
|
52
|
+
- Master switch behavior mode — replaced by a more useful master switch option
|
|
53
|
+
within Single mode
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
- Switch behavior now only has two modes: Independent and Single
|
|
57
|
+
- Added descriptions to both behavior modes explaining how they work
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
- Master Switch option (available in Single mode only): adds an extra switch
|
|
61
|
+
that turns all switches on or off at once
|
|
62
|
+
- New i18n keys for behavior descriptions, master switch label/description
|
|
63
|
+
|
|
3
64
|
## [1.5.1] - 2026-03-21
|
|
4
65
|
|
|
5
66
|
### 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,11 @@
|
|
|
29
28
|
"device": "جهاز",
|
|
30
29
|
"deviceName": "اسم الجهاز",
|
|
31
30
|
"addDevice": "إضافة جهاز",
|
|
32
|
-
"removeDevice": "إزالة الجهاز"
|
|
31
|
+
"removeDevice": "إزالة الجهاز",
|
|
32
|
+
"behaviorIndependentDesc": "كل مفتاح يعمل بشكل مستقل. تشغيل أو إيقاف أحدها لا يؤثر على الآخرين.",
|
|
33
|
+
"behaviorSingleDesc": "يمكن تشغيل مفتاح واحد فقط في نفس الوقت. تشغيل واحد يقوم بإيقاف جميع الآخرين تلقائياً.",
|
|
34
|
+
"masterSwitch": "المفتاح الرئيسي",
|
|
35
|
+
"masterSwitchDesc": "يضيف مفتاحاً رئيسياً يقوم بتشغيل أو إيقاف جميع المفاتيح دفعة واحدة.",
|
|
36
|
+
"switchSingular": "مفتاح",
|
|
37
|
+
"masterSwitchType": "نوع المفتاح الرئيسي"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
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",
|
|
37
|
+
"masterSwitchType": "Hauptschaltertyp"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
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",
|
|
37
|
+
"masterSwitchType": "Master Switch Type"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
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",
|
|
37
|
+
"masterSwitchType": "Tipo de interruptor maestro"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
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",
|
|
37
|
+
"masterSwitchType": "Type d'interrupteur principal"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
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",
|
|
37
|
+
"masterSwitchType": "Tipo di interruttore principale"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
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": "スイッチ",
|
|
37
|
+
"masterSwitchType": "マスタースイッチタイプ"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
29
28
|
"device": "장치",
|
|
30
29
|
"deviceName": "장치 이름",
|
|
31
30
|
"addDevice": "장치 추가",
|
|
32
|
-
"removeDevice": "장치 삭제"
|
|
31
|
+
"removeDevice": "장치 삭제",
|
|
32
|
+
"behaviorIndependentDesc": "각 스위치는 독립적으로 작동합니다. 하나를 켜거나 끄는 것이 다른 것에 영향을 주지 않습니다.",
|
|
33
|
+
"behaviorSingleDesc": "한 번에 하나의 스위치만 켤 수 있습니다. 하나를 켜면 다른 모든 것이 자동으로 꺼집니다.",
|
|
34
|
+
"masterSwitch": "마스터 스위치",
|
|
35
|
+
"masterSwitchDesc": "모든 스위치를 한 번에 켜거나 끄는 마스터 스위치를 추가합니다.",
|
|
36
|
+
"switchSingular": "스위치",
|
|
37
|
+
"masterSwitchType": "마스터 스위치 유형"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
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",
|
|
37
|
+
"masterSwitchType": "Type hoofdschakelaar"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
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",
|
|
37
|
+
"masterSwitchType": "Typ przełącznika głównego"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
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",
|
|
37
|
+
"masterSwitchType": "Tipo de interruptor principal"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
29
28
|
"device": "Устройство",
|
|
30
29
|
"deviceName": "Название устройства",
|
|
31
30
|
"addDevice": "Добавить устройство",
|
|
32
|
-
"removeDevice": "Удалить устройство"
|
|
31
|
+
"removeDevice": "Удалить устройство",
|
|
32
|
+
"behaviorIndependentDesc": "Каждый переключатель работает независимо. Включение или выключение одного не влияет на остальные.",
|
|
33
|
+
"behaviorSingleDesc": "Одновременно может быть включён только один переключатель. Включение одного автоматически выключает все остальные.",
|
|
34
|
+
"masterSwitch": "Главный переключатель",
|
|
35
|
+
"masterSwitchDesc": "Добавляет главный переключатель, который включает или выключает все переключатели одновременно.",
|
|
36
|
+
"switchSingular": "переключатель",
|
|
37
|
+
"masterSwitchType": "Тип главного переключателя"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
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",
|
|
37
|
+
"masterSwitchType": "Ana Anahtar Tipi"
|
|
33
38
|
}
|
|
@@ -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,11 @@
|
|
|
29
28
|
"device": "设备",
|
|
30
29
|
"deviceName": "设备名称",
|
|
31
30
|
"addDevice": "添加设备",
|
|
32
|
-
"removeDevice": "删除设备"
|
|
31
|
+
"removeDevice": "删除设备",
|
|
32
|
+
"behaviorIndependentDesc": "每个开关独立工作。打开或关闭一个不会影响其他开关。",
|
|
33
|
+
"behaviorSingleDesc": "同一时间只能有一个开关处于打开状态。打开一个会自动关闭所有其他开关。",
|
|
34
|
+
"masterSwitch": "主开关",
|
|
35
|
+
"masterSwitchDesc": "添加一个主开关,可以一次性打开或关闭所有开关。",
|
|
36
|
+
"switchSingular": "开关",
|
|
37
|
+
"masterSwitchType": "主开关类型"
|
|
33
38
|
}
|
|
@@ -133,6 +133,11 @@
|
|
|
133
133
|
background: var(--ui-input-bg);
|
|
134
134
|
color: var(--ui-text);
|
|
135
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
|
+
}
|
|
136
141
|
</style>
|
|
137
142
|
|
|
138
143
|
<div id="app"></div>
|
|
@@ -180,14 +185,9 @@
|
|
|
180
185
|
config.devices = [];
|
|
181
186
|
}
|
|
182
187
|
|
|
183
|
-
// Track expand/collapse state
|
|
184
|
-
const expandedDevices = new Set(
|
|
188
|
+
// Track expand/collapse state — all collapsed by default
|
|
189
|
+
const expandedDevices = new Set();
|
|
185
190
|
const expandedSwitches = new Set();
|
|
186
|
-
config.devices.forEach((dev, di) => {
|
|
187
|
-
(dev.switches || []).forEach((_, si) => {
|
|
188
|
-
expandedSwitches.add(`${di}_${si}`);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
191
|
|
|
192
192
|
const app = document.getElementById('app');
|
|
193
193
|
|
|
@@ -232,7 +232,6 @@
|
|
|
232
232
|
// Device toggle
|
|
233
233
|
document.querySelectorAll('.device-toggle').forEach(el => {
|
|
234
234
|
el.addEventListener('click', (e) => {
|
|
235
|
-
// Don't toggle if clicking a button inside header
|
|
236
235
|
if (e.target.closest('.btn')) return;
|
|
237
236
|
const di = parseInt(el.dataset.dev);
|
|
238
237
|
if (expandedDevices.has(di)) {
|
|
@@ -264,6 +263,33 @@
|
|
|
264
263
|
const di = parseInt(input.dataset.dev);
|
|
265
264
|
const field = input.dataset.field;
|
|
266
265
|
config.devices[di][field] = input.value;
|
|
266
|
+
// If switching away from independent, disable masterSwitch
|
|
267
|
+
if (field === 'switchBehavior' && input.value !== 'independent') {
|
|
268
|
+
delete config.devices[di].masterSwitch;
|
|
269
|
+
}
|
|
270
|
+
render();
|
|
271
|
+
save();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Master switch toggle
|
|
276
|
+
document.querySelectorAll('.master-toggle').forEach(input => {
|
|
277
|
+
input.addEventListener('change', () => {
|
|
278
|
+
const di = parseInt(input.dataset.dev);
|
|
279
|
+
config.devices[di].masterSwitch = input.checked;
|
|
280
|
+
if (!input.checked) {
|
|
281
|
+
delete config.devices[di].masterSwitchType;
|
|
282
|
+
}
|
|
283
|
+
render();
|
|
284
|
+
save();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Master switch type
|
|
289
|
+
document.querySelectorAll('.master-type-field').forEach(input => {
|
|
290
|
+
input.addEventListener('change', () => {
|
|
291
|
+
const di = parseInt(input.dataset.dev);
|
|
292
|
+
config.devices[di].masterSwitchType = input.value;
|
|
267
293
|
save();
|
|
268
294
|
});
|
|
269
295
|
});
|
|
@@ -273,7 +299,6 @@
|
|
|
273
299
|
e.stopPropagation();
|
|
274
300
|
const di = parseInt(btn.dataset.dev);
|
|
275
301
|
config.devices.splice(di, 1);
|
|
276
|
-
// Rebuild expand state
|
|
277
302
|
rebuildExpandState();
|
|
278
303
|
render();
|
|
279
304
|
save();
|
|
@@ -322,7 +347,6 @@
|
|
|
322
347
|
}
|
|
323
348
|
|
|
324
349
|
function rebuildExpandState() {
|
|
325
|
-
// Keep devices that still exist expanded
|
|
326
350
|
const newDevices = new Set();
|
|
327
351
|
config.devices.forEach((_, i) => {
|
|
328
352
|
if (expandedDevices.has(i)) newDevices.add(i);
|
|
@@ -345,6 +369,9 @@
|
|
|
345
369
|
const isOpen = expandedDevices.has(di);
|
|
346
370
|
const devLabel = dev.name || `${t.device || 'Device'} #${di + 1}`;
|
|
347
371
|
const switchCount = switches.length;
|
|
372
|
+
const isSingle = dev.switchBehavior === 'single';
|
|
373
|
+
const isIndependent = dev.switchBehavior === 'independent' || !dev.switchBehavior;
|
|
374
|
+
const behaviorDesc = isSingle ? (t.behaviorSingleDesc || '') : (t.behaviorIndependentDesc || '');
|
|
348
375
|
|
|
349
376
|
return `
|
|
350
377
|
<div class="device-card">
|
|
@@ -368,14 +395,34 @@
|
|
|
368
395
|
<div class="form-group">
|
|
369
396
|
<label>${t.switchBehavior || 'Switch Behavior Mode'}</label>
|
|
370
397
|
<select class="dev-field" data-dev="${di}" data-field="switchBehavior">
|
|
371
|
-
<option value="independent" ${
|
|
372
|
-
<option value="
|
|
373
|
-
|
|
398
|
+
<option value="independent" ${isIndependent ? 'selected' : ''}>${t.behaviorIndependent || 'Independent'}</option>
|
|
399
|
+
<option value="single" ${isSingle ? 'selected' : ''}>${t.behaviorSingle || 'Single'}</option>
|
|
400
|
+
</select>
|
|
401
|
+
<div class="desc" style="margin-top:6px">${behaviorDesc}</div>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
${isIndependent ? `
|
|
406
|
+
<div class="master-option">
|
|
407
|
+
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap">
|
|
408
|
+
<div class="toggle-wrap" style="flex:1;min-width:0">
|
|
409
|
+
<input type="checkbox" class="master-toggle" data-dev="${di}" ${dev.masterSwitch ? 'checked' : ''}>
|
|
410
|
+
<div>
|
|
411
|
+
<label style="margin-bottom:0">${t.masterSwitch || 'Master Switch'}</label>
|
|
412
|
+
<div class="desc" style="margin-bottom:0">${t.masterSwitchDesc || ''}</div>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
${dev.masterSwitch ? `
|
|
416
|
+
<select class="master-type-field" data-dev="${di}" style="width:auto;min-width:120px;flex-shrink:0">
|
|
417
|
+
<option value="switch" ${dev.masterSwitchType === 'switch' || !dev.masterSwitchType ? 'selected' : ''}>${t.typeSwitch || 'Switch'}</option>
|
|
418
|
+
<option value="outlet" ${dev.masterSwitchType === 'outlet' ? 'selected' : ''}>${t.typeOutlet || 'Outlet'}</option>
|
|
374
419
|
</select>
|
|
420
|
+
` : ''}
|
|
375
421
|
</div>
|
|
376
422
|
</div>
|
|
423
|
+
` : ''}
|
|
377
424
|
|
|
378
|
-
<div class="section-title">${t.switches || 'Switches'}</div>
|
|
425
|
+
<div class="section-title" style="margin-top:16px">${t.switches || 'Switches'}</div>
|
|
379
426
|
|
|
380
427
|
${switches.map((sw, si) => renderSwitch(sw, di, si)).join('')}
|
|
381
428
|
|
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 === 'independent' && 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, device, switches, services, hasMaster);
|
|
92
96
|
|
|
93
97
|
if (isNew) {
|
|
94
98
|
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
@@ -97,21 +101,51 @@ class MultipleSwitchPlatform {
|
|
|
97
101
|
this.cachedAccessories.delete(uuid);
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
setServiceName(service, displayName) {
|
|
105
|
+
service.displayName = displayName;
|
|
106
|
+
service.setCharacteristic(this.Characteristic.Name, displayName);
|
|
107
|
+
if (this.Characteristic.ConfiguredName) {
|
|
108
|
+
if (!service.testCharacteristic(this.Characteristic.ConfiguredName)) {
|
|
109
|
+
service.addOptionalCharacteristic(this.Characteristic.ConfiguredName);
|
|
110
|
+
}
|
|
111
|
+
service.setCharacteristic(this.Characteristic.ConfiguredName, displayName);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
reconcileServices(accessory, device, switches, services, hasMaster) {
|
|
116
|
+
// Remove ALL existing subtype services (ensures fresh names and correct order)
|
|
117
|
+
const subtypeServices = accessory.services.filter((s) => s.subtype);
|
|
118
|
+
subtypeServices.forEach((s) => accessory.removeService(s));
|
|
119
|
+
|
|
120
|
+
// Remove ServiceLabel if left over from previous version
|
|
121
|
+
const existingLabel = accessory.services.find(
|
|
122
|
+
(s) => s.UUID === this.Service.ServiceLabel.UUID
|
|
123
|
+
);
|
|
124
|
+
if (existingLabel) accessory.removeService(existingLabel);
|
|
125
|
+
|
|
126
|
+
// 1. Create master switch FIRST if enabled (appears at top in HomeKit)
|
|
127
|
+
if (hasMaster) {
|
|
128
|
+
const MasterServiceClass = this.getServiceClass(device.masterSwitchType || 'switch');
|
|
129
|
+
const masterService = accessory.addService(MasterServiceClass, 'Master', MASTER_SUBTYPE);
|
|
102
130
|
|
|
131
|
+
this.setServiceName(masterService, 'Master');
|
|
132
|
+
this.configureMasterHandler(accessory, masterService, services);
|
|
133
|
+
|
|
134
|
+
services.set(MASTER_SUBTYPE, masterService);
|
|
135
|
+
|
|
136
|
+
if (accessory.context.switchStates[MASTER_SUBTYPE] === undefined) {
|
|
137
|
+
accessory.context.switchStates[MASTER_SUBTYPE] = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. Create regular switches in config order
|
|
103
142
|
switches.forEach((sw, index) => {
|
|
104
143
|
const subtype = `switch_${index}`;
|
|
105
|
-
activeSubtypes.add(subtype);
|
|
106
144
|
|
|
107
145
|
const ServiceClass = this.getServiceClass(sw.type);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (!service) {
|
|
111
|
-
service = accessory.addService(ServiceClass, sw.name, subtype);
|
|
112
|
-
}
|
|
146
|
+
const service = accessory.addService(ServiceClass, sw.name, subtype);
|
|
113
147
|
|
|
114
|
-
|
|
148
|
+
this.setServiceName(service, sw.name);
|
|
115
149
|
this.configureSwitchHandlers(accessory, service, sw, subtype, services);
|
|
116
150
|
|
|
117
151
|
services.set(subtype, service);
|
|
@@ -121,10 +155,13 @@ class MultipleSwitchPlatform {
|
|
|
121
155
|
}
|
|
122
156
|
});
|
|
123
157
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
158
|
+
// Clean up states for removed switches
|
|
159
|
+
const activeKeys = new Set(services.keys());
|
|
160
|
+
for (const key of Object.keys(accessory.context.switchStates)) {
|
|
161
|
+
if (!activeKeys.has(key)) {
|
|
162
|
+
delete accessory.context.switchStates[key];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
128
165
|
}
|
|
129
166
|
|
|
130
167
|
configureSwitchHandlers(accessory, service, sw, subtype, services) {
|
|
@@ -140,32 +177,37 @@ class MultipleSwitchPlatform {
|
|
|
140
177
|
this.turnOffOthers(accessory, subtype, services);
|
|
141
178
|
}
|
|
142
179
|
|
|
143
|
-
if (behavior === 'master') {
|
|
144
|
-
this.setAll(accessory, value, services);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
180
|
if (value && sw.delayOff > 0) {
|
|
148
181
|
this.scheduleAutoOff(accessory, service, sw, subtype);
|
|
149
182
|
}
|
|
150
183
|
});
|
|
151
184
|
}
|
|
152
185
|
|
|
186
|
+
configureMasterHandler(accessory, masterService, services) {
|
|
187
|
+
masterService.getCharacteristic(this.Characteristic.On)
|
|
188
|
+
.onGet(() => accessory.context.switchStates[MASTER_SUBTYPE] ?? false)
|
|
189
|
+
.onSet((value) => {
|
|
190
|
+
accessory.context.switchStates[MASTER_SUBTYPE] = value;
|
|
191
|
+
this.log.info(`[Master] ${value ? 'ON' : 'OFF'}`);
|
|
192
|
+
|
|
193
|
+
for (const [key, svc] of services) {
|
|
194
|
+
if (key !== MASTER_SUBTYPE) {
|
|
195
|
+
accessory.context.switchStates[key] = value;
|
|
196
|
+
svc.updateCharacteristic(this.Characteristic.On, value);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
153
202
|
turnOffOthers(accessory, excludeSubtype, services) {
|
|
154
203
|
for (const [key, svc] of services) {
|
|
155
|
-
if (key !== excludeSubtype) {
|
|
204
|
+
if (key !== excludeSubtype && key !== MASTER_SUBTYPE) {
|
|
156
205
|
accessory.context.switchStates[key] = false;
|
|
157
206
|
svc.updateCharacteristic(this.Characteristic.On, false);
|
|
158
207
|
}
|
|
159
208
|
}
|
|
160
209
|
}
|
|
161
210
|
|
|
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
211
|
scheduleAutoOff(accessory, service, sw, subtype) {
|
|
170
212
|
setTimeout(() => {
|
|
171
213
|
if (accessory.context.switchStates[subtype]) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-multiple-switch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0-beta.10",
|
|
4
4
|
"description": "Multiple switch platform for Homebridge",
|
|
5
5
|
"homepage": "https://github.com/azadaydinli/homebridge-multiple-switch",
|
|
6
6
|
"main": "index.js",
|
|
@@ -31,6 +31,6 @@
|
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=18.0.0",
|
|
34
|
-
"homebridge": "
|
|
34
|
+
"homebridge": "^1.3.0 || ^2.0.0-beta.0"
|
|
35
35
|
}
|
|
36
36
|
}
|