homebridge-multiple-switch 1.6.0 → 1.7.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/CHANGELOG.md +65 -0
- package/README.md +70 -35
- package/banner.png +0 -0
- package/config.schema.json +19 -14
- package/homebridge-ui/public/i18n/ar.json +4 -4
- package/homebridge-ui/public/i18n/de.json +4 -4
- package/homebridge-ui/public/i18n/en.json +4 -4
- package/homebridge-ui/public/i18n/es.json +4 -4
- package/homebridge-ui/public/i18n/fr.json +4 -4
- package/homebridge-ui/public/i18n/it.json +4 -4
- package/homebridge-ui/public/i18n/ja.json +4 -4
- package/homebridge-ui/public/i18n/ko.json +4 -4
- package/homebridge-ui/public/i18n/nl.json +4 -4
- package/homebridge-ui/public/i18n/pl.json +4 -4
- package/homebridge-ui/public/i18n/pt.json +4 -4
- package/homebridge-ui/public/i18n/ru.json +4 -4
- package/homebridge-ui/public/i18n/tr.json +4 -4
- package/homebridge-ui/public/i18n/zh-CN.json +4 -4
- package/homebridge-ui/public/index.html +45 -48
- package/index.js +25 -5
- package/logo.png +0 -0
- package/logo.svg +102 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.7.0-beta.10] - 2026-05-17
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Auto-off timer leak: rapidly toggling a switch no longer accumulates stale timeouts — each new auto-off cancels the previous one
|
|
7
|
+
- Duplicate device name now logs a clear warning instead of silently causing UUID collision
|
|
8
|
+
|
|
9
|
+
## [1.7.0-beta.9] - 2026-05-17
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Default State is now a 3-option dropdown: **Remember Last State** / **On** / **Off**
|
|
13
|
+
- Remember: keeps the last known state across restarts (new default)
|
|
14
|
+
- On: always starts ON on every Homebridge restart
|
|
15
|
+
- Off: always starts OFF on every Homebridge restart
|
|
16
|
+
- Backward compatible: old boolean `true`/`false` configs still work correctly
|
|
17
|
+
|
|
18
|
+
## [1.7.0-beta.8] - 2026-05-17
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- Default State now applies on every Homebridge restart, not just on first creation — switches always start in their configured default state
|
|
22
|
+
|
|
23
|
+
## [1.7.0-beta.7] - 2026-05-17
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- `"type"` removed from switch schema `required` array (field no longer exists at switch level)
|
|
27
|
+
- New devices and switches no longer include stale `type` field on creation
|
|
28
|
+
- Removed unused `masterSwitchType` key from all 14 i18n locale files
|
|
29
|
+
|
|
30
|
+
## [1.7.0-beta.6] - 2026-05-17
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- Master Switch now uses the device-level switch type automatically — separate type selector removed
|
|
34
|
+
|
|
35
|
+
### Removed
|
|
36
|
+
- Master Switch Type selector from UI and schema (`masterSwitchType` config field is no longer used)
|
|
37
|
+
|
|
38
|
+
## [1.7.0-beta.5] - 2026-05-17
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- Switch fields (Switch Name, Auto Turn Off, Default State) now use the same 3-equal-column grid as device fields
|
|
42
|
+
|
|
43
|
+
## [1.7.0-beta.4] - 2026-05-17
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- Auto Turn Off column narrowed to 120px so Default State checkbox has more breathing room
|
|
47
|
+
|
|
48
|
+
## [1.7.0-beta.3] - 2026-05-17
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
- Switch card: Switch Name, Auto Turn Off and Default State are now on a single compact row
|
|
52
|
+
|
|
53
|
+
## [1.7.0-beta.2] - 2026-05-17
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
- Switch Type description now appears below the dropdown, consistent with Switch Behavior Mode
|
|
57
|
+
|
|
58
|
+
## [1.7.0-beta.1] - 2026-05-17
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
- Switch type (Switch / Outlet) is now configured per device instead of per individual switch
|
|
62
|
+
- All switches within a device share the same type, set once in the device settings
|
|
63
|
+
- Switch cards no longer show a type selector — type is shown in the collapsed summary from the device level
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
- 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)
|
|
67
|
+
|
|
3
68
|
## [1.6.0] - 2026-03-22
|
|
4
69
|
|
|
5
70
|
### Added
|
package/README.md
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
+
[](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
|
|
4
12
|
[](https://www.npmjs.com/package/homebridge-multiple-switch)
|
|
5
|
-
[](https://www.npmjs.com/package/homebridge-multiple-switch)
|
|
6
14
|
[](https://github.com/azadaydinli/homebridge-multiple-switch/blob/master/LICENSE)
|
|
7
15
|
|
|
8
|
-
|
|
9
|
-
Supports `Independent`, `Master`, and `Single` switch modes.
|
|
16
|
+
</span>
|
|
10
17
|
|
|
11
18
|
---
|
|
12
19
|
|
|
13
20
|
## Features
|
|
14
21
|
|
|
15
|
-
-
|
|
16
|
-
- Accessory type: `switch
|
|
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
|
-
-
|
|
22
|
-
-
|
|
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
|
|
51
|
-
"
|
|
52
|
-
"switches": [
|
|
59
|
+
"name": "Multiple Switch Platform",
|
|
60
|
+
"devices": [
|
|
53
61
|
{
|
|
54
|
-
"name": "
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
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": "
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
77
|
-
|
|
78
|
-
| `name`
|
|
79
|
-
| `
|
|
80
|
-
|
|
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
|
|
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
|
-
-
|
|
133
|
+
- Group several virtual accessories under one device
|
|
99
134
|
|
|
100
135
|
---
|
|
101
136
|
|
package/banner.png
ADDED
|
Binary file
|
package/config.schema.json
CHANGED
|
@@ -34,9 +34,19 @@
|
|
|
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
|
-
"description": "Add a master switch that turns all switches on or off at once.
|
|
49
|
+
"description": "Add a master switch that turns all switches on or off at once. Uses the same type as the device.",
|
|
40
50
|
"type": "boolean",
|
|
41
51
|
"default": false
|
|
42
52
|
},
|
|
@@ -52,22 +62,17 @@
|
|
|
52
62
|
"description": "Display name of the switch in HomeKit.",
|
|
53
63
|
"type": "string"
|
|
54
64
|
},
|
|
55
|
-
"
|
|
56
|
-
"title": "
|
|
57
|
-
"description": "
|
|
65
|
+
"defaultState": {
|
|
66
|
+
"title": "Default State",
|
|
67
|
+
"description": "Switch state on every Homebridge restart.",
|
|
58
68
|
"type": "string",
|
|
59
|
-
"default": "
|
|
69
|
+
"default": "remember",
|
|
60
70
|
"oneOf": [
|
|
61
|
-
{ "title": "
|
|
62
|
-
{ "title": "
|
|
71
|
+
{ "title": "Remember Last State", "enum": ["remember"] },
|
|
72
|
+
{ "title": "On", "enum": ["on"] },
|
|
73
|
+
{ "title": "Off", "enum": ["off"] }
|
|
63
74
|
]
|
|
64
75
|
},
|
|
65
|
-
"defaultState": {
|
|
66
|
-
"title": "Default State",
|
|
67
|
-
"description": "Initial power state when Homebridge starts.",
|
|
68
|
-
"type": "boolean",
|
|
69
|
-
"default": false
|
|
70
|
-
},
|
|
71
76
|
"delayOff": {
|
|
72
77
|
"title": "Auto Turn Off (ms)",
|
|
73
78
|
"description": "Automatically turn off after this many milliseconds. Set to 0 to disable.",
|
|
@@ -76,7 +81,7 @@
|
|
|
76
81
|
"minimum": 0
|
|
77
82
|
}
|
|
78
83
|
},
|
|
79
|
-
"required": ["name"
|
|
84
|
+
"required": ["name"]
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
},
|
|
@@ -11,11 +11,12 @@
|
|
|
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": "الحالة الافتراضية",
|
|
18
|
-
"defaultStateDesc": "حالة
|
|
18
|
+
"defaultStateDesc": "حالة المفتاح عند كل إعادة تشغيل لـ Homebridge.",
|
|
19
|
+
"remember": "تذكر آخر حالة",
|
|
19
20
|
"delayOff": "إيقاف تلقائي (مللي ثانية)",
|
|
20
21
|
"delayOffDesc": "يتم الإيقاف تلقائيًا بعد هذا العدد من المللي ثانية. اضبط على 0 للتعطيل.",
|
|
21
22
|
"addSwitch": "إضافة مفتاح",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "يمكن تشغيل مفتاح واحد فقط في نفس الوقت. تشغيل واحد يقوم بإيقاف جميع الآخرين تلقائياً.",
|
|
34
35
|
"masterSwitch": "المفتاح الرئيسي",
|
|
35
36
|
"masterSwitchDesc": "يضيف مفتاحاً رئيسياً يقوم بتشغيل أو إيقاف جميع المفاتيح دفعة واحدة.",
|
|
36
|
-
"switchSingular": "مفتاح"
|
|
37
|
-
"masterSwitchType": "نوع المفتاح الرئيسي"
|
|
37
|
+
"switchSingular": "مفتاح"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
"switchName": "Schaltername",
|
|
12
12
|
"switchNameDesc": "Anzeigename des Schalters in HomeKit.",
|
|
13
13
|
"switchType": "Schaltertyp",
|
|
14
|
-
"switchTypeDesc": "Der HomeKit-Zubehörtyp für
|
|
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",
|
|
18
|
-
"defaultStateDesc": "
|
|
18
|
+
"defaultStateDesc": "Schaltzustand bei jedem Neustart von Homebridge.",
|
|
19
|
+
"remember": "Letzten Zustand merken",
|
|
19
20
|
"delayOff": "Automatisch ausschalten (ms)",
|
|
20
21
|
"delayOffDesc": "Schaltet nach dieser Anzahl von Millisekunden automatisch aus. Zum Deaktivieren auf 0 setzen.",
|
|
21
22
|
"addSwitch": "Schalter hinzufügen",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "Es kann nur ein Schalter gleichzeitig eingeschaltet sein. Das Einschalten eines Schalters schaltet alle anderen automatisch aus.",
|
|
34
35
|
"masterSwitch": "Hauptschalter",
|
|
35
36
|
"masterSwitchDesc": "Fügt einen Hauptschalter hinzu, der alle Schalter auf einmal ein- oder ausschaltet.",
|
|
36
|
-
"switchSingular": "Schalter"
|
|
37
|
-
"masterSwitchType": "Hauptschaltertyp"
|
|
37
|
+
"switchSingular": "Schalter"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
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
|
|
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",
|
|
18
|
-
"defaultStateDesc": "
|
|
18
|
+
"defaultStateDesc": "Switch state on every Homebridge restart.",
|
|
19
|
+
"remember": "Remember Last State",
|
|
19
20
|
"delayOff": "Auto Turn Off (ms)",
|
|
20
21
|
"delayOffDesc": "Automatically turn off after this many milliseconds. Set to 0 to disable.",
|
|
21
22
|
"addSwitch": "Add Switch",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "Only one switch can be on at a time. Turning one on automatically turns off all others.",
|
|
34
35
|
"masterSwitch": "Master Switch",
|
|
35
36
|
"masterSwitchDesc": "Adds a master switch that turns all switches on or off at once.",
|
|
36
|
-
"switchSingular": "switch"
|
|
37
|
-
"masterSwitchType": "Master Switch Type"
|
|
37
|
+
"switchSingular": "switch"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
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
|
|
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",
|
|
18
|
-
"defaultStateDesc": "Estado
|
|
18
|
+
"defaultStateDesc": "Estado del interruptor en cada reinicio de Homebridge.",
|
|
19
|
+
"remember": "Recordar último estado",
|
|
19
20
|
"delayOff": "Apagado automático (ms)",
|
|
20
21
|
"delayOffDesc": "Se apaga automáticamente después de estos milisegundos. Establecer en 0 para desactivar.",
|
|
21
22
|
"addSwitch": "Agregar interruptor",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "Solo un interruptor puede estar encendido a la vez. Encender uno apaga automáticamente todos los demás.",
|
|
34
35
|
"masterSwitch": "Interruptor maestro",
|
|
35
36
|
"masterSwitchDesc": "Agrega un interruptor maestro que enciende o apaga todos los interruptores a la vez.",
|
|
36
|
-
"switchSingular": "interruptor"
|
|
37
|
-
"masterSwitchType": "Tipo de interruptor maestro"
|
|
37
|
+
"switchSingular": "interruptor"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
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
|
|
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",
|
|
18
|
-
"defaultStateDesc": "État
|
|
18
|
+
"defaultStateDesc": "État de l'interrupteur à chaque redémarrage de Homebridge.",
|
|
19
|
+
"remember": "Mémoriser le dernier état",
|
|
19
20
|
"delayOff": "Arrêt automatique (ms)",
|
|
20
21
|
"delayOffDesc": "S'éteint automatiquement après ce nombre de millisecondes. Mettre à 0 pour désactiver.",
|
|
21
22
|
"addSwitch": "Ajouter un interrupteur",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "Un seul interrupteur peut être activé à la fois. En activer un désactive automatiquement tous les autres.",
|
|
34
35
|
"masterSwitch": "Interrupteur principal",
|
|
35
36
|
"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"
|
|
37
|
+
"switchSingular": "interrupteur"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
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
|
|
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",
|
|
18
|
-
"defaultStateDesc": "Stato
|
|
18
|
+
"defaultStateDesc": "Stato dell'interruttore ad ogni riavvio di Homebridge.",
|
|
19
|
+
"remember": "Ricorda ultimo stato",
|
|
19
20
|
"delayOff": "Spegnimento automatico (ms)",
|
|
20
21
|
"delayOffDesc": "Si spegne automaticamente dopo questi millisecondi. Impostare a 0 per disattivare.",
|
|
21
22
|
"addSwitch": "Aggiungi interruttore",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "Solo un interruttore può essere acceso alla volta. Accenderne uno spegne automaticamente tutti gli altri.",
|
|
34
35
|
"masterSwitch": "Interruttore principale",
|
|
35
36
|
"masterSwitchDesc": "Aggiunge un interruttore principale che accende o spegne tutti gli interruttori contemporaneamente.",
|
|
36
|
-
"switchSingular": "interruttore"
|
|
37
|
-
"masterSwitchType": "Tipo di interruttore principale"
|
|
37
|
+
"switchSingular": "interruttore"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
"switchName": "スイッチ名",
|
|
12
12
|
"switchNameDesc": "HomeKit でのスイッチの表示名。",
|
|
13
13
|
"switchType": "スイッチタイプ",
|
|
14
|
-
"switchTypeDesc": "
|
|
14
|
+
"switchTypeDesc": "このデバイス内のすべてのスイッチの HomeKit アクセサリタイプ。",
|
|
15
15
|
"typeSwitch": "スイッチ",
|
|
16
16
|
"typeOutlet": "コンセント",
|
|
17
17
|
"defaultState": "デフォルト状態",
|
|
18
|
-
"defaultStateDesc": "Homebridge
|
|
18
|
+
"defaultStateDesc": "Homebridge を再起動するたびのスイッチ状態。",
|
|
19
|
+
"remember": "最後の状態を記憶",
|
|
19
20
|
"delayOff": "自動オフ(ミリ秒)",
|
|
20
21
|
"delayOffDesc": "指定したミリ秒後に自動的にオフになります。無効にするには 0 に設定してください。",
|
|
21
22
|
"addSwitch": "スイッチを追加",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "同時に1つのスイッチのみオンにできます。1つをオンにすると他はすべて自動的にオフになります。",
|
|
34
35
|
"masterSwitch": "マスタースイッチ",
|
|
35
36
|
"masterSwitchDesc": "すべてのスイッチを一度にオン/オフするマスタースイッチを追加します。",
|
|
36
|
-
"switchSingular": "スイッチ"
|
|
37
|
-
"masterSwitchType": "マスタースイッチタイプ"
|
|
37
|
+
"switchSingular": "スイッチ"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
"switchName": "스위치 이름",
|
|
12
12
|
"switchNameDesc": "HomeKit에서 스위치의 표시 이름.",
|
|
13
13
|
"switchType": "스위치 유형",
|
|
14
|
-
"switchTypeDesc": "이
|
|
14
|
+
"switchTypeDesc": "이 장치의 모든 스위치에 적용되는 HomeKit 액세서리 유형.",
|
|
15
15
|
"typeSwitch": "스위치",
|
|
16
16
|
"typeOutlet": "콘센트",
|
|
17
17
|
"defaultState": "기본 상태",
|
|
18
|
-
"defaultStateDesc": "Homebridge
|
|
18
|
+
"defaultStateDesc": "Homebridge를 재시작할 때마다의 스위치 상태.",
|
|
19
|
+
"remember": "마지막 상태 기억",
|
|
19
20
|
"delayOff": "자동 꺼짐 (ms)",
|
|
20
21
|
"delayOffDesc": "지정된 밀리초 후 자동으로 꺼집니다. 비활성화하려면 0으로 설정하세요.",
|
|
21
22
|
"addSwitch": "스위치 추가",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "한 번에 하나의 스위치만 켤 수 있습니다. 하나를 켜면 다른 모든 것이 자동으로 꺼집니다.",
|
|
34
35
|
"masterSwitch": "마스터 스위치",
|
|
35
36
|
"masterSwitchDesc": "모든 스위치를 한 번에 켜거나 끄는 마스터 스위치를 추가합니다.",
|
|
36
|
-
"switchSingular": "스위치"
|
|
37
|
-
"masterSwitchType": "마스터 스위치 유형"
|
|
37
|
+
"switchSingular": "스위치"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
"switchName": "Schakelaarnaam",
|
|
12
12
|
"switchNameDesc": "Weergavenaam van de schakelaar in HomeKit.",
|
|
13
13
|
"switchType": "Schakelaartype",
|
|
14
|
-
"switchTypeDesc": "Het HomeKit-accessoiretype voor
|
|
14
|
+
"switchTypeDesc": "Het HomeKit-accessoiretype voor alle schakelaars in dit apparaat.",
|
|
15
15
|
"typeSwitch": "Schakelaar",
|
|
16
16
|
"typeOutlet": "Stopcontact",
|
|
17
17
|
"defaultState": "Standaardstatus",
|
|
18
|
-
"defaultStateDesc": "
|
|
18
|
+
"defaultStateDesc": "Schakelaarstatus bij elke herstart van Homebridge.",
|
|
19
|
+
"remember": "Laatste status onthouden",
|
|
19
20
|
"delayOff": "Automatisch uitschakelen (ms)",
|
|
20
21
|
"delayOffDesc": "Schakelt automatisch uit na dit aantal milliseconden. Stel in op 0 om uit te schakelen.",
|
|
21
22
|
"addSwitch": "Schakelaar toevoegen",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "Er kan slechts één schakelaar tegelijk aan staan. Het inschakelen van één schakelaar schakelt alle andere automatisch uit.",
|
|
34
35
|
"masterSwitch": "Hoofdschakelaar",
|
|
35
36
|
"masterSwitchDesc": "Voegt een hoofdschakelaar toe die alle schakelaars in één keer in- of uitschakelt.",
|
|
36
|
-
"switchSingular": "schakelaar"
|
|
37
|
-
"masterSwitchType": "Type hoofdschakelaar"
|
|
37
|
+
"switchSingular": "schakelaar"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
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
|
|
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",
|
|
18
|
-
"defaultStateDesc": "
|
|
18
|
+
"defaultStateDesc": "Stan przełącznika przy każdym ponownym uruchomieniu Homebridge.",
|
|
19
|
+
"remember": "Zapamiętaj ostatni stan",
|
|
19
20
|
"delayOff": "Automatyczne wyłączenie (ms)",
|
|
20
21
|
"delayOffDesc": "Automatycznie wyłącza się po podanej liczbie milisekund. Ustaw 0, aby wyłączyć.",
|
|
21
22
|
"addSwitch": "Dodaj przełącznik",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "Tylko jeden przełącznik może być włączony jednocześnie. Włączenie jednego automatycznie wyłącza wszystkie pozostałe.",
|
|
34
35
|
"masterSwitch": "Przełącznik główny",
|
|
35
36
|
"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"
|
|
37
|
+
"switchSingular": "przełącznik"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
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
|
|
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",
|
|
18
|
-
"defaultStateDesc": "Estado
|
|
18
|
+
"defaultStateDesc": "Estado do interruptor em cada reinício do Homebridge.",
|
|
19
|
+
"remember": "Lembrar último estado",
|
|
19
20
|
"delayOff": "Desligamento automático (ms)",
|
|
20
21
|
"delayOffDesc": "Desliga automaticamente após estes milissegundos. Defina como 0 para desativar.",
|
|
21
22
|
"addSwitch": "Adicionar interruptor",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "Apenas um interruptor pode estar ligado por vez. Ligar um desliga automaticamente todos os outros.",
|
|
34
35
|
"masterSwitch": "Interruptor principal",
|
|
35
36
|
"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"
|
|
37
|
+
"switchSingular": "interruptor"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
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": "Состояние по умолчанию",
|
|
18
|
-
"defaultStateDesc": "
|
|
18
|
+
"defaultStateDesc": "Состояние переключателя при каждом перезапуске Homebridge.",
|
|
19
|
+
"remember": "Запомнить последнее состояние",
|
|
19
20
|
"delayOff": "Автоматическое выключение (мс)",
|
|
20
21
|
"delayOffDesc": "Автоматически выключается через указанное количество миллисекунд. Установите 0 для отключения.",
|
|
21
22
|
"addSwitch": "Добавить переключатель",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "Одновременно может быть включён только один переключатель. Включение одного автоматически выключает все остальные.",
|
|
34
35
|
"masterSwitch": "Главный переключатель",
|
|
35
36
|
"masterSwitchDesc": "Добавляет главный переключатель, который включает или выключает все переключатели одновременно.",
|
|
36
|
-
"switchSingular": "переключатель"
|
|
37
|
-
"masterSwitchType": "Тип главного переключателя"
|
|
37
|
+
"switchSingular": "переключатель"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
"switchName": "Anahtar Adı",
|
|
12
12
|
"switchNameDesc": "Anahtarın HomeKit'teki görünen adı.",
|
|
13
13
|
"switchType": "Anahtar Tipi",
|
|
14
|
-
"switchTypeDesc": "Bu
|
|
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",
|
|
18
|
-
"defaultStateDesc": "Homebridge
|
|
18
|
+
"defaultStateDesc": "Her Homebridge yeniden başlatmasında anahtarın durumu.",
|
|
19
|
+
"remember": "Son Durumu Hatırla",
|
|
19
20
|
"delayOff": "Otomatik Kapanma (ms)",
|
|
20
21
|
"delayOffDesc": "Belirtilen milisaniye sonra otomatik kapanır. Devre dışı bırakmak için 0 yazın.",
|
|
21
22
|
"addSwitch": "Anahtar Ekle",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "Aynı anda yalnızca bir anahtar açık olabilir. Birini açmak diğerlerini otomatik kapatır.",
|
|
34
35
|
"masterSwitch": "Ana Anahtar",
|
|
35
36
|
"masterSwitchDesc": "Tüm anahtarları tek seferde açan veya kapatan bir ana anahtar ekler.",
|
|
36
|
-
"switchSingular": "anahtar"
|
|
37
|
-
"masterSwitchType": "Ana Anahtar Tipi"
|
|
37
|
+
"switchSingular": "anahtar"
|
|
38
38
|
}
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
"switchName": "开关名称",
|
|
12
12
|
"switchNameDesc": "开关在 HomeKit 中的显示名称。",
|
|
13
13
|
"switchType": "开关类型",
|
|
14
|
-
"switchTypeDesc": "
|
|
14
|
+
"switchTypeDesc": "此设备中所有开关的 HomeKit 配件类型。",
|
|
15
15
|
"typeSwitch": "开关",
|
|
16
16
|
"typeOutlet": "插座",
|
|
17
17
|
"defaultState": "默认状态",
|
|
18
|
-
"defaultStateDesc": "Homebridge
|
|
18
|
+
"defaultStateDesc": "每次 Homebridge 重启时的开关状态。",
|
|
19
|
+
"remember": "记住上次状态",
|
|
19
20
|
"delayOff": "自动关闭(毫秒)",
|
|
20
21
|
"delayOffDesc": "在指定的毫秒数后自动关闭。设置为 0 以禁用。",
|
|
21
22
|
"addSwitch": "添加开关",
|
|
@@ -33,6 +34,5 @@
|
|
|
33
34
|
"behaviorSingleDesc": "同一时间只能有一个开关处于打开状态。打开一个会自动关闭所有其他开关。",
|
|
34
35
|
"masterSwitch": "主开关",
|
|
35
36
|
"masterSwitchDesc": "添加一个主开关,可以一次性打开或关闭所有开关。",
|
|
36
|
-
"switchSingular": "开关"
|
|
37
|
-
"masterSwitchType": "主开关类型"
|
|
37
|
+
"switchSingular": "开关"
|
|
38
38
|
}
|
|
@@ -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
|
}
|
|
@@ -221,7 +233,8 @@
|
|
|
221
233
|
config.devices.push({
|
|
222
234
|
name: '',
|
|
223
235
|
switchBehavior: 'independent',
|
|
224
|
-
|
|
236
|
+
switchType: 'outlet',
|
|
237
|
+
switches: [{ name: '', defaultState: 'remember', delayOff: 0 }],
|
|
225
238
|
});
|
|
226
239
|
expandedDevices.add(di);
|
|
227
240
|
expandedSwitches.add(`${di}_0`);
|
|
@@ -277,22 +290,11 @@
|
|
|
277
290
|
input.addEventListener('change', () => {
|
|
278
291
|
const di = parseInt(input.dataset.dev);
|
|
279
292
|
config.devices[di].masterSwitch = input.checked;
|
|
280
|
-
if (!input.checked) {
|
|
281
|
-
delete config.devices[di].masterSwitchType;
|
|
282
|
-
}
|
|
283
293
|
render();
|
|
284
294
|
save();
|
|
285
295
|
});
|
|
286
296
|
});
|
|
287
297
|
|
|
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;
|
|
293
|
-
save();
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
298
|
|
|
297
299
|
document.querySelectorAll('.btn-remove-device').forEach(btn => {
|
|
298
300
|
btn.addEventListener('click', (e) => {
|
|
@@ -310,7 +312,7 @@
|
|
|
310
312
|
btn.addEventListener('click', () => {
|
|
311
313
|
const di = parseInt(btn.dataset.dev);
|
|
312
314
|
const si = config.devices[di].switches.length;
|
|
313
|
-
config.devices[di].switches.push({ name: '',
|
|
315
|
+
config.devices[di].switches.push({ name: '', defaultState: 'remember', delayOff: 0 });
|
|
314
316
|
expandedSwitches.add(`${di}_${si}`);
|
|
315
317
|
render();
|
|
316
318
|
save();
|
|
@@ -335,7 +337,7 @@
|
|
|
335
337
|
const si = parseInt(input.dataset.sw);
|
|
336
338
|
const field = input.dataset.field;
|
|
337
339
|
if (field === 'defaultState') {
|
|
338
|
-
config.devices[di].switches[si][field] = input.
|
|
340
|
+
config.devices[di].switches[si][field] = input.value;
|
|
339
341
|
} else if (field === 'delayOff') {
|
|
340
342
|
config.devices[di].switches[si][field] = parseInt(input.value) || 0;
|
|
341
343
|
} else {
|
|
@@ -387,7 +389,7 @@
|
|
|
387
389
|
</div>
|
|
388
390
|
|
|
389
391
|
<div class="device-body ${isOpen ? '' : 'collapsed'}">
|
|
390
|
-
<div class="inline-row">
|
|
392
|
+
<div class="inline-row" style="grid-template-columns: 1fr 1fr 1fr">
|
|
391
393
|
<div class="form-group">
|
|
392
394
|
<label>${t.deviceName || 'Device Name'}</label>
|
|
393
395
|
<input type="text" class="dev-field" data-dev="${di}" data-field="name" value="${esc(dev.name || '')}">
|
|
@@ -400,31 +402,31 @@
|
|
|
400
402
|
</select>
|
|
401
403
|
<div class="desc" style="margin-top:6px">${behaviorDesc}</div>
|
|
402
404
|
</div>
|
|
405
|
+
<div class="form-group">
|
|
406
|
+
<label>${t.switchType || 'Switch Type'}</label>
|
|
407
|
+
<select class="dev-field" data-dev="${di}" data-field="switchType">
|
|
408
|
+
<option value="outlet" ${!dev.switchType || dev.switchType === 'outlet' ? 'selected' : ''}>${t.typeOutlet || 'Outlet'}</option>
|
|
409
|
+
<option value="switch" ${dev.switchType === 'switch' ? 'selected' : ''}>${t.typeSwitch || 'Switch'}</option>
|
|
410
|
+
</select>
|
|
411
|
+
<div class="desc" style="margin-top:6px">${t.switchTypeDesc || ''}</div>
|
|
412
|
+
</div>
|
|
403
413
|
</div>
|
|
404
414
|
|
|
405
415
|
${isIndependent ? `
|
|
406
416
|
<div class="master-option">
|
|
407
|
-
<div
|
|
408
|
-
<
|
|
409
|
-
|
|
410
|
-
<
|
|
411
|
-
|
|
412
|
-
<div class="desc" style="margin-bottom:0">${t.masterSwitchDesc || ''}</div>
|
|
413
|
-
</div>
|
|
417
|
+
<div class="toggle-wrap">
|
|
418
|
+
<input type="checkbox" class="master-toggle" data-dev="${di}" ${dev.masterSwitch ? 'checked' : ''}>
|
|
419
|
+
<div>
|
|
420
|
+
<label style="margin-bottom:0">${t.masterSwitch || 'Master Switch'}</label>
|
|
421
|
+
<div class="desc" style="margin-bottom:0">${t.masterSwitchDesc || ''}</div>
|
|
414
422
|
</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
|
-
` : ''}
|
|
421
423
|
</div>
|
|
422
424
|
</div>
|
|
423
425
|
` : ''}
|
|
424
426
|
|
|
425
427
|
<div class="section-title" style="margin-top:16px">${t.switches || 'Switches'}</div>
|
|
426
428
|
|
|
427
|
-
${switches.map((sw, si) => renderSwitch(sw, di, si)).join('')}
|
|
429
|
+
${switches.map((sw, si) => renderSwitch(sw, di, si, dev.switchType || 'outlet')).join('')}
|
|
428
430
|
|
|
429
431
|
<button class="btn btn-primary btn-add-switch" data-dev="${di}">+ ${t.addSwitch || 'Add Switch'}</button>
|
|
430
432
|
</div>
|
|
@@ -432,9 +434,12 @@
|
|
|
432
434
|
`;
|
|
433
435
|
}
|
|
434
436
|
|
|
435
|
-
function renderSwitch(sw, di, si) {
|
|
437
|
+
function renderSwitch(sw, di, si, deviceSwitchType) {
|
|
436
438
|
const isOpen = expandedSwitches.has(`${di}_${si}`);
|
|
437
439
|
const swLabel = sw.name || `#${si + 1}`;
|
|
440
|
+
const typeLabel = deviceSwitchType === 'switch' ? (t.typeSwitch || 'Switch') : (t.typeOutlet || 'Outlet');
|
|
441
|
+
const ds = sw.defaultState;
|
|
442
|
+
const dsValue = (ds === true || ds === 'on') ? 'on' : (ds === false || ds === 'off') ? 'off' : 'remember';
|
|
438
443
|
|
|
439
444
|
return `
|
|
440
445
|
<div class="switch-card">
|
|
@@ -442,37 +447,29 @@
|
|
|
442
447
|
<div class="switch-header-left">
|
|
443
448
|
<span class="chevron ${isOpen ? 'open' : ''}">▶</span>
|
|
444
449
|
<strong>${esc(swLabel)}</strong>
|
|
445
|
-
${!isOpen ? `<span class="switch-summary">${
|
|
450
|
+
${!isOpen ? `<span class="switch-summary">${typeLabel}${sw.delayOff ? ` / ${sw.delayOff}ms` : ''}</span>` : ''}
|
|
446
451
|
</div>
|
|
447
452
|
<div class="switch-header-right">
|
|
448
453
|
<button class="btn btn-danger btn-sm btn-remove-switch" data-dev="${di}" data-sw="${si}">${t.removeSwitch || 'Remove'}</button>
|
|
449
454
|
</div>
|
|
450
455
|
</div>
|
|
451
456
|
<div class="switch-body ${isOpen ? '' : 'collapsed'}">
|
|
452
|
-
<div class="inline-row">
|
|
453
|
-
<div class="form-group">
|
|
457
|
+
<div class="inline-row" style="grid-template-columns: 1fr 1fr 1fr; align-items: end;">
|
|
458
|
+
<div class="form-group" style="margin-bottom:0">
|
|
454
459
|
<label>${t.switchName || 'Switch Name'}</label>
|
|
455
460
|
<input type="text" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="name" value="${esc(sw.name || '')}">
|
|
456
461
|
</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
|
-
<div class="form-group">
|
|
462
|
+
<div class="form-group" style="margin-bottom:0">
|
|
467
463
|
<label>${t.delayOff || 'Auto Turn Off (ms)'}</label>
|
|
468
464
|
<input type="number" class="sw-field" data-dev="${di}" data-sw="${si}" data-field="delayOff" min="0" value="${sw.delayOff || 0}">
|
|
469
465
|
</div>
|
|
470
|
-
<div class="form-group">
|
|
466
|
+
<div class="form-group" style="margin-bottom:0">
|
|
471
467
|
<label>${t.defaultState || 'Default State'}</label>
|
|
472
|
-
<
|
|
473
|
-
<
|
|
474
|
-
<
|
|
475
|
-
|
|
468
|
+
<select class="sw-field" data-dev="${di}" data-sw="${si}" data-field="defaultState">
|
|
469
|
+
<option value="remember" ${dsValue === 'remember' ? 'selected' : ''}>${t.remember || 'Remember'}</option>
|
|
470
|
+
<option value="on" ${dsValue === 'on' ? 'selected' : ''}>${t.on || 'On'}</option>
|
|
471
|
+
<option value="off" ${dsValue === 'off' ? 'selected' : ''}>${t.off || 'Off'}</option>
|
|
472
|
+
</select>
|
|
476
473
|
</div>
|
|
477
474
|
</div>
|
|
478
475
|
</div>
|
package/index.js
CHANGED
|
@@ -23,6 +23,7 @@ class MultipleSwitchPlatform {
|
|
|
23
23
|
this.Characteristic = api.hap.Characteristic;
|
|
24
24
|
this.cachedAccessories = new Map();
|
|
25
25
|
this.deviceServices = new Map();
|
|
26
|
+
this.autoOffTimers = new Map();
|
|
26
27
|
|
|
27
28
|
this.api.on('didFinishLaunching', () => {
|
|
28
29
|
this.log.info('MultipleSwitchPlatform started.');
|
|
@@ -78,6 +79,10 @@ class MultipleSwitchPlatform {
|
|
|
78
79
|
const hasMaster = behavior === 'independent' && device.masterSwitch === true;
|
|
79
80
|
const uuid = this.api.hap.uuid.generate(name);
|
|
80
81
|
|
|
82
|
+
if (this.deviceServices.has(uuid)) {
|
|
83
|
+
this.log.warn(`Duplicate device name "${name}" detected. Each device must have a unique name.`);
|
|
84
|
+
}
|
|
85
|
+
|
|
81
86
|
let accessory = this.cachedAccessories.get(uuid);
|
|
82
87
|
const isNew = !accessory;
|
|
83
88
|
|
|
@@ -119,7 +124,7 @@ class MultipleSwitchPlatform {
|
|
|
119
124
|
|
|
120
125
|
// Create master switch if enabled
|
|
121
126
|
if (hasMaster) {
|
|
122
|
-
const MasterServiceClass = this.getServiceClass(device.
|
|
127
|
+
const MasterServiceClass = this.getServiceClass(device.switchType || 'outlet');
|
|
123
128
|
const masterService = accessory.addService(MasterServiceClass, 'Master', MASTER_SUBTYPE);
|
|
124
129
|
|
|
125
130
|
this.setServiceName(masterService, 'Master');
|
|
@@ -133,10 +138,11 @@ class MultipleSwitchPlatform {
|
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
// Create regular switches
|
|
141
|
+
const switchType = device.switchType || (switches[0] && switches[0].type) || 'outlet';
|
|
136
142
|
switches.forEach((sw, index) => {
|
|
137
143
|
const subtype = `switch_${index}`;
|
|
138
144
|
|
|
139
|
-
const ServiceClass = this.getServiceClass(
|
|
145
|
+
const ServiceClass = this.getServiceClass(switchType);
|
|
140
146
|
const service = accessory.addService(ServiceClass, sw.name, subtype);
|
|
141
147
|
|
|
142
148
|
this.setServiceName(service, sw.name);
|
|
@@ -144,8 +150,16 @@ class MultipleSwitchPlatform {
|
|
|
144
150
|
|
|
145
151
|
services.set(subtype, service);
|
|
146
152
|
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
const ds = sw.defaultState;
|
|
154
|
+
if (ds === false || ds === 'off') {
|
|
155
|
+
accessory.context.switchStates[subtype] = false;
|
|
156
|
+
} else if (ds === true || ds === 'on') {
|
|
157
|
+
accessory.context.switchStates[subtype] = true;
|
|
158
|
+
} else {
|
|
159
|
+
// 'remember' or undefined — keep existing state; initialise to false on first run
|
|
160
|
+
if (accessory.context.switchStates[subtype] === undefined) {
|
|
161
|
+
accessory.context.switchStates[subtype] = false;
|
|
162
|
+
}
|
|
149
163
|
}
|
|
150
164
|
});
|
|
151
165
|
|
|
@@ -203,13 +217,19 @@ class MultipleSwitchPlatform {
|
|
|
203
217
|
}
|
|
204
218
|
|
|
205
219
|
scheduleAutoOff(accessory, service, sw, subtype) {
|
|
206
|
-
|
|
220
|
+
const timerKey = `${accessory.UUID}_${subtype}`;
|
|
221
|
+
if (this.autoOffTimers.has(timerKey)) {
|
|
222
|
+
clearTimeout(this.autoOffTimers.get(timerKey));
|
|
223
|
+
}
|
|
224
|
+
const timer = setTimeout(() => {
|
|
225
|
+
this.autoOffTimers.delete(timerKey);
|
|
207
226
|
if (accessory.context.switchStates[subtype]) {
|
|
208
227
|
accessory.context.switchStates[subtype] = false;
|
|
209
228
|
service.updateCharacteristic(this.Characteristic.On, false);
|
|
210
229
|
this.log.info(`[${sw.name}] auto-off after ${sw.delayOff}ms`);
|
|
211
230
|
}
|
|
212
231
|
}, sw.delayOff);
|
|
232
|
+
this.autoOffTimers.set(timerKey, timer);
|
|
213
233
|
}
|
|
214
234
|
|
|
215
235
|
getServiceClass(type) {
|
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