@homebridge-plugins/homebridge-ewelink 12.16.3-beta.0 → 12.17.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 +3 -1
- package/lib/homebridge-ui/public/index.html +348 -111
- package/lib/homebridge-ui/server.js +45 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@homebridge-plugins/homebridge-ewelink` will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## v12.
|
|
5
|
+
## v12.17.0 (Pending Release)
|
|
6
6
|
|
|
7
7
|
### Changed
|
|
8
8
|
|
|
9
9
|
- fix: fire the button press for R5 and SwitchMan scene controllers when the action time arrives as a number, so HomeKit sees the press (#762)
|
|
10
|
+
- feat(ui): add, remove and hide devices from the config via the my devices tab
|
|
11
|
+
- style(ui): standardise the custom ui layout and sync the support tab with the readme
|
|
10
12
|
|
|
11
13
|
## v12.16.2 (2026-07-18)
|
|
12
14
|
|
|
@@ -17,6 +17,22 @@
|
|
|
17
17
|
color: #eeeeee !important;
|
|
18
18
|
font-weight: 500 !important;
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
.list-group-item {
|
|
22
|
+
background-color: #333333;
|
|
23
|
+
border-color: #444444;
|
|
24
|
+
color: #eeeeee;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.text-muted {
|
|
28
|
+
color: #aaaaaa !important;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
.min-w-50 {
|
|
32
|
+
min-width: 50px;
|
|
33
|
+
}
|
|
34
|
+
.action-desc {
|
|
35
|
+
font-size: 0.85rem;
|
|
20
36
|
}
|
|
21
37
|
select {
|
|
22
38
|
background-image:
|
|
@@ -81,11 +97,46 @@
|
|
|
81
97
|
<div id="pageDevices" class="mt-4" style="display: none;">
|
|
82
98
|
<div id="deviceInfo">
|
|
83
99
|
<form>
|
|
84
|
-
<div class="form-group">
|
|
100
|
+
<div class="form-group mb-0">
|
|
85
101
|
<select class="form-control" id="deviceSelect"></select>
|
|
86
102
|
</div>
|
|
87
103
|
</form>
|
|
88
|
-
<
|
|
104
|
+
<ul class="list-group mt-4 mb-4 mx-0" id="configActionList" style="display: none;">
|
|
105
|
+
<li class="list-group-item">
|
|
106
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
107
|
+
<div class="pr-2">
|
|
108
|
+
<div>Add All Devices</div>
|
|
109
|
+
<div class="text-muted action-desc">Add all devices to the config that are not already in the config.</div>
|
|
110
|
+
</div>
|
|
111
|
+
<button type="button" class="btn btn-primary m-0 ml-3 py-1 min-w-50" id="addAllBtn" aria-label="Add all devices to the config">
|
|
112
|
+
<i aria-hidden="true" class="fas fa-arrow-right"></i>
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
</li>
|
|
116
|
+
<li class="list-group-item">
|
|
117
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
118
|
+
<div class="pr-2">
|
|
119
|
+
<div id="deviceConfigTitle">Add This Device</div>
|
|
120
|
+
<div class="text-muted action-desc" id="deviceConfigText">Add this device to the plugin config.</div>
|
|
121
|
+
</div>
|
|
122
|
+
<button type="button" class="btn btn-primary m-0 ml-3 py-1 min-w-50" id="deviceConfigBtn" aria-label="Add this device to the config">
|
|
123
|
+
<i aria-hidden="true" class="fas fa-arrow-right"></i>
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</li>
|
|
127
|
+
<li class="list-group-item">
|
|
128
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
129
|
+
<div class="pr-2">
|
|
130
|
+
<div id="hideDeviceTitle">Hide This Device</div>
|
|
131
|
+
<div class="text-muted action-desc" id="hideDeviceText">Hide this device from HomeKit. This adds the device to the config if needed.</div>
|
|
132
|
+
</div>
|
|
133
|
+
<button type="button" class="btn btn-primary m-0 ml-3 py-1 min-w-50" id="hideDeviceBtn" aria-label="Hide this device from HomeKit">
|
|
134
|
+
<i aria-hidden="true" class="fas fa-arrow-right"></i>
|
|
135
|
+
</button>
|
|
136
|
+
</div>
|
|
137
|
+
</li>
|
|
138
|
+
</ul>
|
|
139
|
+
<table class="table w-100 mt-0" id="deviceTable" style="display: none;">
|
|
89
140
|
<thead>
|
|
90
141
|
<tr class="table-active">
|
|
91
142
|
<th scope="col" style="width: 40%;">Device Name</th>
|
|
@@ -140,124 +191,292 @@
|
|
|
140
191
|
</table>
|
|
141
192
|
</div>
|
|
142
193
|
</div>
|
|
143
|
-
<div id="pageSupport" class="mt-4" style="display: none
|
|
194
|
+
<div id="pageSupport" class="mt-4" style="display: none">
|
|
144
195
|
<p class="text-center lead">Thank you for using <strong>homebridge-ewelink</strong></p>
|
|
145
|
-
<
|
|
146
|
-
|
|
196
|
+
<h5>Plugin Information</h5>
|
|
197
|
+
<ul>
|
|
198
|
+
<li>This plugin allows you to view and control your eWeLink devices within HomeKit. The plugin:</li>
|
|
147
199
|
<ul>
|
|
148
|
-
<li>
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
</li>
|
|
153
|
-
<li>
|
|
154
|
-
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Configuration" target="_blank"
|
|
155
|
-
>Configuration</a
|
|
156
|
-
>
|
|
157
|
-
</li>
|
|
158
|
-
<li>
|
|
159
|
-
<a href="https://github.com/homebridge/homebridge/wiki/How-to-Install-Alternate-Plugin-Versions" target="_blank"
|
|
160
|
-
>Beta Version</a
|
|
161
|
-
>
|
|
162
|
-
</li>
|
|
163
|
-
<li>
|
|
164
|
-
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Node-Version" target="_blank"
|
|
165
|
-
>Node Version</a
|
|
166
|
-
>
|
|
167
|
-
</li>
|
|
200
|
+
<li>requires your eWeLink credentials to download a device list</li>
|
|
201
|
+
<li>attempts to control your supported devices locally, reverting to cloud control if necessary</li>
|
|
202
|
+
<li>listens for real-time device updates when controlled externally</li>
|
|
203
|
+
<li>supports removing your eWeLink credentials from the configuration in certain situations, see <a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Connection-Methods#lan-mode-without-ewelink-credentials" target="_blank">wiki</a></li>
|
|
168
204
|
</ul>
|
|
169
|
-
|
|
205
|
+
</ul>
|
|
206
|
+
<h5>Prerequisites</h5>
|
|
207
|
+
<ul>
|
|
208
|
+
<li>To use this plugin, you will need to already have:</li>
|
|
170
209
|
<ul>
|
|
171
|
-
<li>
|
|
172
|
-
|
|
173
|
-
>Supported Devices</a
|
|
174
|
-
>
|
|
175
|
-
</li>
|
|
176
|
-
<li>
|
|
177
|
-
<a
|
|
178
|
-
href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Accessory-Simulations"
|
|
179
|
-
target="_blank"
|
|
180
|
-
>Accessory Simulations</a
|
|
181
|
-
>
|
|
182
|
-
</li>
|
|
183
|
-
<li>
|
|
184
|
-
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Connection-Methods" target="_blank"
|
|
185
|
-
>Connection Methods</a
|
|
186
|
-
>
|
|
187
|
-
</li>
|
|
188
|
-
<li>
|
|
189
|
-
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Internal-API" target="_blank"
|
|
190
|
-
>Internal API</a
|
|
191
|
-
>
|
|
192
|
-
</li>
|
|
193
|
-
</ul>
|
|
194
|
-
<h4>Help/About</h4>
|
|
195
|
-
<ul>
|
|
196
|
-
<li>
|
|
197
|
-
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Common-Errors" target="_blank"
|
|
198
|
-
>Common Errors</a
|
|
199
|
-
>
|
|
200
|
-
</li>
|
|
201
|
-
<li>
|
|
202
|
-
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/issues/new/choose" target="_blank"
|
|
203
|
-
>Support Request</a
|
|
204
|
-
>
|
|
205
|
-
</li>
|
|
206
|
-
<li>
|
|
207
|
-
<a href="https://github.com/homebridge-plugins/homebridge-ewelink/blob/latest/CHANGELOG.md" target="_blank"
|
|
208
|
-
>Changelog</a
|
|
209
|
-
>
|
|
210
|
-
</li>
|
|
211
|
-
<li>
|
|
212
|
-
<a href="https://github.com/sponsors/bwp91" target="_blank">About Me</a>
|
|
213
|
-
</li>
|
|
214
|
-
</ul>
|
|
215
|
-
<h4>Credits</h4>
|
|
216
|
-
<ul>
|
|
217
|
-
<li>
|
|
218
|
-
To the original plugin maintainer:
|
|
219
|
-
<a href="https://github.com/gbro115" target="_blank">@gbro115</a>.
|
|
220
|
-
</li>
|
|
221
|
-
<li>
|
|
222
|
-
To successive contributors:
|
|
223
|
-
<a href="https://github.com/MrTomAsh" target="_blank">@MrTomAsh</a> and
|
|
224
|
-
<a href="https://github.com/howanghk" target="_blank">@howanghk</a> for
|
|
225
|
-
<a href="https://github.com/howanghk/homebridge-ewelink" target="_blank"
|
|
226
|
-
>homebridge-ewelink-max</a
|
|
227
|
-
>.
|
|
228
|
-
</li>
|
|
229
|
-
<li>
|
|
230
|
-
To the creators/contributors of
|
|
231
|
-
<a href="https://homebridge.io" target="_blank">Homebridge</a> who make this plugin possible.
|
|
232
|
-
</li>
|
|
233
|
-
<li>
|
|
234
|
-
To the creators/contributors of
|
|
235
|
-
<a href="https://github.com/simont77/fakegato-history" target="_blank">Fakegato</a>:
|
|
236
|
-
<a href="https://github.com/simont77" target="_blank">@simont77</a> and
|
|
237
|
-
<a href="https://github.com/NorthernMan54" target="_blank">@NorthernMan54</a>.
|
|
238
|
-
</li>
|
|
239
|
-
<li>
|
|
240
|
-
To the creator of the awesome plugin header logo:
|
|
241
|
-
<a href="https://www.instagram.com/keryan.me" target="_blank">Keryan Belahcene</a>.
|
|
242
|
-
</li>
|
|
243
|
-
<li>To all users who have shared their devices to enable functionality.</li>
|
|
244
|
-
</ul>
|
|
245
|
-
|
|
246
|
-
<h4>Disclaimer</h4>
|
|
247
|
-
<ul>
|
|
248
|
-
<li>
|
|
249
|
-
I am in no way affiliated with eWeLink nor any of the device brands (like Sonoff) and this
|
|
250
|
-
plugin is a personal project that I maintain in my free time.
|
|
251
|
-
</li>
|
|
252
|
-
<li>
|
|
253
|
-
Use this plugin entirely at your own risk - please see licence for more information.
|
|
254
|
-
</li>
|
|
210
|
+
<li><a href="https://nodejs.org" target="_blank">Node</a>: latest version of <code>v22</code> or <code>v24</code> - any other major version is not supported.</li>
|
|
211
|
+
<li><a href="https://homebridge.io" target="_blank">Homebridge</a>: <code>v1.6</code> or above - refer to link for more information and installation instructions.</li>
|
|
255
212
|
</ul>
|
|
213
|
+
</ul>
|
|
214
|
+
<h5>Setup</h5>
|
|
215
|
+
<ul>
|
|
216
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Installation" target="_blank">Installation</a></li>
|
|
217
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Configuration" target="_blank">Configuration</a></li>
|
|
218
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Beta-Version" target="_blank">Beta Version</a></li>
|
|
219
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Node-Version" target="_blank">Node Version</a></li>
|
|
220
|
+
</ul>
|
|
221
|
+
<h5>Features</h5>
|
|
222
|
+
<ul>
|
|
223
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Supported-Devices" target="_blank">Supported Devices</a></li>
|
|
224
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Accessory-Simulations" target="_blank">Accessory Simulations</a></li>
|
|
225
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Connection-Methods" target="_blank">Connection Methods</a></li>
|
|
226
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Internal-API" target="_blank">Internal API</a></li>
|
|
227
|
+
</ul>
|
|
228
|
+
<h5>Help/About</h5>
|
|
229
|
+
<ul>
|
|
230
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/wiki/Common-Errors" target="_blank">Common Errors</a></li>
|
|
231
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/issues/new/choose" target="_blank">Support Request</a></li>
|
|
232
|
+
<li><a href="https://github.com/homebridge-plugins/homebridge-ewelink/blob/latest/CHANGELOG.md" target="_blank">Changelog</a></li>
|
|
233
|
+
<li><a href="https://github.com/sponsors/bwp91" target="_blank">About Me</a></li>
|
|
234
|
+
</ul>
|
|
235
|
+
<h5>Credits</h5>
|
|
236
|
+
<ul>
|
|
237
|
+
<li>To the original plugin maintainer: <a href="https://github.com/gbro115" target="_blank">@gbro115</a>.</li>
|
|
238
|
+
<li>To successive contributors: <a href="https://github.com/MrTomAsh" target="_blank">@MrTomAsh</a> and <a href="https://github.com/howanghk" target="_blank">@howanghk</a> for <a href="https://github.com/howanghk/homebridge-ewelink" target="_blank">homebridge-ewelink-max</a>.</li>
|
|
239
|
+
<li>To the creators/contributors of <a href="https://homebridge.io" target="_blank">Homebridge</a> who make this plugin possible.</li>
|
|
240
|
+
<li>To the creators/contributors of <a href="https://github.com/simont77/fakegato-history" target="_blank">Fakegato</a>: <a href="https://github.com/simont77" target="_blank">@simont77</a> and <a href="https://github.com/NorthernMan54" target="_blank">@NorthernMan54</a>.</li>
|
|
241
|
+
<li>To the creator of the awesome plugin header logo: <a href="https://www.instagram.com/keryan.me" target="_blank">Keryan Belahcene</a>.</li>
|
|
242
|
+
<li>To all users who have shared their devices to enable functionality.</li>
|
|
243
|
+
</ul>
|
|
244
|
+
<h5>Disclaimer</h5>
|
|
245
|
+
<ul>
|
|
246
|
+
<li>I am in no way affiliated with eWeLink nor any of the device brands (like Sonoff) and this plugin is a personal project that I maintain in my free time.</li>
|
|
247
|
+
<li>Use this plugin entirely at your own risk - please see licence for more information.</li>
|
|
248
|
+
</ul>
|
|
256
249
|
</div>
|
|
257
250
|
<script>
|
|
258
251
|
;(async () => {
|
|
259
252
|
try {
|
|
260
253
|
const currentConfig = await homebridge.getPluginConfig()
|
|
254
|
+
|
|
255
|
+
// ===== Device picker (add/remove/hide config entries from the My Devices tab) =====
|
|
256
|
+
// The control state is always computed from a fresh getPluginConfig() call,
|
|
257
|
+
// so staged-but-unsaved edits made in the Settings tab are respected.
|
|
258
|
+
// Config entries are matched by the base eWeLink device id, so the
|
|
259
|
+
// channels of a multi-channel device all share one config entry.
|
|
260
|
+
const deviceArrays = [
|
|
261
|
+
'singleDevices',
|
|
262
|
+
'multiDevices',
|
|
263
|
+
'lightDevices',
|
|
264
|
+
'thDevices',
|
|
265
|
+
'fanDevices',
|
|
266
|
+
'sensorDevices',
|
|
267
|
+
'rfDevices',
|
|
268
|
+
]
|
|
269
|
+
let pickerAccessories = []
|
|
270
|
+
let pickerUiidMap = {}
|
|
271
|
+
let removeConfirmTimer
|
|
272
|
+
|
|
273
|
+
// The action buttons always show the settings-page arrow; the row text
|
|
274
|
+
// (and the button's aria-label) says what the arrow will do
|
|
275
|
+
const actionArrow = '<i aria-hidden="true" class="fas fa-arrow-right"></i>'
|
|
276
|
+
|
|
277
|
+
// Compare device ids ignoring case and separators, so an entry typed by
|
|
278
|
+
// hand in a different format still counts as the same device
|
|
279
|
+
const looseId = id => (id || '').toString().toLowerCase().replace(/[^a-z0-9]+/g, '')
|
|
280
|
+
|
|
281
|
+
const getConfigBlocks = async () => {
|
|
282
|
+
const blocks = await homebridge.getPluginConfig()
|
|
283
|
+
if (!blocks.length) {
|
|
284
|
+
blocks.push({ name: 'eWeLink' })
|
|
285
|
+
}
|
|
286
|
+
return blocks
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const findInConfig = (config, deviceId) => {
|
|
290
|
+
const target = looseId(deviceId)
|
|
291
|
+
for (const key of deviceArrays) {
|
|
292
|
+
if (Array.isArray(config[key])) {
|
|
293
|
+
const index = config[key].findIndex(entry => entry && looseId(entry.deviceId) === target)
|
|
294
|
+
if (index !== -1) {
|
|
295
|
+
return { key, index }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return null
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const selectedAccessory = () => {
|
|
303
|
+
const hbId = document.getElementById('deviceSelect').value
|
|
304
|
+
return pickerAccessories.find(x => x.context.hbDeviceId === hbId)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const setHideControl = hidden => {
|
|
308
|
+
const btn = document.getElementById('hideDeviceBtn')
|
|
309
|
+
btn.dataset.hidden = hidden ? 'yes' : 'no'
|
|
310
|
+
btn.innerHTML = actionArrow
|
|
311
|
+
btn.setAttribute('aria-label', hidden ? 'Show this device in HomeKit' : 'Hide this device from HomeKit')
|
|
312
|
+
document.getElementById('hideDeviceTitle').innerHTML = hidden ? 'Show This Device' : 'Hide This Device'
|
|
313
|
+
document.getElementById('hideDeviceText').innerHTML = hidden
|
|
314
|
+
? 'Show this device in HomeKit again. The config entry is kept.'
|
|
315
|
+
: 'Hide this device from HomeKit. This adds the device to the config if needed.'
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const setConfigButton = inConfig => {
|
|
319
|
+
const btn = document.getElementById('deviceConfigBtn')
|
|
320
|
+
clearTimeout(removeConfirmTimer)
|
|
321
|
+
delete btn.dataset.confirm
|
|
322
|
+
btn.dataset.state = inConfig ? 'remove' : 'add'
|
|
323
|
+
btn.classList.remove('btn-danger')
|
|
324
|
+
btn.classList.add('btn-primary')
|
|
325
|
+
btn.innerHTML = actionArrow
|
|
326
|
+
btn.setAttribute('aria-label', inConfig ? 'Remove this device from the config' : 'Add this device to the config')
|
|
327
|
+
document.getElementById('deviceConfigTitle').innerHTML = inConfig ? 'Remove This Device' : 'Add This Device'
|
|
328
|
+
document.getElementById('deviceConfigText').innerHTML = inConfig
|
|
329
|
+
? 'Remove this device from the plugin config.'
|
|
330
|
+
: 'Add this device to the plugin config.'
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const addDeviceToConfig = (config, acc) => {
|
|
334
|
+
// Which array a device belongs in comes from its uiid via the plugin's
|
|
335
|
+
// own lists; anything unknown is safe in singleDevices because the
|
|
336
|
+
// platform merges every array into one lookup by device id
|
|
337
|
+
const arrayKey = pickerUiidMap[acc.context.eweUIID] || 'singleDevices'
|
|
338
|
+
if (!Array.isArray(config[arrayKey])) {
|
|
339
|
+
config[arrayKey] = []
|
|
340
|
+
}
|
|
341
|
+
// label is included so the entry is identifiable in the config editor
|
|
342
|
+
config[arrayKey].push({ label: acc.displayName, deviceId: acc.context.eweDeviceId })
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
document.getElementById('deviceConfigBtn').addEventListener('click', async () => {
|
|
346
|
+
try {
|
|
347
|
+
const btn = document.getElementById('deviceConfigBtn')
|
|
348
|
+
const acc = selectedAccessory()
|
|
349
|
+
if (!acc) {
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
const blocks = await getConfigBlocks()
|
|
353
|
+
const config = blocks[0]
|
|
354
|
+
const found = findInConfig(config, acc.context.eweDeviceId)
|
|
355
|
+
if (btn.dataset.state === 'add') {
|
|
356
|
+
// The config may have changed since the button was drawn, so
|
|
357
|
+
// re-check and never add a duplicate - just correct the button
|
|
358
|
+
if (found) {
|
|
359
|
+
setConfigButton(true)
|
|
360
|
+
setHideControl(!!config[found.key][found.index].ignoreDevice)
|
|
361
|
+
homebridge.toast.info(acc.displayName + ' is already in the config.', 'Not Added')
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
addDeviceToConfig(config, acc)
|
|
365
|
+
await homebridge.updatePluginConfig(blocks)
|
|
366
|
+
setConfigButton(true)
|
|
367
|
+
setHideControl(false)
|
|
368
|
+
homebridge.toast.success(acc.displayName + ' added - press Save to apply.', 'Added To Config')
|
|
369
|
+
} else {
|
|
370
|
+
if (!found) {
|
|
371
|
+
setConfigButton(false)
|
|
372
|
+
setHideControl(false)
|
|
373
|
+
homebridge.toast.info(acc.displayName + ' is not in the config.', 'Not Removed')
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
const entry = config[found.key][found.index]
|
|
377
|
+
const extraKeys = Object.keys(entry).filter(k => !['deviceId', 'label'].includes(k))
|
|
378
|
+
if (extraKeys.length > 0 && btn.dataset.confirm !== 'yes') {
|
|
379
|
+
// The entry has settings beyond id and label which would be
|
|
380
|
+
// lost, so ask for a second click within five seconds
|
|
381
|
+
btn.dataset.confirm = 'yes'
|
|
382
|
+
btn.classList.remove('btn-primary')
|
|
383
|
+
btn.classList.add('btn-danger')
|
|
384
|
+
btn.innerHTML = actionArrow
|
|
385
|
+
btn.setAttribute('aria-label', 'Confirm removing this device from the config')
|
|
386
|
+
document.getElementById('deviceConfigText').innerHTML = 'This entry has extra settings which will be lost. Click again to remove it.'
|
|
387
|
+
removeConfirmTimer = setTimeout(() => setConfigButton(true), 5000)
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
config[found.key].splice(found.index, 1)
|
|
391
|
+
if (config[found.key].length === 0) {
|
|
392
|
+
delete config[found.key]
|
|
393
|
+
}
|
|
394
|
+
await homebridge.updatePluginConfig(blocks)
|
|
395
|
+
setConfigButton(false)
|
|
396
|
+
setHideControl(false)
|
|
397
|
+
homebridge.toast.success(acc.displayName + ' removed - press Save to apply.', 'Removed From Config')
|
|
398
|
+
}
|
|
399
|
+
} catch (err) {
|
|
400
|
+
homebridge.toast.error(err.message, 'Error')
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
document.getElementById('hideDeviceBtn').addEventListener('click', async () => {
|
|
405
|
+
try {
|
|
406
|
+
const acc = selectedAccessory()
|
|
407
|
+
if (!acc) {
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
const blocks = await getConfigBlocks()
|
|
411
|
+
const config = blocks[0]
|
|
412
|
+
let found = findInConfig(config, acc.context.eweDeviceId)
|
|
413
|
+
const currentlyHidden = document.getElementById('hideDeviceBtn').dataset.hidden === 'yes'
|
|
414
|
+
if (!currentlyHidden) {
|
|
415
|
+
// Hiding needs a config entry to carry the flag, so create one if
|
|
416
|
+
// the device is not in the config yet
|
|
417
|
+
if (!found) {
|
|
418
|
+
addDeviceToConfig(config, acc)
|
|
419
|
+
found = findInConfig(config, acc.context.eweDeviceId)
|
|
420
|
+
}
|
|
421
|
+
config[found.key][found.index].ignoreDevice = true
|
|
422
|
+
await homebridge.updatePluginConfig(blocks)
|
|
423
|
+
setConfigButton(true)
|
|
424
|
+
setHideControl(true)
|
|
425
|
+
homebridge.toast.success(acc.displayName + ' will be hidden from HomeKit - press Save to apply.', 'Device Hidden')
|
|
426
|
+
} else {
|
|
427
|
+
if (found) {
|
|
428
|
+
// Drop the flag rather than storing ignoreDevice false, to keep
|
|
429
|
+
// the entry clean
|
|
430
|
+
delete config[found.key][found.index].ignoreDevice
|
|
431
|
+
await homebridge.updatePluginConfig(blocks)
|
|
432
|
+
}
|
|
433
|
+
setConfigButton(!!found)
|
|
434
|
+
setHideControl(false)
|
|
435
|
+
homebridge.toast.success(acc.displayName + ' will be shown in HomeKit - press Save to apply.', 'Device Unhidden')
|
|
436
|
+
}
|
|
437
|
+
} catch (err) {
|
|
438
|
+
homebridge.toast.error(err.message, 'Error')
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
document.getElementById('addAllBtn').addEventListener('click', async () => {
|
|
443
|
+
try {
|
|
444
|
+
const blocks = await getConfigBlocks()
|
|
445
|
+
const config = blocks[0]
|
|
446
|
+
let added = 0
|
|
447
|
+
let skipped = 0
|
|
448
|
+
pickerAccessories.forEach(acc => {
|
|
449
|
+
if (!acc.context?.eweDeviceId) {
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
// Channels of a multi-channel device share one entry, so once the
|
|
453
|
+
// first channel is added the rest count as already in the config
|
|
454
|
+
if (findInConfig(config, acc.context.eweDeviceId)) {
|
|
455
|
+
skipped += 1
|
|
456
|
+
return
|
|
457
|
+
}
|
|
458
|
+
addDeviceToConfig(config, acc)
|
|
459
|
+
added += 1
|
|
460
|
+
})
|
|
461
|
+
if (added > 0) {
|
|
462
|
+
await homebridge.updatePluginConfig(blocks)
|
|
463
|
+
homebridge.toast.success(added + ' device(s) added, ' + skipped + ' already in the config - press Save to apply.', 'Added To Config')
|
|
464
|
+
} else {
|
|
465
|
+
homebridge.toast.info('All devices are already in the config.', 'Nothing To Add')
|
|
466
|
+
}
|
|
467
|
+
const acc = selectedAccessory()
|
|
468
|
+
if (acc) {
|
|
469
|
+
const nowFound = findInConfig(config, acc.context.eweDeviceId)
|
|
470
|
+
setConfigButton(!!nowFound)
|
|
471
|
+
if (nowFound) {
|
|
472
|
+
setHideControl(!!config[nowFound.key][nowFound.index].ignoreDevice)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} catch (err) {
|
|
476
|
+
homebridge.toast.error(err.message, 'Error')
|
|
477
|
+
}
|
|
478
|
+
})
|
|
479
|
+
// ===== End device picker =====
|
|
261
480
|
showIntro = () => {
|
|
262
481
|
homebridge.disableSaveButton?.()
|
|
263
482
|
const introContinue = document.getElementById('introContinue')
|
|
@@ -272,7 +491,9 @@
|
|
|
272
491
|
}
|
|
273
492
|
showDevices = async () => {
|
|
274
493
|
homebridge.showSpinner()
|
|
275
|
-
|
|
494
|
+
// The save button stays enabled here so entries staged by the picker
|
|
495
|
+
// buttons can be saved without switching back to the Settings tab
|
|
496
|
+
homebridge.enableSaveButton?.()
|
|
276
497
|
homebridge.hideSchemaForm()
|
|
277
498
|
document.getElementById('menuHome').classList.remove('btn-elegant')
|
|
278
499
|
document.getElementById('menuHome').classList.add('btn-primary')
|
|
@@ -295,6 +516,16 @@
|
|
|
295
516
|
: 0
|
|
296
517
|
})
|
|
297
518
|
}
|
|
519
|
+
pickerAccessories = cachedAccessories
|
|
520
|
+
if (Object.keys(pickerUiidMap).length === 0) {
|
|
521
|
+
try {
|
|
522
|
+
pickerUiidMap = await homebridge.request('/getUiidMap')
|
|
523
|
+
} catch (err) {
|
|
524
|
+
// Without the map every added device falls back to singleDevices,
|
|
525
|
+
// which still works because the platform merges all the arrays
|
|
526
|
+
pickerUiidMap = {}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
298
529
|
const deviceSelect = document.getElementById('deviceSelect')
|
|
299
530
|
deviceSelect.innerHTML = ''
|
|
300
531
|
cachedAccessories.forEach(a => {
|
|
@@ -329,6 +560,11 @@
|
|
|
329
560
|
? '<img src="' + context.eweBrandLogo + '" style="width: 150px;">'
|
|
330
561
|
: ''
|
|
331
562
|
document.getElementById('deviceTable').style.display = 'inline-table'
|
|
563
|
+
const configBlocks = await getConfigBlocks()
|
|
564
|
+
const configEntry = findInConfig(configBlocks[0], context.eweDeviceId)
|
|
565
|
+
setConfigButton(!!configEntry)
|
|
566
|
+
setHideControl(!!(configEntry && configBlocks[0][configEntry.key][configEntry.index].ignoreDevice))
|
|
567
|
+
document.getElementById('configActionList').style.display = 'block'
|
|
332
568
|
homebridge.hideSpinner()
|
|
333
569
|
}
|
|
334
570
|
deviceSelect.addEventListener('change', event => showDeviceInfo(event.target.value))
|
|
@@ -339,6 +575,7 @@
|
|
|
339
575
|
option.text = 'No Devices'
|
|
340
576
|
deviceSelect.add(option)
|
|
341
577
|
deviceSelect.disabled = true
|
|
578
|
+
document.getElementById('configActionList').style.display = 'none'
|
|
342
579
|
}
|
|
343
580
|
homebridge.hideSpinner()
|
|
344
581
|
}
|
|
@@ -2,10 +2,55 @@ import fs from 'node:fs'
|
|
|
2
2
|
|
|
3
3
|
import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils'
|
|
4
4
|
|
|
5
|
+
import platformConsts from '../utils/constants.js'
|
|
6
|
+
|
|
7
|
+
// Which config array each device category belongs in. The platform merges
|
|
8
|
+
// every array into one lookup by device id, so this only decides which set of
|
|
9
|
+
// options the schema editor shows next to the entry; anything not listed sits
|
|
10
|
+
// safely in singleDevices.
|
|
11
|
+
const categoryToArray = {
|
|
12
|
+
switchMulti: 'multiDevices',
|
|
13
|
+
switchMultiPower: 'multiDevices',
|
|
14
|
+
panel: 'multiDevices',
|
|
15
|
+
lightDimmer: 'lightDevices',
|
|
16
|
+
lightRGB: 'lightDevices',
|
|
17
|
+
lightCCT: 'lightDevices',
|
|
18
|
+
lightRGBCCT: 'lightDevices',
|
|
19
|
+
zbLightDimmer: 'lightDevices',
|
|
20
|
+
zbLightCCT: 'lightDevices',
|
|
21
|
+
zbLightRGBCCT: 'lightDevices',
|
|
22
|
+
sensorAmbient: 'thDevices',
|
|
23
|
+
thermostat: 'thDevices',
|
|
24
|
+
fan: 'fanDevices',
|
|
25
|
+
fanGear: 'fanDevices',
|
|
26
|
+
rfBridge: 'rfDevices',
|
|
27
|
+
sensorContact: 'sensorDevices',
|
|
28
|
+
sensorTempHumi: 'sensorDevices',
|
|
29
|
+
sensorAirQuality: 'sensorDevices',
|
|
30
|
+
zbSensorAmbient: 'sensorDevices',
|
|
31
|
+
zbSensorMotion: 'sensorDevices',
|
|
32
|
+
zbSensorOccupancy: 'sensorDevices',
|
|
33
|
+
zbSensorContact: 'sensorDevices',
|
|
34
|
+
zbSensorWater: 'sensorDevices',
|
|
35
|
+
zbSensorSmoke: 'sensorDevices',
|
|
36
|
+
}
|
|
37
|
+
|
|
5
38
|
class PluginUiServer extends HomebridgePluginUiServer {
|
|
6
39
|
constructor() {
|
|
7
40
|
super()
|
|
8
41
|
|
|
42
|
+
// Used by the My Devices tab to know which config array a device belongs
|
|
43
|
+
// in, built from the same uiid lists the plugin itself uses
|
|
44
|
+
this.onRequest('/getUiidMap', () => {
|
|
45
|
+
const uiidToArray = {}
|
|
46
|
+
Object.entries(categoryToArray).forEach(([category, arrayKey]) => {
|
|
47
|
+
(platformConsts.devices[category] || []).forEach((uiid) => {
|
|
48
|
+
uiidToArray[uiid] = arrayKey
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
return uiidToArray
|
|
52
|
+
})
|
|
53
|
+
|
|
9
54
|
/*
|
|
10
55
|
A native method getCachedAccessories() was introduced in config-ui-x v4.37.0
|
|
11
56
|
The following is for users who have a lower version of config-ui-x
|
package/package.json
CHANGED