@homebridge-plugins/homebridge-resideo 3.1.5 → 3.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## v3.2.0 (Pending Release)
2
+
3
+ ### Changed
4
+
5
+ - feat(ui): add, remove and hide devices from the config via the devices tab
6
+ - style(ui): standardise the custom ui layout and sync the support tab with the readme
7
+
1
8
  ## v3.1.5 (2026-07-24)
2
9
 
3
10
  ### 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"
@@ -46,9 +62,7 @@
46
62
  <button type="button" class="btn btn-primary" id="menuSettings">
47
63
  Settings
48
64
  </button>
49
- <button type="button" class="btn btn-primary" id="menuDevices">
50
- Devices
51
- </button>
65
+ <button type="button" class="btn btn-primary" id="menuDevices">My Devices</button>
52
66
  <button type="button" class="btn btn-primary mr-0" id="menuHome">
53
67
  Support
54
68
  </button>
@@ -88,11 +102,46 @@
88
102
  <div id="pageDevices" class="mt-4" style="display: none;">
89
103
  <div id="deviceInfo">
90
104
  <form>
91
- <div class="form-group">
105
+ <div class="form-group mb-0">
92
106
  <select class="form-control" id="deviceSelect"></select>
93
107
  </div>
94
108
  </form>
95
- <table class="table w-100" id="deviceTable" style="display: none;">
109
+ <ul class="list-group mt-4 mb-4 mx-0" id="configActionList" style="display: none;">
110
+ <li class="list-group-item">
111
+ <div class="d-flex justify-content-between align-items-center">
112
+ <div class="pr-2">
113
+ <div>Add All Devices</div>
114
+ <div class="text-muted action-desc">Add all devices to the config that are not already in the config.</div>
115
+ </div>
116
+ <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">
117
+ <i aria-hidden="true" class="fas fa-arrow-right"></i>
118
+ </button>
119
+ </div>
120
+ </li>
121
+ <li class="list-group-item">
122
+ <div class="d-flex justify-content-between align-items-center">
123
+ <div class="pr-2">
124
+ <div id="deviceConfigTitle">Add This Device</div>
125
+ <div class="text-muted action-desc" id="deviceConfigText">Add this device to the plugin config.</div>
126
+ </div>
127
+ <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">
128
+ <i aria-hidden="true" class="fas fa-arrow-right"></i>
129
+ </button>
130
+ </div>
131
+ </li>
132
+ <li class="list-group-item">
133
+ <div class="d-flex justify-content-between align-items-center">
134
+ <div class="pr-2">
135
+ <div id="hideDeviceTitle">Hide This Device</div>
136
+ <div class="text-muted action-desc" id="hideDeviceText">Hide this device from HomeKit. This adds the device to the config if needed.</div>
137
+ </div>
138
+ <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">
139
+ <i aria-hidden="true" class="fas fa-arrow-right"></i>
140
+ </button>
141
+ </div>
142
+ </li>
143
+ </ul>
144
+ <table class="table w-100 mt-0" id="deviceTable" style="display: none;">
96
145
  <thead>
97
146
  <tr class="table-active">
98
147
  <th scope="col" style="width: 40%;">Device Name</th>
@@ -116,71 +165,291 @@
116
165
  </table>
117
166
  </div>
118
167
  </div>
119
- <div id="pageSupport" class="mt-4" style="display: none;">
120
- <p class="text-center lead">Thank you for using <strong>homebridge-resideo</strong></p>
121
- <p class="text-center">The links below will take you to our GitHub wiki</p>
122
- <h5>Setup</h5>
123
- <ul>
124
- <li>
125
- <a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki" target="_blank">Wiki Home</a>
126
- </li>
127
- <li>
128
- <a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Configuration"
129
- target="_blank">Configuration</a>
130
- </li>
131
- <li>
132
- <a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Beta-Version" target="_blank">Beta
133
- Version</a>
134
- </li>
135
- <li>
136
- <a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Node-Version" target="_blank">Node
137
- Version</a>
138
- </li>
139
- <li>
140
- <a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Uninstallation"
141
- target="_blank">Uninstallation</a>
142
- </li>
143
- </ul>
144
- <h5>Features</h5>
145
- <ul>
146
- <li>
147
- <a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Supported-Devices" target="_blank">Supported
148
- Devices</a>
149
- </li>
150
- <li>
151
- <a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Fan-Modes" target="_blank">Fan
152
- Modes</a>
153
- </li>
154
- </ul>
155
- <h5>Help/About</h5>
156
- <ul>
157
- <li>
158
- <a href="https://github.com/homebridge-plugins/homebridge-resideo/issues/new/choose" target="_blank">Support
159
- Request</a>
160
- </li>
161
- <li>
162
- <a href="https://github.com/homebridge-plugins/homebridge-resideo/blob/latest/CHANGELOG.md"
163
- target="_blank">Changelog</a>
164
- </li>
165
- <li>
166
- <a href="https://github.com/sponsors/donavanbecker" target="_blank">About Me</a>
167
- </li>
168
- </ul>
169
- <h5>Disclaimer</h5>
170
- <ul>
171
- <li>
172
- I am in no way affiliated with Resideo and this plugin is a personal project that I maintain in
173
- my free time.
174
- </li>
175
- <li>
176
- Use this plugin entirely at your own risk - please see licence for more information.
177
- </li>
178
- </ul>
168
+ <div id="pageSupport" class="mt-4" style="display: none">
169
+ <p class="text-center lead">Thank you for using <strong>homebridge-resideo</strong></p>
170
+ <h5>Plugin Information</h5>
171
+ <ul>
172
+ <li>This plugin allows you to view and control your <a href="https://www.resideo.com" target="_blank">Resideo</a> (Honeywell Home) devices within HomeKit. The plugin:</li>
173
+ <ul>
174
+ <li>requires a (free) Resideo developer account to work - this is separate from the Resideo account you use in the Resideo app</li>
175
+ <li>connects to the Resideo cloud to discover and control your devices</li>
176
+ </ul>
177
+ </ul>
178
+ <h5>Prerequisites</h5>
179
+ <ul>
180
+ <li>To use this plugin, you will need to already have:</li>
181
+ <ul>
182
+ <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>
183
+ <li><a href="https://homebridge.io" target="_blank">Homebridge</a>: <code>v2</code> - refer to link for more information and installation instructions.</li>
184
+ </ul>
185
+ </ul>
186
+ <h5>Setup</h5>
187
+ <ul>
188
+ <li><a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Installation" target="_blank">Installation</a></li>
189
+ <li><a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Configuration" target="_blank">Configuration</a></li>
190
+ <li><a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Beta-Version" target="_blank">Beta Version</a></li>
191
+ <li><a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Node-Version" target="_blank">Node Version</a></li>
192
+ </ul>
193
+ <h5>Supported Devices</h5>
194
+ <ul>
195
+ <li><a href="https://www.resideo.com/us/en/products/air/thermostats/wifi-thermostats/t10-pro-smart-thermostat-with-redlinkr-room-sensor-thx321wfs2001w-u/" target="_blank">T10 Thermostat</a> - already HomeKit certified</li>
196
+ <li><a href="https://www.resideo.com/us/en/products/air/thermostats/wifi-thermostats/t9-smart-thermostat-with-sensor-rcht9610wfsw2003-u/" target="_blank">T9 Thermostat</a> - already HomeKit certified</li>
197
+ <ul>
198
+ <li><a href="https://www.resideo.com/us/en/products/air/thermostat-accessories/t9-smart-sensor-rchtsensor-1pk-u/" target="_blank">T9 Smart Roomsensors</a></li>
199
+ </ul>
200
+ <li><a href="https://www.resideo.com/us/en/products/air/thermostats/wifi-thermostats/t6-pro-smart-thermostat-multi-stage-3-heat-2-cool-th6320wf2003-u/" target="_blank">T6 Thermostat</a> - already HomeKit certified</li>
201
+ <li><a href="https://www.resideo.com/us/en/products/air/thermostats/wifi-thermostats/t5-smart-thermostat-with-c-wire-adapter-rcht8612wf2005-u/" target="_blank">T5 Thermostat</a> - already HomeKit certified</li>
202
+ <li><a href="https://www.resideo.com/us/en/products/air/thermostats/wifi-thermostats/the-round-smart-thermostat-rch9310wf5003-u/" target="_blank">Round Thermostat</a> - already HomeKit certified</li>
203
+ <li>Some Total Comfort Control thermostats</li>
204
+ <ul>
205
+ <li>Pushing commands may not be supported on some.</li>
206
+ </ul>
207
+ <li><a href="https://www.resideo.com/us/en/products/water/spot-leak-detection/wifi-water-leak-freeze-detector-rchw3610wf1001-u/" target="_blank">WiFi Water Leak &amp; Freeze Detector</a></li>
208
+ <li>Water Shutoff Valves</li>
209
+ </ul>
210
+ <h5>Help/About</h5>
211
+ <ul>
212
+ <li><a href="https://github.com/homebridge-plugins/homebridge-resideo/wiki/Common-Errors" target="_blank">Common Errors</a></li>
213
+ <li><a href="https://github.com/homebridge-plugins/homebridge-resideo/issues/new/choose" target="_blank">Support Request</a></li>
214
+ <li><a href="https://github.com/homebridge-plugins/homebridge-resideo/blob/latest/CHANGELOG.md" target="_blank">Changelog</a></li>
215
+ <li><a href="https://github.com/sponsors/bwp91" target="_blank">About Me</a></li>
216
+ </ul>
217
+ <h5>Credits</h5>
218
+ <ul>
219
+ <li>To <a href="https://github.com/donavanbecker" target="_blank">@donavanbecker</a>: the original creator and maintainer of this plugin.</li>
220
+ <li>To the creators/contributors of <a href="https://homebridge.io" target="_blank">Homebridge</a> who make this plugin possible.</li>
221
+ </ul>
222
+ <h5>Disclaimer</h5>
223
+ <ul>
224
+ <li>I am in no way affiliated with Resideo or Honeywell and this plugin is a personal project that I maintain in my free time.</li>
225
+ <li>Use this plugin entirely at your own risk - please see licence for more information.</li>
226
+ </ul>
179
227
  </div>
180
228
  <script>
181
229
  ; (async () => {
182
230
  try {
183
231
  const currentConfig = await homebridge.getPluginConfig()
232
+
233
+ // ===== Device picker (add/remove/hide config entries from the Devices tab) =====
234
+ // The control state is always computed from a fresh getPluginConfig() call,
235
+ // so staged-but-unsaved edits made in the Settings tab are respected
236
+ let pickerAccessories = []
237
+ let removeConfirmTimer
238
+
239
+ // The action buttons always show the settings-page arrow; the row text
240
+ // (and the button's aria-label) says what the arrow will do
241
+ const actionArrow = '<i aria-hidden="true" class="fas fa-arrow-right"></i>'
242
+
243
+ // The platform merges device config by exact trimmed id, so the
244
+ // picker mirrors that rather than a looser comparison
245
+ const cleanId = id => (id || '').toString().trim()
246
+
247
+ const getConfigBlocks = async () => {
248
+ const blocks = await homebridge.getPluginConfig()
249
+ if (!blocks.length) {
250
+ blocks.push({ name: 'Resideo' })
251
+ }
252
+ return blocks
253
+ }
254
+
255
+ // The devices array lives nested under options
256
+ const getDevicesArray = (config, create) => {
257
+ if (!config.options || typeof config.options !== 'object') {
258
+ if (!create) {
259
+ return null
260
+ }
261
+ config.options = {}
262
+ }
263
+ if (!Array.isArray(config.options.devices)) {
264
+ if (!create) {
265
+ return null
266
+ }
267
+ config.options.devices = []
268
+ }
269
+ return config.options.devices
270
+ }
271
+
272
+ const findInConfig = (config, deviceID) => {
273
+ const target = cleanId(deviceID)
274
+ const devices = getDevicesArray(config, false)
275
+ if (!devices) {
276
+ return null
277
+ }
278
+ const index = devices.findIndex(entry => entry && cleanId(entry.deviceID) === target)
279
+ return index === -1 ? null : { index }
280
+ }
281
+
282
+ const selectedAccessory = () => {
283
+ const uuid = document.getElementById('deviceSelect').value
284
+ return pickerAccessories.find(x => x.UUID === uuid)
285
+ }
286
+
287
+ const setHideControl = hidden => {
288
+ const btn = document.getElementById('hideDeviceBtn')
289
+ btn.dataset.hidden = hidden ? 'yes' : 'no'
290
+ btn.innerHTML = actionArrow
291
+ btn.setAttribute('aria-label', hidden ? 'Show this device in HomeKit' : 'Hide this device from HomeKit')
292
+ document.getElementById('hideDeviceTitle').innerHTML = hidden ? 'Show This Device' : 'Hide This Device'
293
+ document.getElementById('hideDeviceText').innerHTML = hidden
294
+ ? 'Show this device in HomeKit again. The config entry is kept.'
295
+ : 'Hide this device from HomeKit. This adds the device to the config if needed.'
296
+ }
297
+
298
+ const setConfigButton = inConfig => {
299
+ const btn = document.getElementById('deviceConfigBtn')
300
+ clearTimeout(removeConfirmTimer)
301
+ delete btn.dataset.confirm
302
+ btn.dataset.state = inConfig ? 'remove' : 'add'
303
+ btn.classList.remove('btn-danger')
304
+ btn.classList.add('btn-primary')
305
+ btn.innerHTML = actionArrow
306
+ btn.setAttribute('aria-label', inConfig ? 'Remove this device from the config' : 'Add this device to the config')
307
+ document.getElementById('deviceConfigTitle').innerHTML = inConfig ? 'Remove This Device' : 'Add This Device'
308
+ document.getElementById('deviceConfigText').innerHTML = inConfig
309
+ ? 'Remove this device from the plugin config.'
310
+ : 'Add this device to the plugin config.'
311
+ }
312
+
313
+ const addDeviceToConfig = (config, acc) => {
314
+ const devices = getDevicesArray(config, true)
315
+ // configDeviceName is included so the entry is identifiable in the
316
+ // config editor; it matches the current name so nothing is renamed
317
+ devices.push({ deviceID: acc.context.deviceID, configDeviceName: acc.displayName })
318
+ }
319
+
320
+ document.getElementById('deviceConfigBtn').addEventListener('click', async () => {
321
+ try {
322
+ const btn = document.getElementById('deviceConfigBtn')
323
+ const acc = selectedAccessory()
324
+ if (!acc) {
325
+ return
326
+ }
327
+ const blocks = await getConfigBlocks()
328
+ const config = blocks[0]
329
+ const found = findInConfig(config, acc.context.deviceID)
330
+ if (btn.dataset.state === 'add') {
331
+ // The config may have changed since the button was drawn, so
332
+ // re-check and never add a duplicate - just correct the button
333
+ if (found) {
334
+ setConfigButton(true)
335
+ setHideControl(!!config.options.devices[found.index].hide_device)
336
+ homebridge.toast.info(acc.displayName + ' is already in the config.', 'Not Added')
337
+ return
338
+ }
339
+ addDeviceToConfig(config, acc)
340
+ await homebridge.updatePluginConfig(blocks)
341
+ setConfigButton(true)
342
+ setHideControl(false)
343
+ homebridge.toast.success(acc.displayName + ' added - press Save to apply.', 'Added To Config')
344
+ } else {
345
+ if (!found) {
346
+ setConfigButton(false)
347
+ setHideControl(false)
348
+ homebridge.toast.info(acc.displayName + ' is not in the config.', 'Not Removed')
349
+ return
350
+ }
351
+ const entry = config.options.devices[found.index]
352
+ const extraKeys = Object.keys(entry).filter(k => !['deviceID', 'configDeviceName'].includes(k))
353
+ if (extraKeys.length > 0 && btn.dataset.confirm !== 'yes') {
354
+ // The entry has settings beyond id and name which would be
355
+ // lost, so ask for a second click within five seconds
356
+ btn.dataset.confirm = 'yes'
357
+ btn.classList.remove('btn-primary')
358
+ btn.classList.add('btn-danger')
359
+ btn.innerHTML = actionArrow
360
+ btn.setAttribute('aria-label', 'Confirm removing this device from the config')
361
+ document.getElementById('deviceConfigText').innerHTML = 'This entry has extra settings which will be lost. Click again to remove it.'
362
+ removeConfirmTimer = setTimeout(() => setConfigButton(true), 5000)
363
+ return
364
+ }
365
+ config.options.devices.splice(found.index, 1)
366
+ if (config.options.devices.length === 0) {
367
+ delete config.options.devices
368
+ }
369
+ await homebridge.updatePluginConfig(blocks)
370
+ setConfigButton(false)
371
+ setHideControl(false)
372
+ homebridge.toast.success(acc.displayName + ' removed - press Save to apply.', 'Removed From Config')
373
+ }
374
+ } catch (err) {
375
+ homebridge.toast.error(err.message, 'Error')
376
+ }
377
+ })
378
+
379
+ document.getElementById('hideDeviceBtn').addEventListener('click', async () => {
380
+ try {
381
+ const acc = selectedAccessory()
382
+ if (!acc) {
383
+ return
384
+ }
385
+ const blocks = await getConfigBlocks()
386
+ const config = blocks[0]
387
+ let found = findInConfig(config, acc.context.deviceID)
388
+ const currentlyHidden = document.getElementById('hideDeviceBtn').dataset.hidden === 'yes'
389
+ if (!currentlyHidden) {
390
+ // Hiding needs a config entry to carry the flag, so create
391
+ // one if the device is not in the config yet
392
+ if (!found) {
393
+ addDeviceToConfig(config, acc)
394
+ found = findInConfig(config, acc.context.deviceID)
395
+ }
396
+ config.options.devices[found.index].hide_device = true
397
+ await homebridge.updatePluginConfig(blocks)
398
+ setConfigButton(true)
399
+ setHideControl(true)
400
+ homebridge.toast.success(acc.displayName + ' will be hidden from HomeKit - press Save to apply.', 'Device Hidden')
401
+ } else {
402
+ if (found) {
403
+ // Drop the flag rather than storing hide_device false, to
404
+ // keep the entry clean
405
+ delete config.options.devices[found.index].hide_device
406
+ await homebridge.updatePluginConfig(blocks)
407
+ }
408
+ setConfigButton(!!found)
409
+ setHideControl(false)
410
+ homebridge.toast.success(acc.displayName + ' will be shown in HomeKit - press Save to apply.', 'Device Unhidden')
411
+ }
412
+ } catch (err) {
413
+ homebridge.toast.error(err.message, 'Error')
414
+ }
415
+ })
416
+
417
+ document.getElementById('addAllBtn').addEventListener('click', async () => {
418
+ try {
419
+ const blocks = await getConfigBlocks()
420
+ const config = blocks[0]
421
+ let added = 0
422
+ let skipped = 0
423
+ pickerAccessories.forEach(acc => {
424
+ if (!acc.context?.deviceID) {
425
+ return
426
+ }
427
+ if (findInConfig(config, acc.context.deviceID)) {
428
+ skipped += 1
429
+ return
430
+ }
431
+ addDeviceToConfig(config, acc)
432
+ added += 1
433
+ })
434
+ if (added > 0) {
435
+ await homebridge.updatePluginConfig(blocks)
436
+ homebridge.toast.success(added + ' device(s) added, ' + skipped + ' already in the config - press Save to apply.', 'Added To Config')
437
+ } else {
438
+ homebridge.toast.info('All devices are already in the config.', 'Nothing To Add')
439
+ }
440
+ const acc = selectedAccessory()
441
+ if (acc) {
442
+ const nowFound = findInConfig(config, acc.context.deviceID)
443
+ setConfigButton(!!nowFound)
444
+ if (nowFound) {
445
+ setHideControl(!!config.options.devices[nowFound.index].hide_device)
446
+ }
447
+ }
448
+ } catch (err) {
449
+ homebridge.toast.error(err.message, 'Error')
450
+ }
451
+ })
452
+ // ===== End device picker =====
184
453
  const hostname = window.location.hostname
185
454
  try {
186
455
  await homebridge.request('Start Resideo Login Server')
@@ -303,6 +572,7 @@
303
572
  : 0
304
573
  })
305
574
  }
575
+ pickerAccessories = cachedAccessories
306
576
  const deviceSelect = document.getElementById('deviceSelect')
307
577
  deviceSelect.innerHTML = ''
308
578
  cachedAccessories.forEach(a => {
@@ -320,6 +590,11 @@
320
590
  document.getElementById('model').innerHTML = context.model
321
591
  document.getElementById('firmwareRevision').innerHTML = context.firmwareRevision || 'N/A'
322
592
  document.getElementById('deviceTable').style.display = 'inline-table'
593
+ const configBlocks = await getConfigBlocks()
594
+ const configEntry = findInConfig(configBlocks[0], context.deviceID)
595
+ setConfigButton(!!configEntry)
596
+ setHideControl(!!(configEntry && configBlocks[0].options.devices[configEntry.index].hide_device))
597
+ document.getElementById('configActionList').style.display = 'block'
323
598
  homebridge.hideSpinner()
324
599
  }
325
600
  deviceSelect.addEventListener('change', event => showDeviceInfo(event.target.value))
@@ -330,6 +605,7 @@
330
605
  option.text = 'No Devices'
331
606
  deviceSelect.add(option)
332
607
  deviceSelect.disabled = true
608
+ document.getElementById('configActionList').style.display = 'none'
333
609
  }
334
610
  homebridge.hideSpinner()
335
611
  }
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.1.5",
5
+ "version": "3.2.0-beta.1",
6
6
  "description": "The Resideo plugin allows you to access your Resideo device(s) from HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",