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.
@@ -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
@@ -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(config.devices.map((_, i) => i));
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" ${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>
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
- reconcileServices(accessory, switches, services) {
101
- const activeSubtypes = new Set();
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
- let service = accessory.getServiceById(ServiceClass, subtype);
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
- service.setCharacteristic(this.Characteristic.Name, sw.name);
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
- const servicesToRemove = accessory.services.filter((s) => {
125
- return s.subtype && !activeSubtypes.has(s.subtype);
126
- });
127
- servicesToRemove.forEach((s) => accessory.removeService(s));
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.5.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": ">=1.3.0"
34
+ "homebridge": "^1.3.0 || ^2.0.0-beta.0"
35
35
  }
36
36
  }