homebridge-multiple-switch 1.6.0-beta.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,51 @@
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
+
3
49
  ## [1.6.0-beta.1] - 2026-03-21
4
50
 
5
51
  ### Removed
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "يمكن تشغيل مفتاح واحد فقط في نفس الوقت. تشغيل واحد يقوم بإيقاف جميع الآخرين تلقائياً.",
34
34
  "masterSwitch": "المفتاح الرئيسي",
35
35
  "masterSwitchDesc": "يضيف مفتاحاً رئيسياً يقوم بتشغيل أو إيقاف جميع المفاتيح دفعة واحدة.",
36
- "switchSingular": "مفتاح"
36
+ "switchSingular": "مفتاح",
37
+ "masterSwitchType": "نوع المفتاح الرئيسي"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "Es kann nur ein Schalter gleichzeitig eingeschaltet sein. Das Einschalten eines Schalters schaltet alle anderen automatisch aus.",
34
34
  "masterSwitch": "Hauptschalter",
35
35
  "masterSwitchDesc": "Fügt einen Hauptschalter hinzu, der alle Schalter auf einmal ein- oder ausschaltet.",
36
- "switchSingular": "Schalter"
36
+ "switchSingular": "Schalter",
37
+ "masterSwitchType": "Hauptschaltertyp"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "Only one switch can be on at a time. Turning one on automatically turns off all others.",
34
34
  "masterSwitch": "Master Switch",
35
35
  "masterSwitchDesc": "Adds a master switch that turns all switches on or off at once.",
36
- "switchSingular": "switch"
36
+ "switchSingular": "switch",
37
+ "masterSwitchType": "Master Switch Type"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "Solo un interruptor puede estar encendido a la vez. Encender uno apaga automáticamente todos los demás.",
34
34
  "masterSwitch": "Interruptor maestro",
35
35
  "masterSwitchDesc": "Agrega un interruptor maestro que enciende o apaga todos los interruptores a la vez.",
36
- "switchSingular": "interruptor"
36
+ "switchSingular": "interruptor",
37
+ "masterSwitchType": "Tipo de interruptor maestro"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "Un seul interrupteur peut être activé à la fois. En activer un désactive automatiquement tous les autres.",
34
34
  "masterSwitch": "Interrupteur principal",
35
35
  "masterSwitchDesc": "Ajoute un interrupteur principal qui active ou désactive tous les interrupteurs en une seule fois.",
36
- "switchSingular": "interrupteur"
36
+ "switchSingular": "interrupteur",
37
+ "masterSwitchType": "Type d'interrupteur principal"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "Solo un interruttore può essere acceso alla volta. Accenderne uno spegne automaticamente tutti gli altri.",
34
34
  "masterSwitch": "Interruttore principale",
35
35
  "masterSwitchDesc": "Aggiunge un interruttore principale che accende o spegne tutti gli interruttori contemporaneamente.",
36
- "switchSingular": "interruttore"
36
+ "switchSingular": "interruttore",
37
+ "masterSwitchType": "Tipo di interruttore principale"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "同時に1つのスイッチのみオンにできます。1つをオンにすると他はすべて自動的にオフになります。",
34
34
  "masterSwitch": "マスタースイッチ",
35
35
  "masterSwitchDesc": "すべてのスイッチを一度にオン/オフするマスタースイッチを追加します。",
36
- "switchSingular": "スイッチ"
36
+ "switchSingular": "スイッチ",
37
+ "masterSwitchType": "マスタースイッチタイプ"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "한 번에 하나의 스위치만 켤 수 있습니다. 하나를 켜면 다른 모든 것이 자동으로 꺼집니다.",
34
34
  "masterSwitch": "마스터 스위치",
35
35
  "masterSwitchDesc": "모든 스위치를 한 번에 켜거나 끄는 마스터 스위치를 추가합니다.",
36
- "switchSingular": "스위치"
36
+ "switchSingular": "스위치",
37
+ "masterSwitchType": "마스터 스위치 유형"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "Er kan slechts één schakelaar tegelijk aan staan. Het inschakelen van één schakelaar schakelt alle andere automatisch uit.",
34
34
  "masterSwitch": "Hoofdschakelaar",
35
35
  "masterSwitchDesc": "Voegt een hoofdschakelaar toe die alle schakelaars in één keer in- of uitschakelt.",
36
- "switchSingular": "schakelaar"
36
+ "switchSingular": "schakelaar",
37
+ "masterSwitchType": "Type hoofdschakelaar"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "Tylko jeden przełącznik może być włączony jednocześnie. Włączenie jednego automatycznie wyłącza wszystkie pozostałe.",
34
34
  "masterSwitch": "Przełącznik główny",
35
35
  "masterSwitchDesc": "Dodaje przełącznik główny, który włącza lub wyłącza wszystkie przełączniki jednocześnie.",
36
- "switchSingular": "przełącznik"
36
+ "switchSingular": "przełącznik",
37
+ "masterSwitchType": "Typ przełącznika głównego"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "Apenas um interruptor pode estar ligado por vez. Ligar um desliga automaticamente todos os outros.",
34
34
  "masterSwitch": "Interruptor principal",
35
35
  "masterSwitchDesc": "Adiciona um interruptor principal que liga ou desliga todos os interruptores de uma só vez.",
36
- "switchSingular": "interruptor"
36
+ "switchSingular": "interruptor",
37
+ "masterSwitchType": "Tipo de interruptor principal"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "Одновременно может быть включён только один переключатель. Включение одного автоматически выключает все остальные.",
34
34
  "masterSwitch": "Главный переключатель",
35
35
  "masterSwitchDesc": "Добавляет главный переключатель, который включает или выключает все переключатели одновременно.",
36
- "switchSingular": "переключатель"
36
+ "switchSingular": "переключатель",
37
+ "masterSwitchType": "Тип главного переключателя"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "Aynı anda yalnızca bir anahtar açık olabilir. Birini açmak diğerlerini otomatik kapatır.",
34
34
  "masterSwitch": "Ana Anahtar",
35
35
  "masterSwitchDesc": "Tüm anahtarları tek seferde açan veya kapatan bir ana anahtar ekler.",
36
- "switchSingular": "anahtar"
36
+ "switchSingular": "anahtar",
37
+ "masterSwitchType": "Ana Anahtar Tipi"
37
38
  }
@@ -33,5 +33,6 @@
33
33
  "behaviorSingleDesc": "同一时间只能有一个开关处于打开状态。打开一个会自动关闭所有其他开关。",
34
34
  "masterSwitch": "主开关",
35
35
  "masterSwitchDesc": "添加一个主开关,可以一次性打开或关闭所有开关。",
36
- "switchSingular": "开关"
36
+ "switchSingular": "开关",
37
+ "masterSwitchType": "主开关类型"
37
38
  }
@@ -185,14 +185,9 @@
185
185
  config.devices = [];
186
186
  }
187
187
 
188
- // Track expand/collapse state
189
- const expandedDevices = new Set(config.devices.map((_, i) => i));
188
+ // Track expand/collapse state — all collapsed by default
189
+ const expandedDevices = new Set();
190
190
  const expandedSwitches = new Set();
191
- config.devices.forEach((dev, di) => {
192
- (dev.switches || []).forEach((_, si) => {
193
- expandedSwitches.add(`${di}_${si}`);
194
- });
195
- });
196
191
 
197
192
  const app = document.getElementById('app');
198
193
 
@@ -268,8 +263,8 @@
268
263
  const di = parseInt(input.dataset.dev);
269
264
  const field = input.dataset.field;
270
265
  config.devices[di][field] = input.value;
271
- // If switching away from single, disable masterSwitch
272
- if (field === 'switchBehavior' && input.value !== 'single') {
266
+ // If switching away from independent, disable masterSwitch
267
+ if (field === 'switchBehavior' && input.value !== 'independent') {
273
268
  delete config.devices[di].masterSwitch;
274
269
  }
275
270
  render();
@@ -282,6 +277,19 @@
282
277
  input.addEventListener('change', () => {
283
278
  const di = parseInt(input.dataset.dev);
284
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;
285
293
  save();
286
294
  });
287
295
  });
@@ -362,6 +370,8 @@
362
370
  const devLabel = dev.name || `${t.device || 'Device'} #${di + 1}`;
363
371
  const switchCount = switches.length;
364
372
  const isSingle = dev.switchBehavior === 'single';
373
+ const isIndependent = dev.switchBehavior === 'independent' || !dev.switchBehavior;
374
+ const behaviorDesc = isSingle ? (t.behaviorSingleDesc || '') : (t.behaviorIndependentDesc || '');
365
375
 
366
376
  return `
367
377
  <div class="device-card">
@@ -384,22 +394,30 @@
384
394
  </div>
385
395
  <div class="form-group">
386
396
  <label>${t.switchBehavior || 'Switch Behavior Mode'}</label>
387
- <div class="desc">${isSingle ? (t.behaviorSingleDesc || '') : (t.behaviorIndependentDesc || '')}</div>
388
397
  <select class="dev-field" data-dev="${di}" data-field="switchBehavior">
389
- <option value="independent" ${dev.switchBehavior === 'independent' || !dev.switchBehavior ? 'selected' : ''}>${t.behaviorIndependent || 'Independent'}</option>
398
+ <option value="independent" ${isIndependent ? 'selected' : ''}>${t.behaviorIndependent || 'Independent'}</option>
390
399
  <option value="single" ${isSingle ? 'selected' : ''}>${t.behaviorSingle || 'Single'}</option>
391
400
  </select>
401
+ <div class="desc" style="margin-top:6px">${behaviorDesc}</div>
392
402
  </div>
393
403
  </div>
394
404
 
395
- ${isSingle ? `
405
+ ${isIndependent ? `
396
406
  <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>
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>
402
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>
419
+ </select>
420
+ ` : ''}
403
421
  </div>
404
422
  </div>
405
423
  ` : ''}
package/index.js CHANGED
@@ -75,7 +75,7 @@ class MultipleSwitchPlatform {
75
75
 
76
76
  const name = device.name || 'Multiple Switch Panel';
77
77
  const behavior = device.switchBehavior || 'independent';
78
- const hasMaster = behavior === 'single' && device.masterSwitch === true;
78
+ const hasMaster = behavior === 'independent' && device.masterSwitch === true;
79
79
  const uuid = this.api.hap.uuid.generate(name);
80
80
 
81
81
  let accessory = this.cachedAccessories.get(uuid);
@@ -92,7 +92,7 @@ class MultipleSwitchPlatform {
92
92
  const services = new Map();
93
93
  this.deviceServices.set(uuid, services);
94
94
 
95
- this.reconcileServices(accessory, switches, services, hasMaster);
95
+ this.reconcileServices(accessory, device, switches, services, hasMaster);
96
96
 
97
97
  if (isNew) {
98
98
  this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
@@ -101,22 +101,51 @@ class MultipleSwitchPlatform {
101
101
  this.cachedAccessories.delete(uuid);
102
102
  }
103
103
 
104
- reconcileServices(accessory, switches, services, hasMaster) {
105
- 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);
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
+ }
106
140
 
107
- // Create regular switches
141
+ // 2. Create regular switches in config order
108
142
  switches.forEach((sw, index) => {
109
143
  const subtype = `switch_${index}`;
110
- activeSubtypes.add(subtype);
111
144
 
112
145
  const ServiceClass = this.getServiceClass(sw.type);
113
- let service = accessory.getServiceById(ServiceClass, subtype);
114
-
115
- if (!service) {
116
- service = accessory.addService(ServiceClass, sw.name, subtype);
117
- }
146
+ const service = accessory.addService(ServiceClass, sw.name, subtype);
118
147
 
119
- service.setCharacteristic(this.Characteristic.Name, sw.name);
148
+ this.setServiceName(service, sw.name);
120
149
  this.configureSwitchHandlers(accessory, service, sw, subtype, services);
121
150
 
122
151
  services.set(subtype, service);
@@ -126,30 +155,13 @@ class MultipleSwitchPlatform {
126
155
  }
127
156
  });
128
157
 
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;
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];
145
163
  }
146
164
  }
147
-
148
- // Remove stale services
149
- const servicesToRemove = accessory.services.filter((s) => {
150
- return s.subtype && !activeSubtypes.has(s.subtype);
151
- });
152
- servicesToRemove.forEach((s) => accessory.removeService(s));
153
165
  }
154
166
 
155
167
  configureSwitchHandlers(accessory, service, sw, subtype, services) {
@@ -178,8 +190,6 @@ class MultipleSwitchPlatform {
178
190
  accessory.context.switchStates[MASTER_SUBTYPE] = value;
179
191
  this.log.info(`[Master] ${value ? 'ON' : 'OFF'}`);
180
192
 
181
- // Master ON → all regular switches ON
182
- // Master OFF → all regular switches OFF
183
193
  for (const [key, svc] of services) {
184
194
  if (key !== MASTER_SUBTYPE) {
185
195
  accessory.context.switchStates[key] = value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-multiple-switch",
3
- "version": "1.6.0-beta.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
  }