homebridge-multiple-switch 1.6.0-beta.9 → 1.7.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 CHANGED
@@ -1,65 +1,51 @@
1
1
  # Changelog
2
2
 
3
- ## [1.6.0-beta.9] - 2026-03-22
4
-
5
- ### Fixed
6
- - Homebridge v2 compatibility — plugin now shows green checkmark in v2 readiness check
7
- - Switch ordering in HomeKit — services are now linked to ServiceLabel with
8
- ServiceLabelIndex for deterministic ordering (requires re-adding accessory in HomeKit
9
- if previously cached)
3
+ ## [1.7.0-beta.1] - 2026-05-17
10
4
 
11
5
  ### 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
6
+ - Switch type (Switch / Outlet) is now configured per device instead of per individual switch
7
+ - All switches within a device share the same type, set once in the device settings
8
+ - Switch cards no longer show a type selector — type is shown in the collapsed summary from the device level
15
9
 
16
10
  ### 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)
11
+ - Automatic migration: existing configs with per-switch `type` are migrated to device-level `switchType` on first UI save (first switch's type is used as the device type)
22
12
 
23
- ### Changed
24
- - Master switch type selector now inline with the toggle (same row)
25
-
26
- ## [1.6.0-beta.4] - 2026-03-21
13
+ ## [1.6.0] - 2026-03-22
27
14
 
28
15
  ### Added
29
- - Master switch type selection (Switch or Outlet) appears when master switch is enabled
30
- - `masterSwitchType` config option
16
+ - Multi-device support: create multiple separate HomeKit accessories, each with
17
+ its own name, switch behavior mode, and set of switches
18
+ - `devices` array in config — each device becomes a separate accessory in HomeKit
19
+ - Master Switch option (available in Independent mode): adds an extra switch
20
+ that turns all switches on or off at once
21
+ - Master switch type selection (Switch or Outlet)
22
+ - Collapsible device and switch cards in config UI with chevron animation
23
+ - Summary info shown when collapsed (switch count for devices, type/delay for switches)
24
+ - i18n localization with 14 languages: English, Turkish, German, French, Spanish,
25
+ Portuguese, Italian, Russian, Chinese (Simplified), Japanese, Korean, Polish,
26
+ Dutch, Arabic
27
+ - Custom UI (`homebridge-ui/public/`) with `homebridge.i18nCurrentLang()` for
28
+ proper language detection
29
+ - Descriptions for both switch behavior modes (Independent / Single)
30
+ - ConfiguredName characteristic for correct switch names in HomeKit
31
+ - Homebridge v2 compatibility (`^2.0.0-beta.0` in engines)
31
32
 
32
33
  ### 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
34
+ - Switch behavior now has two modes: Independent and Single (removed Master mode)
35
+ - Master switch type defaults to Switch instead of Outlet
36
+ - All devices and switches start collapsed when config UI is opened
37
+ - Services are recreated on each start to ensure fresh names in HomeKit
37
38
 
38
39
  ### 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
40
+ - Dark mode: custom `--ui-*` CSS variables with `@media (prefers-color-scheme: dark)`
41
+ for reliable theme support inside iframe
42
+ - Switch names now correctly appear in HomeKit (cached services kept stale names)
43
+ - Backward compatibility: old configs with `switches` at root level auto-migrate
44
+ to `devices` format
50
45
 
51
46
  ### 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
47
+ - Lightbulb and Fan switch types (HomeKit natively converts switches to these)
48
+ - Master behavior mode (replaced by Master Switch option in Independent mode)
63
49
 
64
50
  ## [1.5.1] - 2026-03-21
65
51
 
package/README.md CHANGED
@@ -1,25 +1,34 @@
1
- # homebridge-multiple-switch
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/azadaydinli/homebridge-multiple-switch/master/banner.png" width="600">
3
+ </p>
2
4
 
3
- ![CI](https://github.com/azadaydinli/homebridge-multiple-switch/actions/workflows/ci.yml/badge.svg)
5
+ <span align="center">
6
+
7
+ # Homebridge Multiple Switch
8
+
9
+ A lightweight Homebridge plugin that lets you create multiple customizable dummy switches in HomeKit. Supports multi-device, master switch, and 14 languages.
10
+
11
+ [![verified-by-homebridge](https://img.shields.io/badge/homebridge-verified-blueviolet?color=%23491F59&style=flat)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
4
12
  [![npm](https://img.shields.io/npm/v/homebridge-multiple-switch)](https://www.npmjs.com/package/homebridge-multiple-switch)
5
- [![GitHub issues](https://img.shields.io/github/issues/azadaydinli/homebridge-multiple-switch)](https://github.com/azadaydinli/homebridge-multiple-switch/issues)
13
+ [![npm](https://img.shields.io/npm/dt/homebridge-multiple-switch)](https://www.npmjs.com/package/homebridge-multiple-switch)
6
14
  [![GitHub license](https://img.shields.io/github/license/azadaydinli/homebridge-multiple-switch)](https://github.com/azadaydinli/homebridge-multiple-switch/blob/master/LICENSE)
7
15
 
8
- A lightweight Homebridge plugin that lets you create multiple customizable dummy switches under a single accessory — configurable as `Switch`, `Outlet`, `Lightbulb`, or `Fan`.
9
- Supports `Independent`, `Master`, and `Single` switch modes.
16
+ </span>
10
17
 
11
18
  ---
12
19
 
13
20
  ## Features
14
21
 
15
- - Grouped multiple switches in one HomeKit tile
16
- - Accessory type: `switch`, `outlet`, `lightbulb`, or `fan`
22
+ - **Multi-device support** create multiple separate HomeKit accessories
23
+ - Accessory type: `switch` or `outlet`
17
24
  - **Independent Mode** – All switches operate separately
18
- - **Master Mode** – One switch controls all others
19
25
  - **Single Mode** – Only one switch can be active at a time
26
+ - **Master Switch** (Independent mode) — one switch controls all others
20
27
  - Per-switch config support (type, auto-off delay, default state)
21
- - Switch states preserved across Homebridge restarts via cached accessories
22
- - Automatic cleanup of stale accessories on config change
28
+ - Collapsible config UI with dark mode support
29
+ - i18n localization (14 languages)
30
+ - Homebridge v2 compatible
31
+ - Switch states preserved across restarts via cached accessories
23
32
  - Compatible with HomeKit and Siri
24
33
 
25
34
  ---
@@ -47,23 +56,40 @@ Configure from Homebridge UI or manually edit `config.json`:
47
56
  ```json
48
57
  {
49
58
  "platform": "MultipleSwitchPlatform",
50
- "name": "Multiple Switches",
51
- "switchBehavior": "single",
52
- "switches": [
59
+ "name": "Multiple Switch Platform",
60
+ "devices": [
53
61
  {
54
- "name": "Heater",
55
- "type": "outlet",
56
- "defaultState": true,
57
- "delayOff": 10000
62
+ "name": "Living Room",
63
+ "switchBehavior": "independent",
64
+ "masterSwitch": true,
65
+ "masterSwitchType": "switch",
66
+ "switches": [
67
+ {
68
+ "name": "Lamp",
69
+ "type": "outlet",
70
+ "defaultState": false,
71
+ "delayOff": 0
72
+ },
73
+ {
74
+ "name": "Heater",
75
+ "type": "switch",
76
+ "delayOff": 10000
77
+ }
78
+ ]
58
79
  },
59
80
  {
60
- "name": "Fan",
61
- "type": "fan"
62
- },
63
- {
64
- "name": "Light",
65
- "type": "lightbulb",
66
- "delayOff": 5000
81
+ "name": "Bedroom",
82
+ "switchBehavior": "single",
83
+ "switches": [
84
+ {
85
+ "name": "Scene 1",
86
+ "type": "switch"
87
+ },
88
+ {
89
+ "name": "Scene 2",
90
+ "type": "switch"
91
+ }
92
+ ]
67
93
  }
68
94
  ]
69
95
  }
@@ -73,19 +99,28 @@ Configure from Homebridge UI or manually edit `config.json`:
73
99
 
74
100
  ### Platform Options
75
101
 
76
- | Field | Type | Required | Description |
77
- |------------------|--------|----------|--------------------------------------|
78
- | `name` | string | Yes | Name of the platform instance |
79
- | `switchBehavior` | string | No | `independent`, `master`, or `single` |
80
- | `switches` | array | Yes | List of switches to create |
102
+ | Field | Type | Required | Description |
103
+ |-----------|--------|----------|-------------------------------|
104
+ | `name` | string | Yes | Name of the platform instance |
105
+ | `devices` | array | Yes | List of devices to create |
106
+
107
+ ### Device Options
108
+
109
+ | Field | Type | Required | Description |
110
+ |--------------------|---------|----------|-------------------------------------------------|
111
+ | `name` | string | Yes | Device name (becomes HomeKit accessory name) |
112
+ | `switchBehavior` | string | No | `independent` or `single` (default: independent) |
113
+ | `masterSwitch` | boolean | No | Enable master switch (Independent mode only) |
114
+ | `masterSwitchType` | string | No | `switch` or `outlet` (default: switch) |
115
+ | `switches` | array | Yes | List of switches for this device |
81
116
 
82
117
  ### Per-Switch Options
83
118
 
84
- | Field | Type | Required | Description |
85
- |----------------|---------|----------|--------------------------------------------------|
86
- | `name` | string | Yes | Name of the switch |
87
- | `type` | string | No | `switch`, `outlet`, `lightbulb`, or `fan` |
88
- | `defaultState` | boolean | No | Initial power state (default: `false`) |
119
+ | Field | Type | Required | Description |
120
+ |----------------|---------|----------|---------------------------------------------------|
121
+ | `name` | string | Yes | Name of the switch |
122
+ | `type` | string | No | `switch` or `outlet` (default: outlet) |
123
+ | `defaultState` | boolean | No | Initial power state (default: `false`) |
89
124
  | `delayOff` | number | No | Auto turn off after N milliseconds (default: `0`) |
90
125
 
91
126
  ---
@@ -95,7 +130,7 @@ Configure from Homebridge UI or manually edit `config.json`:
95
130
  - Simulate smart plugs for automation testing
96
131
  - Trigger HomeKit scenes manually
97
132
  - Create virtual switches for non-HomeKit devices
98
- - Combine several virtual accessories under one tile
133
+ - Group several virtual accessories under one device
99
134
 
100
135
  ---
101
136
 
package/banner.png ADDED
Binary file
@@ -34,6 +34,16 @@
34
34
  { "title": "Single", "enum": ["single"] }
35
35
  ]
36
36
  },
37
+ "switchType": {
38
+ "title": "Switch Type",
39
+ "description": "The HomeKit accessory type for all switches in this device.",
40
+ "type": "string",
41
+ "default": "outlet",
42
+ "oneOf": [
43
+ { "title": "Switch", "enum": ["switch"] },
44
+ { "title": "Outlet", "enum": ["outlet"] }
45
+ ]
46
+ },
37
47
  "masterSwitch": {
38
48
  "title": "Master Switch",
39
49
  "description": "Add a master switch that turns all switches on or off at once. Only available in Single mode.",
@@ -52,16 +62,6 @@
52
62
  "description": "Display name of the switch in HomeKit.",
53
63
  "type": "string"
54
64
  },
55
- "type": {
56
- "title": "Switch Type",
57
- "description": "The HomeKit accessory type for this switch.",
58
- "type": "string",
59
- "default": "outlet",
60
- "oneOf": [
61
- { "title": "Switch", "enum": ["switch"] },
62
- { "title": "Outlet", "enum": ["outlet"] }
63
- ]
64
- },
65
65
  "defaultState": {
66
66
  "title": "Default State",
67
67
  "description": "Initial power state when Homebridge starts.",
@@ -11,7 +11,7 @@
11
11
  "switchName": "اسم المفتاح",
12
12
  "switchNameDesc": "اسم العرض للمفتاح في HomeKit.",
13
13
  "switchType": "نوع المفتاح",
14
- "switchTypeDesc": "نوع ملحق HomeKit لهذا المفتاح.",
14
+ "switchTypeDesc": "نوع ملحق HomeKit لجميع المفاتيح في هذا الجهاز.",
15
15
  "typeSwitch": "مفتاح",
16
16
  "typeOutlet": "مقبس",
17
17
  "defaultState": "الحالة الافتراضية",
@@ -11,7 +11,7 @@
11
11
  "switchName": "Schaltername",
12
12
  "switchNameDesc": "Anzeigename des Schalters in HomeKit.",
13
13
  "switchType": "Schaltertyp",
14
- "switchTypeDesc": "Der HomeKit-Zubehörtyp für diesen Schalter.",
14
+ "switchTypeDesc": "Der HomeKit-Zubehörtyp für alle Schalter in diesem Gerät.",
15
15
  "typeSwitch": "Schalter",
16
16
  "typeOutlet": "Steckdose",
17
17
  "defaultState": "Standardzustand",
@@ -11,7 +11,7 @@
11
11
  "switchName": "Switch Name",
12
12
  "switchNameDesc": "Display name of the switch in HomeKit.",
13
13
  "switchType": "Switch Type",
14
- "switchTypeDesc": "The HomeKit accessory type for this switch.",
14
+ "switchTypeDesc": "The HomeKit accessory type for all switches in this device.",
15
15
  "typeSwitch": "Switch",
16
16
  "typeOutlet": "Outlet",
17
17
  "defaultState": "Default State",
@@ -11,7 +11,7 @@
11
11
  "switchName": "Nombre del interruptor",
12
12
  "switchNameDesc": "Nombre visible del interruptor en HomeKit.",
13
13
  "switchType": "Tipo de interruptor",
14
- "switchTypeDesc": "El tipo de accesorio HomeKit para este interruptor.",
14
+ "switchTypeDesc": "El tipo de accesorio HomeKit para todos los interruptores de este dispositivo.",
15
15
  "typeSwitch": "Interruptor",
16
16
  "typeOutlet": "Enchufe",
17
17
  "defaultState": "Estado predeterminado",
@@ -11,7 +11,7 @@
11
11
  "switchName": "Nom de l'interrupteur",
12
12
  "switchNameDesc": "Nom affiché de l'interrupteur dans HomeKit.",
13
13
  "switchType": "Type d'interrupteur",
14
- "switchTypeDesc": "Le type d'accessoire HomeKit pour cet interrupteur.",
14
+ "switchTypeDesc": "Le type d'accessoire HomeKit pour tous les interrupteurs de cet appareil.",
15
15
  "typeSwitch": "Interrupteur",
16
16
  "typeOutlet": "Prise",
17
17
  "defaultState": "État par défaut",
@@ -11,7 +11,7 @@
11
11
  "switchName": "Nome dell'interruttore",
12
12
  "switchNameDesc": "Nome visualizzato dell'interruttore in HomeKit.",
13
13
  "switchType": "Tipo di interruttore",
14
- "switchTypeDesc": "Il tipo di accessorio HomeKit per questo interruttore.",
14
+ "switchTypeDesc": "Il tipo di accessorio HomeKit per tutti gli interruttori in questo dispositivo.",
15
15
  "typeSwitch": "Interruttore",
16
16
  "typeOutlet": "Presa",
17
17
  "defaultState": "Stato predefinito",
@@ -11,7 +11,7 @@
11
11
  "switchName": "スイッチ名",
12
12
  "switchNameDesc": "HomeKit でのスイッチの表示名。",
13
13
  "switchType": "スイッチタイプ",
14
- "switchTypeDesc": "このスイッチの HomeKit アクセサリタイプ。",
14
+ "switchTypeDesc": "このデバイス内のすべてのスイッチの HomeKit アクセサリタイプ。",
15
15
  "typeSwitch": "スイッチ",
16
16
  "typeOutlet": "コンセント",
17
17
  "defaultState": "デフォルト状態",
@@ -11,7 +11,7 @@
11
11
  "switchName": "스위치 이름",
12
12
  "switchNameDesc": "HomeKit에서 스위치의 표시 이름.",
13
13
  "switchType": "스위치 유형",
14
- "switchTypeDesc": "이 스위치의 HomeKit 액세서리 유형.",
14
+ "switchTypeDesc": "이 장치의 모든 스위치에 적용되는 HomeKit 액세서리 유형.",
15
15
  "typeSwitch": "스위치",
16
16
  "typeOutlet": "콘센트",
17
17
  "defaultState": "기본 상태",
@@ -11,7 +11,7 @@
11
11
  "switchName": "Schakelaarnaam",
12
12
  "switchNameDesc": "Weergavenaam van de schakelaar in HomeKit.",
13
13
  "switchType": "Schakelaartype",
14
- "switchTypeDesc": "Het HomeKit-accessoiretype voor deze schakelaar.",
14
+ "switchTypeDesc": "Het HomeKit-accessoiretype voor alle schakelaars in dit apparaat.",
15
15
  "typeSwitch": "Schakelaar",
16
16
  "typeOutlet": "Stopcontact",
17
17
  "defaultState": "Standaardstatus",
@@ -11,7 +11,7 @@
11
11
  "switchName": "Nazwa przełącznika",
12
12
  "switchNameDesc": "Wyświetlana nazwa przełącznika w HomeKit.",
13
13
  "switchType": "Typ przełącznika",
14
- "switchTypeDesc": "Typ akcesorium HomeKit dla tego przełącznika.",
14
+ "switchTypeDesc": "Typ akcesorium HomeKit dla wszystkich przełączników w tym urządzeniu.",
15
15
  "typeSwitch": "Przełącznik",
16
16
  "typeOutlet": "Gniazdko",
17
17
  "defaultState": "Stan domyślny",
@@ -11,7 +11,7 @@
11
11
  "switchName": "Nome do interruptor",
12
12
  "switchNameDesc": "Nome de exibição do interruptor no HomeKit.",
13
13
  "switchType": "Tipo de interruptor",
14
- "switchTypeDesc": "O tipo de acessório HomeKit para este interruptor.",
14
+ "switchTypeDesc": "O tipo de acessório HomeKit para todos os interruptores deste dispositivo.",
15
15
  "typeSwitch": "Interruptor",
16
16
  "typeOutlet": "Tomada",
17
17
  "defaultState": "Estado padrão",
@@ -11,7 +11,7 @@
11
11
  "switchName": "Название переключателя",
12
12
  "switchNameDesc": "Отображаемое имя переключателя в HomeKit.",
13
13
  "switchType": "Тип переключателя",
14
- "switchTypeDesc": "Тип аксессуара HomeKit для этого переключателя.",
14
+ "switchTypeDesc": "Тип аксессуара HomeKit для всех переключателей в этом устройстве.",
15
15
  "typeSwitch": "Выключатель",
16
16
  "typeOutlet": "Розетка",
17
17
  "defaultState": "Состояние по умолчанию",
@@ -11,7 +11,7 @@
11
11
  "switchName": "Anahtar Adı",
12
12
  "switchNameDesc": "Anahtarın HomeKit'teki görünen adı.",
13
13
  "switchType": "Anahtar Tipi",
14
- "switchTypeDesc": "Bu anahtar için HomeKit aksesuar tipi.",
14
+ "switchTypeDesc": "Bu cihazdaki tüm anahtarlar için HomeKit aksesuar tipi.",
15
15
  "typeSwitch": "Anahtar",
16
16
  "typeOutlet": "Priz",
17
17
  "defaultState": "Varsayılan Durum",
@@ -11,7 +11,7 @@
11
11
  "switchName": "开关名称",
12
12
  "switchNameDesc": "开关在 HomeKit 中的显示名称。",
13
13
  "switchType": "开关类型",
14
- "switchTypeDesc": "此开关的 HomeKit 配件类型。",
14
+ "switchTypeDesc": "此设备中所有开关的 HomeKit 配件类型。",
15
15
  "typeSwitch": "开关",
16
16
  "typeOutlet": "插座",
17
17
  "defaultState": "默认状态",
@@ -181,6 +181,18 @@
181
181
  delete config.switchBehavior;
182
182
  }
183
183
 
184
+ // Migrate per-switch type to device-level switchType
185
+ if (Array.isArray(config.devices)) {
186
+ config.devices.forEach(dev => {
187
+ if (!dev.switchType && Array.isArray(dev.switches) && dev.switches.length > 0) {
188
+ dev.switchType = dev.switches[0].type || 'outlet';
189
+ }
190
+ if (Array.isArray(dev.switches)) {
191
+ dev.switches.forEach(sw => { delete sw.type; });
192
+ }
193
+ });
194
+ }
195
+
184
196
  if (!config.devices) {
185
197
  config.devices = [];
186
198
  }
@@ -387,7 +399,7 @@
387
399
  </div>
388
400
 
389
401
  <div class="device-body ${isOpen ? '' : 'collapsed'}">
390
- <div class="inline-row">
402
+ <div class="inline-row" style="grid-template-columns: 1fr 1fr 1fr">
391
403
  <div class="form-group">
392
404
  <label>${t.deviceName || 'Device Name'}</label>
393
405
  <input type="text" class="dev-field" data-dev="${di}" data-field="name" value="${esc(dev.name || '')}">
@@ -400,6 +412,14 @@
400
412
  </select>
401
413
  <div class="desc" style="margin-top:6px">${behaviorDesc}</div>
402
414
  </div>
415
+ <div class="form-group">
416
+ <label>${t.switchType || 'Switch Type'}</label>
417
+ <div class="desc">${t.switchTypeDesc || ''}</div>
418
+ <select class="dev-field" data-dev="${di}" data-field="switchType">
419
+ <option value="outlet" ${!dev.switchType || dev.switchType === 'outlet' ? 'selected' : ''}>${t.typeOutlet || 'Outlet'}</option>
420
+ <option value="switch" ${dev.switchType === 'switch' ? 'selected' : ''}>${t.typeSwitch || 'Switch'}</option>
421
+ </select>
422
+ </div>
403
423
  </div>
404
424
 
405
425
  ${isIndependent ? `
@@ -424,7 +444,7 @@
424
444
 
425
445
  <div class="section-title" style="margin-top:16px">${t.switches || 'Switches'}</div>
426
446
 
427
- ${switches.map((sw, si) => renderSwitch(sw, di, si)).join('')}
447
+ ${switches.map((sw, si) => renderSwitch(sw, di, si, dev.switchType || 'outlet')).join('')}
428
448
 
429
449
  <button class="btn btn-primary btn-add-switch" data-dev="${di}">+ ${t.addSwitch || 'Add Switch'}</button>
430
450
  </div>
@@ -432,9 +452,10 @@
432
452
  `;
433
453
  }
434
454
 
435
- function renderSwitch(sw, di, si) {
455
+ function renderSwitch(sw, di, si, deviceSwitchType) {
436
456
  const isOpen = expandedSwitches.has(`${di}_${si}`);
437
457
  const swLabel = sw.name || `#${si + 1}`;
458
+ const typeLabel = deviceSwitchType === 'switch' ? (t.typeSwitch || 'Switch') : (t.typeOutlet || 'Outlet');
438
459
 
439
460
  return `
440
461
  <div class="switch-card">
@@ -442,7 +463,7 @@
442
463
  <div class="switch-header-left">
443
464
  <span class="chevron ${isOpen ? 'open' : ''}">&#9654;</span>
444
465
  <strong>${esc(swLabel)}</strong>
445
- ${!isOpen ? `<span class="switch-summary">${sw.type || 'outlet'}${sw.delayOff ? ` / ${sw.delayOff}ms` : ''}</span>` : ''}
466
+ ${!isOpen ? `<span class="switch-summary">${typeLabel}${sw.delayOff ? ` / ${sw.delayOff}ms` : ''}</span>` : ''}
446
467
  </div>
447
468
  <div class="switch-header-right">
448
469
  <button class="btn btn-danger btn-sm btn-remove-switch" data-dev="${di}" data-sw="${si}">${t.removeSwitch || 'Remove'}</button>
@@ -454,25 +475,16 @@
454
475
  <label>${t.switchName || 'Switch Name'}</label>
455
476
  <input type="text" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="name" value="${esc(sw.name || '')}">
456
477
  </div>
457
- <div class="form-group">
458
- <label>${t.switchType || 'Switch Type'}</label>
459
- <select class="sw-field" data-dev="${di}" data-sw="${si}" data-field="type">
460
- <option value="switch" ${sw.type === 'switch' ? 'selected' : ''}>${t.typeSwitch || 'Switch'}</option>
461
- <option value="outlet" ${sw.type === 'outlet' || !sw.type ? 'selected' : ''}>${t.typeOutlet || 'Outlet'}</option>
462
- </select>
463
- </div>
464
- </div>
465
- <div class="inline-row">
466
478
  <div class="form-group">
467
479
  <label>${t.delayOff || 'Auto Turn Off (ms)'}</label>
468
480
  <input type="number" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="delayOff" min="0" value="${sw.delayOff || 0}">
469
481
  </div>
470
- <div class="form-group">
471
- <label>${t.defaultState || 'Default State'}</label>
472
- <div class="toggle-wrap" style="margin-top:6px">
473
- <input type="checkbox" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="defaultState" ${sw.defaultState ? 'checked' : ''}>
474
- <span>${sw.defaultState ? (t.on || 'On') : (t.off || 'Off')}</span>
475
- </div>
482
+ </div>
483
+ <div class="form-group">
484
+ <label>${t.defaultState || 'Default State'}</label>
485
+ <div class="toggle-wrap" style="margin-top:6px">
486
+ <input type="checkbox" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="defaultState" ${sw.defaultState ? 'checked' : ''}>
487
+ <span>${sw.defaultState ? (t.on || 'On') : (t.off || 'Off')}</span>
476
488
  </div>
477
489
  </div>
478
490
  </div>
package/index.js CHANGED
@@ -113,71 +113,43 @@ class MultipleSwitchPlatform {
113
113
  }
114
114
 
115
115
  reconcileServices(accessory, device, switches, services, hasMaster) {
116
- // Remove ALL existing subtype services (ensures fresh names and correct order)
116
+ // Remove ALL existing subtype services (ensures fresh names)
117
117
  const subtypeServices = accessory.services.filter((s) => s.subtype);
118
118
  subtypeServices.forEach((s) => accessory.removeService(s));
119
119
 
120
- // Remove existing ServiceLabel if present, then re-add to ensure ordering
121
- const existingLabel = accessory.services.find(
122
- (s) => s.UUID === this.Service.ServiceLabel.UUID
123
- );
124
- if (existingLabel) accessory.removeService(existingLabel);
125
-
126
- const labelService = accessory.addService(this.Service.ServiceLabel);
127
- labelService.setCharacteristic(
128
- this.Characteristic.ServiceLabelNamespace,
129
- this.Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS
130
- );
131
-
132
- let labelIndex = 1;
133
- const orderedServices = [];
134
-
135
- // 1. Create master switch FIRST if enabled (appears at top in HomeKit)
120
+ // Create master switch if enabled
136
121
  if (hasMaster) {
137
122
  const MasterServiceClass = this.getServiceClass(device.masterSwitchType || 'switch');
138
123
  const masterService = accessory.addService(MasterServiceClass, 'Master', MASTER_SUBTYPE);
139
124
 
140
125
  this.setServiceName(masterService, 'Master');
141
- masterService.addOptionalCharacteristic(this.Characteristic.ServiceLabelIndex);
142
- masterService.setCharacteristic(this.Characteristic.ServiceLabelIndex, labelIndex++);
143
126
  this.configureMasterHandler(accessory, masterService, services);
144
127
 
145
128
  services.set(MASTER_SUBTYPE, masterService);
146
- orderedServices.push(masterService);
147
129
 
148
130
  if (accessory.context.switchStates[MASTER_SUBTYPE] === undefined) {
149
131
  accessory.context.switchStates[MASTER_SUBTYPE] = false;
150
132
  }
151
133
  }
152
134
 
153
- // 2. Create regular switches in config order
135
+ // Create regular switches
136
+ const switchType = device.switchType || (switches[0] && switches[0].type) || 'outlet';
154
137
  switches.forEach((sw, index) => {
155
138
  const subtype = `switch_${index}`;
156
139
 
157
- const ServiceClass = this.getServiceClass(sw.type);
140
+ const ServiceClass = this.getServiceClass(switchType);
158
141
  const service = accessory.addService(ServiceClass, sw.name, subtype);
159
142
 
160
143
  this.setServiceName(service, sw.name);
161
- service.addOptionalCharacteristic(this.Characteristic.ServiceLabelIndex);
162
- service.setCharacteristic(this.Characteristic.ServiceLabelIndex, labelIndex++);
163
144
  this.configureSwitchHandlers(accessory, service, sw, subtype, services);
164
145
 
165
146
  services.set(subtype, service);
166
- orderedServices.push(service);
167
147
 
168
148
  if (accessory.context.switchStates[subtype] === undefined) {
169
149
  accessory.context.switchStates[subtype] = sw.defaultState || false;
170
150
  }
171
151
  });
172
152
 
173
- // Link all services to ServiceLabel for HomeKit ordering
174
- for (const svc of orderedServices) {
175
- labelService.addLinkedService(svc);
176
- }
177
-
178
- // Log service order for debugging
179
- this.log.info(`[${device.name}] Service order: ${orderedServices.map((s, i) => `${i + 1}. ${s.displayName}`).join(', ')}`);
180
-
181
153
  // Clean up states for removed switches
182
154
  const activeKeys = new Set(services.keys());
183
155
  for (const key of Object.keys(accessory.context.switchStates)) {
package/logo.png ADDED
Binary file
package/logo.svg ADDED
@@ -0,0 +1,102 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#1a1a2e"/>
5
+ <stop offset="50%" style="stop-color:#16213e"/>
6
+ <stop offset="100%" style="stop-color:#0f3460"/>
7
+ </linearGradient>
8
+ <linearGradient id="glowGreen" x1="0%" y1="0%" x2="0%" y2="100%">
9
+ <stop offset="0%" style="stop-color:#00ff88"/>
10
+ <stop offset="100%" style="stop-color:#00cc6a"/>
11
+ </linearGradient>
12
+ <linearGradient id="glowAmber" x1="0%" y1="0%" x2="0%" y2="100%">
13
+ <stop offset="0%" style="stop-color:#ffb347"/>
14
+ <stop offset="100%" style="stop-color:#ff8c00"/>
15
+ </linearGradient>
16
+ <linearGradient id="glowBlue" x1="0%" y1="0%" x2="0%" y2="100%">
17
+ <stop offset="0%" style="stop-color:#00d4ff"/>
18
+ <stop offset="100%" style="stop-color:#0099cc"/>
19
+ </linearGradient>
20
+ <linearGradient id="ringGrad" x1="0%" y1="0%" x2="100%" y2="100%">
21
+ <stop offset="0%" style="stop-color:rgba(255,255,255,0.15)"/>
22
+ <stop offset="100%" style="stop-color:rgba(255,255,255,0.03)"/>
23
+ </linearGradient>
24
+ <filter id="glowFilter1">
25
+ <feGaussianBlur stdDeviation="12" result="blur"/>
26
+ <feMerge>
27
+ <feMergeNode in="blur"/>
28
+ <feMergeNode in="SourceGraphic"/>
29
+ </feMerge>
30
+ </filter>
31
+ <filter id="glowFilter2">
32
+ <feGaussianBlur stdDeviation="8" result="blur"/>
33
+ <feMerge>
34
+ <feMergeNode in="blur"/>
35
+ <feMergeNode in="SourceGraphic"/>
36
+ </feMerge>
37
+ </filter>
38
+ <filter id="softShadow">
39
+ <feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="#000" flood-opacity="0.4"/>
40
+ </filter>
41
+ </defs>
42
+
43
+ <!-- Background -->
44
+ <rect width="512" height="512" rx="112" fill="url(#bg)"/>
45
+
46
+ <!-- Subtle grid pattern -->
47
+ <line x1="256" y1="80" x2="256" y2="432" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
48
+ <line x1="80" y1="256" x2="432" y2="256" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
49
+
50
+ <!-- Central hub ring -->
51
+ <circle cx="256" cy="256" r="155" fill="none" stroke="url(#ringGrad)" stroke-width="2"/>
52
+ <circle cx="256" cy="256" r="90" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="1.5"/>
53
+
54
+ <!-- Connection lines from center to switches -->
55
+ <line x1="256" y1="256" x2="160" y2="160" stroke="rgba(0,255,136,0.25)" stroke-width="2" stroke-dasharray="4,4"/>
56
+ <line x1="256" y1="256" x2="352" y2="160" stroke="rgba(0,212,255,0.25)" stroke-width="2" stroke-dasharray="4,4"/>
57
+ <line x1="256" y1="256" x2="160" y2="352" stroke="rgba(255,179,71,0.25)" stroke-width="2" stroke-dasharray="4,4"/>
58
+ <line x1="256" y1="256" x2="352" y2="352" stroke="rgba(255,255,255,0.1)" stroke-width="2" stroke-dasharray="4,4"/>
59
+
60
+ <!-- Switch 1 — Top Left — Green (ON) -->
61
+ <g filter="url(#glowFilter1)">
62
+ <circle cx="160" cy="160" r="8" fill="#00ff88" opacity="0.3"/>
63
+ </g>
64
+ <circle cx="160" cy="160" r="38" fill="rgba(0,255,136,0.08)" stroke="rgba(0,255,136,0.4)" stroke-width="2"/>
65
+ <circle cx="160" cy="160" r="22" fill="rgba(0,255,136,0.12)" stroke="rgba(0,255,136,0.6)" stroke-width="1.5"/>
66
+ <!-- Power icon -->
67
+ <line x1="160" y1="146" x2="160" y2="155" stroke="#00ff88" stroke-width="3" stroke-linecap="round"/>
68
+ <path d="M 149 153 A 14 14 0 1 0 171 153" fill="none" stroke="#00ff88" stroke-width="2.5" stroke-linecap="round"/>
69
+
70
+ <!-- Switch 2 — Top Right — Blue (ON) -->
71
+ <g filter="url(#glowFilter1)">
72
+ <circle cx="352" cy="160" r="8" fill="#00d4ff" opacity="0.3"/>
73
+ </g>
74
+ <circle cx="352" cy="160" r="38" fill="rgba(0,212,255,0.08)" stroke="rgba(0,212,255,0.4)" stroke-width="2"/>
75
+ <circle cx="352" cy="160" r="22" fill="rgba(0,212,255,0.12)" stroke="rgba(0,212,255,0.6)" stroke-width="1.5"/>
76
+ <!-- Power icon -->
77
+ <line x1="352" y1="146" x2="352" y2="155" stroke="#00d4ff" stroke-width="3" stroke-linecap="round"/>
78
+ <path d="M 341 153 A 14 14 0 1 0 363 153" fill="none" stroke="#00d4ff" stroke-width="2.5" stroke-linecap="round"/>
79
+
80
+ <!-- Switch 3 — Bottom Left — Amber (ON) -->
81
+ <g filter="url(#glowFilter1)">
82
+ <circle cx="160" cy="352" r="8" fill="#ffb347" opacity="0.3"/>
83
+ </g>
84
+ <circle cx="160" cy="352" r="38" fill="rgba(255,179,71,0.08)" stroke="rgba(255,179,71,0.4)" stroke-width="2"/>
85
+ <circle cx="160" cy="352" r="22" fill="rgba(255,179,71,0.12)" stroke="rgba(255,179,71,0.6)" stroke-width="1.5"/>
86
+ <!-- Power icon -->
87
+ <line x1="160" y1="338" x2="160" y2="347" stroke="#ffb347" stroke-width="3" stroke-linecap="round"/>
88
+ <path d="M 149 345 A 14 14 0 1 0 171 345" fill="none" stroke="#ffb347" stroke-width="2.5" stroke-linecap="round"/>
89
+
90
+ <!-- Switch 4 — Bottom Right — OFF -->
91
+ <circle cx="352" cy="352" r="38" fill="rgba(255,255,255,0.03)" stroke="rgba(255,255,255,0.12)" stroke-width="2"/>
92
+ <circle cx="352" cy="352" r="22" fill="rgba(255,255,255,0.04)" stroke="rgba(255,255,255,0.15)" stroke-width="1.5"/>
93
+ <!-- Power icon (dimmed) -->
94
+ <line x1="352" y1="338" x2="352" y2="347" stroke="rgba(255,255,255,0.2)" stroke-width="3" stroke-linecap="round"/>
95
+ <path d="M 341 345 A 14 14 0 1 0 363 345" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="2.5" stroke-linecap="round"/>
96
+
97
+ <!-- Center hub -->
98
+ <circle cx="256" cy="256" r="32" fill="rgba(255,255,255,0.06)" stroke="rgba(255,255,255,0.15)" stroke-width="2"/>
99
+ <circle cx="256" cy="256" r="18" fill="rgba(255,255,255,0.08)" stroke="rgba(255,255,255,0.2)" stroke-width="1.5"/>
100
+ <!-- HomeKit house icon in center -->
101
+ <path d="M 245 260 L 256 249 L 267 260 L 267 270 L 261 270 L 261 263 L 251 263 L 251 270 L 245 270 Z" fill="rgba(255,255,255,0.7)" filter="url(#softShadow)"/>
102
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-multiple-switch",
3
- "version": "1.6.0-beta.9",
3
+ "version": "1.7.0-beta.1",
4
4
  "description": "Multiple switch platform for Homebridge",
5
5
  "homepage": "https://github.com/azadaydinli/homebridge-multiple-switch",
6
6
  "main": "index.js",