@homebridge-plugins/homebridge-resideo 3.1.5 → 3.2.0-beta.0
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 +6 -0
- package/dist/homebridge-ui/public/index.html +281 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.min-w-50 {
|
|
3
|
+
min-width: 50px;
|
|
4
|
+
}
|
|
5
|
+
.action-desc {
|
|
6
|
+
font-size: 0.85rem;
|
|
7
|
+
}
|
|
8
|
+
.dark-mode .list-group-item {
|
|
9
|
+
background-color: #333333;
|
|
10
|
+
border-color: #444444;
|
|
11
|
+
color: #eeeeee;
|
|
12
|
+
}
|
|
13
|
+
.dark-mode .text-muted {
|
|
14
|
+
color: #aaaaaa !important;
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
1
17
|
<p class="text-center">
|
|
2
18
|
<img
|
|
3
19
|
src="https://raw.githubusercontent.com/homebridge-plugins/homebridge-resideo/latest/branding/Homebridge_x_Resideo.svg"
|
|
@@ -88,11 +104,46 @@
|
|
|
88
104
|
<div id="pageDevices" class="mt-4" style="display: none;">
|
|
89
105
|
<div id="deviceInfo">
|
|
90
106
|
<form>
|
|
91
|
-
<div class="form-group">
|
|
107
|
+
<div class="form-group mb-0">
|
|
92
108
|
<select class="form-control" id="deviceSelect"></select>
|
|
93
109
|
</div>
|
|
94
110
|
</form>
|
|
95
|
-
<
|
|
111
|
+
<ul class="list-group mt-4 mb-4 mx-0" id="configActionList" style="display: none;">
|
|
112
|
+
<li class="list-group-item">
|
|
113
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
114
|
+
<div class="pr-2">
|
|
115
|
+
<div>Add All Devices</div>
|
|
116
|
+
<div class="text-muted action-desc">Add all devices to the config that are not already in the config.</div>
|
|
117
|
+
</div>
|
|
118
|
+
<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">
|
|
119
|
+
<i aria-hidden="true" class="fas fa-arrow-right"></i>
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
</li>
|
|
123
|
+
<li class="list-group-item">
|
|
124
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
125
|
+
<div class="pr-2">
|
|
126
|
+
<div id="deviceConfigTitle">Add This Device</div>
|
|
127
|
+
<div class="text-muted action-desc" id="deviceConfigText">Add this device to the plugin config.</div>
|
|
128
|
+
</div>
|
|
129
|
+
<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">
|
|
130
|
+
<i aria-hidden="true" class="fas fa-arrow-right"></i>
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
</li>
|
|
134
|
+
<li class="list-group-item">
|
|
135
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
136
|
+
<div class="pr-2">
|
|
137
|
+
<div id="hideDeviceTitle">Hide This Device</div>
|
|
138
|
+
<div class="text-muted action-desc" id="hideDeviceText">Hide this device from HomeKit. This adds the device to the config if needed.</div>
|
|
139
|
+
</div>
|
|
140
|
+
<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">
|
|
141
|
+
<i aria-hidden="true" class="fas fa-arrow-right"></i>
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
</li>
|
|
145
|
+
</ul>
|
|
146
|
+
<table class="table w-100 mt-0" id="deviceTable" style="display: none;">
|
|
96
147
|
<thead>
|
|
97
148
|
<tr class="table-active">
|
|
98
149
|
<th scope="col" style="width: 40%;">Device Name</th>
|
|
@@ -181,6 +232,227 @@
|
|
|
181
232
|
; (async () => {
|
|
182
233
|
try {
|
|
183
234
|
const currentConfig = await homebridge.getPluginConfig()
|
|
235
|
+
|
|
236
|
+
// ===== Device picker (add/remove/hide config entries from the Devices tab) =====
|
|
237
|
+
// The control state is always computed from a fresh getPluginConfig() call,
|
|
238
|
+
// so staged-but-unsaved edits made in the Settings tab are respected
|
|
239
|
+
let pickerAccessories = []
|
|
240
|
+
let removeConfirmTimer
|
|
241
|
+
|
|
242
|
+
// The action buttons always show the settings-page arrow; the row text
|
|
243
|
+
// (and the button's aria-label) says what the arrow will do
|
|
244
|
+
const actionArrow = '<i aria-hidden="true" class="fas fa-arrow-right"></i>'
|
|
245
|
+
|
|
246
|
+
// The platform merges device config by exact trimmed id, so the
|
|
247
|
+
// picker mirrors that rather than a looser comparison
|
|
248
|
+
const cleanId = id => (id || '').toString().trim()
|
|
249
|
+
|
|
250
|
+
const getConfigBlocks = async () => {
|
|
251
|
+
const blocks = await homebridge.getPluginConfig()
|
|
252
|
+
if (!blocks.length) {
|
|
253
|
+
blocks.push({ name: 'Resideo' })
|
|
254
|
+
}
|
|
255
|
+
return blocks
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// The devices array lives nested under options
|
|
259
|
+
const getDevicesArray = (config, create) => {
|
|
260
|
+
if (!config.options || typeof config.options !== 'object') {
|
|
261
|
+
if (!create) {
|
|
262
|
+
return null
|
|
263
|
+
}
|
|
264
|
+
config.options = {}
|
|
265
|
+
}
|
|
266
|
+
if (!Array.isArray(config.options.devices)) {
|
|
267
|
+
if (!create) {
|
|
268
|
+
return null
|
|
269
|
+
}
|
|
270
|
+
config.options.devices = []
|
|
271
|
+
}
|
|
272
|
+
return config.options.devices
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const findInConfig = (config, deviceID) => {
|
|
276
|
+
const target = cleanId(deviceID)
|
|
277
|
+
const devices = getDevicesArray(config, false)
|
|
278
|
+
if (!devices) {
|
|
279
|
+
return null
|
|
280
|
+
}
|
|
281
|
+
const index = devices.findIndex(entry => entry && cleanId(entry.deviceID) === target)
|
|
282
|
+
return index === -1 ? null : { index }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const selectedAccessory = () => {
|
|
286
|
+
const uuid = document.getElementById('deviceSelect').value
|
|
287
|
+
return pickerAccessories.find(x => x.UUID === uuid)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const setHideControl = hidden => {
|
|
291
|
+
const btn = document.getElementById('hideDeviceBtn')
|
|
292
|
+
btn.dataset.hidden = hidden ? 'yes' : 'no'
|
|
293
|
+
btn.innerHTML = actionArrow
|
|
294
|
+
btn.setAttribute('aria-label', hidden ? 'Show this device in HomeKit' : 'Hide this device from HomeKit')
|
|
295
|
+
document.getElementById('hideDeviceTitle').innerHTML = hidden ? 'Show This Device' : 'Hide This Device'
|
|
296
|
+
document.getElementById('hideDeviceText').innerHTML = hidden
|
|
297
|
+
? 'Show this device in HomeKit again. The config entry is kept.'
|
|
298
|
+
: 'Hide this device from HomeKit. This adds the device to the config if needed.'
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const setConfigButton = inConfig => {
|
|
302
|
+
const btn = document.getElementById('deviceConfigBtn')
|
|
303
|
+
clearTimeout(removeConfirmTimer)
|
|
304
|
+
delete btn.dataset.confirm
|
|
305
|
+
btn.dataset.state = inConfig ? 'remove' : 'add'
|
|
306
|
+
btn.classList.remove('btn-danger')
|
|
307
|
+
btn.classList.add('btn-primary')
|
|
308
|
+
btn.innerHTML = actionArrow
|
|
309
|
+
btn.setAttribute('aria-label', inConfig ? 'Remove this device from the config' : 'Add this device to the config')
|
|
310
|
+
document.getElementById('deviceConfigTitle').innerHTML = inConfig ? 'Remove This Device' : 'Add This Device'
|
|
311
|
+
document.getElementById('deviceConfigText').innerHTML = inConfig
|
|
312
|
+
? 'Remove this device from the plugin config.'
|
|
313
|
+
: 'Add this device to the plugin config.'
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const addDeviceToConfig = (config, acc) => {
|
|
317
|
+
const devices = getDevicesArray(config, true)
|
|
318
|
+
// configDeviceName is included so the entry is identifiable in the
|
|
319
|
+
// config editor; it matches the current name so nothing is renamed
|
|
320
|
+
devices.push({ deviceID: acc.context.deviceID, configDeviceName: acc.displayName })
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
document.getElementById('deviceConfigBtn').addEventListener('click', async () => {
|
|
324
|
+
try {
|
|
325
|
+
const btn = document.getElementById('deviceConfigBtn')
|
|
326
|
+
const acc = selectedAccessory()
|
|
327
|
+
if (!acc) {
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
const blocks = await getConfigBlocks()
|
|
331
|
+
const config = blocks[0]
|
|
332
|
+
const found = findInConfig(config, acc.context.deviceID)
|
|
333
|
+
if (btn.dataset.state === 'add') {
|
|
334
|
+
// The config may have changed since the button was drawn, so
|
|
335
|
+
// re-check and never add a duplicate - just correct the button
|
|
336
|
+
if (found) {
|
|
337
|
+
setConfigButton(true)
|
|
338
|
+
setHideControl(!!config.options.devices[found.index].hide_device)
|
|
339
|
+
homebridge.toast.info(acc.displayName + ' is already in the config.', 'Not Added')
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
addDeviceToConfig(config, acc)
|
|
343
|
+
await homebridge.updatePluginConfig(blocks)
|
|
344
|
+
setConfigButton(true)
|
|
345
|
+
setHideControl(false)
|
|
346
|
+
homebridge.toast.success(acc.displayName + ' added - press Save to apply.', 'Added To Config')
|
|
347
|
+
} else {
|
|
348
|
+
if (!found) {
|
|
349
|
+
setConfigButton(false)
|
|
350
|
+
setHideControl(false)
|
|
351
|
+
homebridge.toast.info(acc.displayName + ' is not in the config.', 'Not Removed')
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
const entry = config.options.devices[found.index]
|
|
355
|
+
const extraKeys = Object.keys(entry).filter(k => !['deviceID', 'configDeviceName'].includes(k))
|
|
356
|
+
if (extraKeys.length > 0 && btn.dataset.confirm !== 'yes') {
|
|
357
|
+
// The entry has settings beyond id and name which would be
|
|
358
|
+
// lost, so ask for a second click within five seconds
|
|
359
|
+
btn.dataset.confirm = 'yes'
|
|
360
|
+
btn.classList.remove('btn-primary')
|
|
361
|
+
btn.classList.add('btn-danger')
|
|
362
|
+
btn.innerHTML = actionArrow
|
|
363
|
+
btn.setAttribute('aria-label', 'Confirm removing this device from the config')
|
|
364
|
+
document.getElementById('deviceConfigText').innerHTML = 'This entry has extra settings which will be lost. Click again to remove it.'
|
|
365
|
+
removeConfirmTimer = setTimeout(() => setConfigButton(true), 5000)
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
config.options.devices.splice(found.index, 1)
|
|
369
|
+
if (config.options.devices.length === 0) {
|
|
370
|
+
delete config.options.devices
|
|
371
|
+
}
|
|
372
|
+
await homebridge.updatePluginConfig(blocks)
|
|
373
|
+
setConfigButton(false)
|
|
374
|
+
setHideControl(false)
|
|
375
|
+
homebridge.toast.success(acc.displayName + ' removed - press Save to apply.', 'Removed From Config')
|
|
376
|
+
}
|
|
377
|
+
} catch (err) {
|
|
378
|
+
homebridge.toast.error(err.message, 'Error')
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
document.getElementById('hideDeviceBtn').addEventListener('click', async () => {
|
|
383
|
+
try {
|
|
384
|
+
const acc = selectedAccessory()
|
|
385
|
+
if (!acc) {
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
const blocks = await getConfigBlocks()
|
|
389
|
+
const config = blocks[0]
|
|
390
|
+
let found = findInConfig(config, acc.context.deviceID)
|
|
391
|
+
const currentlyHidden = document.getElementById('hideDeviceBtn').dataset.hidden === 'yes'
|
|
392
|
+
if (!currentlyHidden) {
|
|
393
|
+
// Hiding needs a config entry to carry the flag, so create
|
|
394
|
+
// one if the device is not in the config yet
|
|
395
|
+
if (!found) {
|
|
396
|
+
addDeviceToConfig(config, acc)
|
|
397
|
+
found = findInConfig(config, acc.context.deviceID)
|
|
398
|
+
}
|
|
399
|
+
config.options.devices[found.index].hide_device = true
|
|
400
|
+
await homebridge.updatePluginConfig(blocks)
|
|
401
|
+
setConfigButton(true)
|
|
402
|
+
setHideControl(true)
|
|
403
|
+
homebridge.toast.success(acc.displayName + ' will be hidden from HomeKit - press Save to apply.', 'Device Hidden')
|
|
404
|
+
} else {
|
|
405
|
+
if (found) {
|
|
406
|
+
// Drop the flag rather than storing hide_device false, to
|
|
407
|
+
// keep the entry clean
|
|
408
|
+
delete config.options.devices[found.index].hide_device
|
|
409
|
+
await homebridge.updatePluginConfig(blocks)
|
|
410
|
+
}
|
|
411
|
+
setConfigButton(!!found)
|
|
412
|
+
setHideControl(false)
|
|
413
|
+
homebridge.toast.success(acc.displayName + ' will be shown in HomeKit - press Save to apply.', 'Device Unhidden')
|
|
414
|
+
}
|
|
415
|
+
} catch (err) {
|
|
416
|
+
homebridge.toast.error(err.message, 'Error')
|
|
417
|
+
}
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
document.getElementById('addAllBtn').addEventListener('click', async () => {
|
|
421
|
+
try {
|
|
422
|
+
const blocks = await getConfigBlocks()
|
|
423
|
+
const config = blocks[0]
|
|
424
|
+
let added = 0
|
|
425
|
+
let skipped = 0
|
|
426
|
+
pickerAccessories.forEach(acc => {
|
|
427
|
+
if (!acc.context?.deviceID) {
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
if (findInConfig(config, acc.context.deviceID)) {
|
|
431
|
+
skipped += 1
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
addDeviceToConfig(config, acc)
|
|
435
|
+
added += 1
|
|
436
|
+
})
|
|
437
|
+
if (added > 0) {
|
|
438
|
+
await homebridge.updatePluginConfig(blocks)
|
|
439
|
+
homebridge.toast.success(added + ' device(s) added, ' + skipped + ' already in the config - press Save to apply.', 'Added To Config')
|
|
440
|
+
} else {
|
|
441
|
+
homebridge.toast.info('All devices are already in the config.', 'Nothing To Add')
|
|
442
|
+
}
|
|
443
|
+
const acc = selectedAccessory()
|
|
444
|
+
if (acc) {
|
|
445
|
+
const nowFound = findInConfig(config, acc.context.deviceID)
|
|
446
|
+
setConfigButton(!!nowFound)
|
|
447
|
+
if (nowFound) {
|
|
448
|
+
setHideControl(!!config.options.devices[nowFound.index].hide_device)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
} catch (err) {
|
|
452
|
+
homebridge.toast.error(err.message, 'Error')
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
// ===== End device picker =====
|
|
184
456
|
const hostname = window.location.hostname
|
|
185
457
|
try {
|
|
186
458
|
await homebridge.request('Start Resideo Login Server')
|
|
@@ -303,6 +575,7 @@
|
|
|
303
575
|
: 0
|
|
304
576
|
})
|
|
305
577
|
}
|
|
578
|
+
pickerAccessories = cachedAccessories
|
|
306
579
|
const deviceSelect = document.getElementById('deviceSelect')
|
|
307
580
|
deviceSelect.innerHTML = ''
|
|
308
581
|
cachedAccessories.forEach(a => {
|
|
@@ -320,6 +593,11 @@
|
|
|
320
593
|
document.getElementById('model').innerHTML = context.model
|
|
321
594
|
document.getElementById('firmwareRevision').innerHTML = context.firmwareRevision || 'N/A'
|
|
322
595
|
document.getElementById('deviceTable').style.display = 'inline-table'
|
|
596
|
+
const configBlocks = await getConfigBlocks()
|
|
597
|
+
const configEntry = findInConfig(configBlocks[0], context.deviceID)
|
|
598
|
+
setConfigButton(!!configEntry)
|
|
599
|
+
setHideControl(!!(configEntry && configBlocks[0].options.devices[configEntry.index].hide_device))
|
|
600
|
+
document.getElementById('configActionList').style.display = 'block'
|
|
323
601
|
homebridge.hideSpinner()
|
|
324
602
|
}
|
|
325
603
|
deviceSelect.addEventListener('change', event => showDeviceInfo(event.target.value))
|
|
@@ -330,6 +608,7 @@
|
|
|
330
608
|
option.text = 'No Devices'
|
|
331
609
|
deviceSelect.add(option)
|
|
332
610
|
deviceSelect.disabled = true
|
|
611
|
+
document.getElementById('configActionList').style.display = 'none'
|
|
333
612
|
}
|
|
334
613
|
homebridge.hideSpinner()
|
|
335
614
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@homebridge-plugins/homebridge-resideo",
|
|
3
3
|
"displayName": "Resideo",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.2.0-beta.0",
|
|
6
6
|
"description": "The Resideo plugin allows you to access your Resideo device(s) from HomeKit.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "bwp91",
|