@homebridge-plugins/homebridge-ecovacs 8.1.4 → 8.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
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to `@homebridge-plugins/homebridge-ecovacs` will be documented in this file.
4
4
 
5
+ ## v8.2.0 (Pending Release)
6
+
7
+ ### Changed
8
+
9
+ - feat(ui): add, remove and hide devices from the config via the my devices tab
10
+ - style(ui): standardise the custom ui layout and sync the support tab with the readme
11
+
5
12
  ## v8.1.4 (2026-07-19)
6
13
 
7
14
  ### Changed
@@ -19,6 +19,22 @@
19
19
  color: #eeeeee !important;
20
20
  font-weight: 500 !important;
21
21
  }
22
+
23
+ .list-group-item {
24
+ background-color: #333333;
25
+ border-color: #444444;
26
+ color: #eeeeee;
27
+ }
28
+
29
+ .text-muted {
30
+ color: #aaaaaa !important;
31
+ }
32
+ }
33
+ .min-w-50 {
34
+ min-width: 50px;
35
+ }
36
+ .action-desc {
37
+ font-size: 0.85rem;
22
38
  }
23
39
  select {
24
40
  background-image:
@@ -76,11 +92,46 @@
76
92
  <!-- HAP device view (useMatter: false) -->
77
93
  <div id="deviceInfo">
78
94
  <form>
79
- <div class="form-group">
95
+ <div class="form-group mb-0">
80
96
  <select class="form-control" id="deviceSelect"></select>
81
97
  </div>
82
98
  </form>
83
- <table class="table w-100 mt-3" id="deviceTable" style="display: none">
99
+ <ul class="list-group mt-4 mb-4 mx-0" id="configActionList" style="display: none">
100
+ <li class="list-group-item">
101
+ <div class="d-flex justify-content-between align-items-center">
102
+ <div class="pr-2">
103
+ <div>Add All Devices</div>
104
+ <div class="text-muted action-desc">Add all devices to the config that are not already in the config.</div>
105
+ </div>
106
+ <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">
107
+ <i aria-hidden="true" class="fas fa-arrow-right"></i>
108
+ </button>
109
+ </div>
110
+ </li>
111
+ <li class="list-group-item">
112
+ <div class="d-flex justify-content-between align-items-center">
113
+ <div class="pr-2">
114
+ <div id="deviceConfigTitle">Add This Device</div>
115
+ <div class="text-muted action-desc" id="deviceConfigText">Add this device to the plugin config.</div>
116
+ </div>
117
+ <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">
118
+ <i aria-hidden="true" class="fas fa-arrow-right"></i>
119
+ </button>
120
+ </div>
121
+ </li>
122
+ <li class="list-group-item">
123
+ <div class="d-flex justify-content-between align-items-center">
124
+ <div class="pr-2">
125
+ <div id="hideDeviceTitle">Hide This Device</div>
126
+ <div class="text-muted action-desc" id="hideDeviceText">Hide this device from HomeKit. This adds the device to the config if needed.</div>
127
+ </div>
128
+ <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">
129
+ <i aria-hidden="true" class="fas fa-arrow-right"></i>
130
+ </button>
131
+ </div>
132
+ </li>
133
+ </ul>
134
+ <table class="table w-100 mt-0" id="deviceTable" style="display: none">
84
135
  <thead>
85
136
  <tr class="table-active">
86
137
  <th scope="col" style="width: 40%">Device Name</th>
@@ -128,138 +179,292 @@
128
179
  </div>
129
180
  </div>
130
181
  <div id="pageSupport" class="mt-4" style="display: none">
131
- <p class="text-center lead">
132
- Thank you for using <strong>homebridge-ecovacs</strong>
133
- </p>
134
- <p class="text-center">The links below will take you to our GitHub wiki</p>
135
- <h4>Setup</h4>
136
- <ul>
137
- <li>
138
- <a
139
- href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Installation"
140
- target="_blank"
141
- >Installation</a
142
- >
143
- </li>
144
- <li>
145
- <a
146
- href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Configuration"
147
- target="_blank"
148
- >Configuration</a
149
- >
150
- </li>
151
- <li>
152
- <a
153
- href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Beta-Version"
154
- target="_blank"
155
- >Beta Version</a
156
- >
157
- </li>
158
- <li>
159
- <a
160
- href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Node-Version"
161
- target="_blank"
162
- >Node Version</a
163
- >
164
- </li>
165
- </ul>
166
- <h4>Features</h4>
182
+ <p class="text-center lead">Thank you for using <strong>homebridge-ecovacs</strong></p>
183
+ <h5>Plugin Information</h5>
184
+ <ul>
185
+ <li>This plugin allows you to view and control your Ecovacs or Yeedi devices within HomeKit. The plugin:</li>
167
186
  <ul>
168
- <li>
169
- <a
170
- href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Supported-Devices"
171
- target="_blank"
172
- >Supported Devices</a
173
- >
174
- </li>
175
- <li>
176
- <a
177
- href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Speed-Control"
178
- target="_blank"
179
- >Speed Control</a
180
- >
181
- </li>
182
- <li>
183
- <a
184
- href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Custom-Areas"
185
- target="_blank"
186
- >Custom Areas</a
187
- >
188
- </li>
187
+ <li>requires your Ecovacs or Yeedi credentials to function</li>
188
+ <li>uses a cloud-based connection (i.e. requires the internet)</li>
189
189
  </ul>
190
- <h4>Help/About</h4>
190
+ </ul>
191
+ <h5>Prerequisites</h5>
192
+ <ul>
193
+ <li>To use this plugin, you will need to already have:</li>
191
194
  <ul>
192
- <li>
193
- <a
194
- href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Common-Errors"
195
- target="_blank"
196
- >Common Errors</a
197
- >
198
- </li>
199
- <li>
200
- <a
201
- href="https://github.com/homebridge-plugins/homebridge-ecovacs/issues/new/choose"
202
- target="_blank"
203
- >Support Request</a
204
- >
205
- </li>
206
- <li>
207
- <a
208
- href="https://github.com/homebridge-plugins/homebridge-ecovacs/blob/latest/CHANGELOG.md"
209
- target="_blank"
210
- >Changelog</a
211
- >
212
- </li>
213
- <li>
214
- <a href="https://github.com/sponsors/bwp91" target="_blank">About Me</a>
215
- </li>
216
- </ul>
217
- <h4>Credits</h4>
218
- <ul>
219
- <li>
220
- To the owner of
221
- <a href="https://github.com/mrbungle64/ecovacs-deebot.js" target="_blank"
222
- >ecovacs-deebot.js</a
223
- >
224
- client:
225
- <a href="https://github.com/mrbungle64" target="_blank">@mrbungle64</a>.
226
- </li>
227
- <li>
228
- To the owner of
229
- <a
230
- href="https://github.com/nicoduj/homebridge-deebotEcovacs"
231
- target="_blank"
232
- >homebridge-deebotEcovacs</a
233
- >: <a href="https://github.com/nicoduj" target="_blank">@nicoduj</a>.
234
- </li>
235
- <li>
236
- To the creator of the awesome plugin header logo:
237
- <a href="https://www.instagram.com/keryan.me" target="_blank"
238
- >Keryan Belahcene</a
239
- >.
240
- </li>
241
- <li>
242
- To the creators/contributors of
243
- <a href="https://homebridge.io" target="_blank">Homebridge</a> who make
244
- this plugin possible.
245
- </li>
246
- </ul>
247
- <h4>Disclaimer</h4>
248
- <ul>
249
- <li>
250
- I am in no way affiliated with Ecovacs and this plugin is a personal
251
- project that I maintain in my free time.
252
- </li>
253
- <li>
254
- Use this plugin entirely at your own risk - please see licence for more
255
- information.
256
- </li>
195
+ <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>
196
+ <li><a href="https://homebridge.io" target="_blank">Homebridge</a>: <code>v2.0</code> or above - refer to link for more information and installation instructions.</li>
257
197
  </ul>
198
+ </ul>
199
+ <h5>Setup</h5>
200
+ <ul>
201
+ <li><a href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Installation" target="_blank">Installation</a></li>
202
+ <li><a href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Configuration" target="_blank">Configuration</a></li>
203
+ <li><a href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Beta-Version" target="_blank">Beta Version</a></li>
204
+ <li><a href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Node-Version" target="_blank">Node Version</a></li>
205
+ </ul>
206
+ <h5>Features</h5>
207
+ <ul>
208
+ <li><a href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Supported-Devices" target="_blank">Supported Devices</a></li>
209
+ <li><a href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Speed-Control" target="_blank">Speed Control</a></li>
210
+ <li><a href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Predefined-Areas" target="_blank">Predefined Areas</a></li>
211
+ </ul>
212
+ <h5>Help/About</h5>
213
+ <ul>
214
+ <li><a href="https://github.com/homebridge-plugins/homebridge-ecovacs/wiki/Common-Errors" target="_blank">Common Errors</a></li>
215
+ <li><a href="https://github.com/homebridge-plugins/homebridge-ecovacs/issues/new/choose" target="_blank">Support Request</a></li>
216
+ <li><a href="https://github.com/homebridge-plugins/homebridge-ecovacs/blob/latest/CHANGELOG.md" target="_blank">Changelog</a></li>
217
+ <li><a href="https://github.com/sponsors/bwp91" target="_blank">About Me</a></li>
218
+ </ul>
219
+ <h5>Credits</h5>
220
+ <ul>
221
+ <li>To the owner of <a href="https://github.com/mrbungle64/ecovacs-deebot.js" target="_blank">ecovacs-deebot.js</a> client: <a href="https://github.com/mrbungle64" target="_blank">@mrbungle64</a>.</li>
222
+ <li>To the owner of <a href="https://github.com/nicoduj/homebridge-deebotEcovacs" target="_blank">homebridge-deebotEcovacs</a>: <a href="https://github.com/nicoduj" target="_blank">@nicoduj</a>.</li>
223
+ <li>To the creator of the awesome plugin header logo: <a href="https://www.instagram.com/keryan.me" target="_blank">Keryan Belahcene</a>.</li>
224
+ <li>To the creators/contributors of <a href="https://homebridge.io" target="_blank">Homebridge</a> who make this plugin possible.</li>
225
+ </ul>
226
+ <h5>Disclaimer</h5>
227
+ <ul>
228
+ <li>I am in no way affiliated with Ecovacs/Deebot/Yeedi and this plugin is a personal project that I maintain in my free time.</li>
229
+ <li>Use this plugin entirely at your own risk - please see licence for more information.</li>
230
+ </ul>
258
231
  </div>
259
232
  <script>
260
233
  (async () => {
261
234
  try {
262
235
  const currentConfig = await homebridge.getPluginConfig();
236
+
237
+ // ===== Device picker (add/remove/hide config entries from the My Devices tab) =====
238
+ // The control state is always computed from a fresh getPluginConfig() call,
239
+ // so staged-but-unsaved edits made in the Settings tab are respected
240
+ let pickerAccessories = [];
241
+ let removeConfirmTimer;
242
+
243
+ // The action buttons always show the settings-page arrow; the row text
244
+ // (and the button's aria-label) says what the arrow will do
245
+ const actionArrow = '<i aria-hidden="true" class="fas fa-arrow-right"></i>';
246
+
247
+ // The platform matches device ids exactly (only whitespace is stripped),
248
+ // so the picker mirrors that rather than a looser comparison
249
+ const cleanId = (id) => (id || "").toString().replace(/\s+/g, "");
250
+
251
+ const getConfigBlocks = async () => {
252
+ const blocks = await homebridge.getPluginConfig();
253
+ if (!blocks.length) {
254
+ blocks.push({ name: "Deebot" });
255
+ }
256
+ return blocks;
257
+ };
258
+
259
+ const findInConfig = (config, deviceId) => {
260
+ const target = cleanId(deviceId);
261
+ if (!Array.isArray(config.devices)) {
262
+ return null;
263
+ }
264
+ const index = config.devices.findIndex(
265
+ (entry) => entry && cleanId(entry.deviceId) === target,
266
+ );
267
+ return index === -1 ? null : { index };
268
+ };
269
+
270
+ const selectedAccessory = () => {
271
+ const deviceId = document.getElementById("deviceSelect").value;
272
+ return pickerAccessories.find((x) => x.context.ecoDeviceId === deviceId);
273
+ };
274
+
275
+ const setHideControl = (hidden) => {
276
+ const btn = document.getElementById("hideDeviceBtn");
277
+ btn.dataset.hidden = hidden ? "yes" : "no";
278
+ btn.innerHTML = actionArrow;
279
+ btn.setAttribute(
280
+ "aria-label",
281
+ hidden ? "Show this device in HomeKit" : "Hide this device from HomeKit",
282
+ );
283
+ document.getElementById("hideDeviceTitle").innerHTML = hidden
284
+ ? "Show This Device"
285
+ : "Hide This Device";
286
+ document.getElementById("hideDeviceText").innerHTML = hidden
287
+ ? "Show this device in HomeKit again. The config entry is kept."
288
+ : "Hide this device from HomeKit. This adds the device to the config if needed.";
289
+ };
290
+
291
+ const setConfigButton = (inConfig) => {
292
+ const btn = document.getElementById("deviceConfigBtn");
293
+ clearTimeout(removeConfirmTimer);
294
+ delete btn.dataset.confirm;
295
+ btn.dataset.state = inConfig ? "remove" : "add";
296
+ btn.classList.remove("btn-danger");
297
+ btn.classList.add("btn-primary");
298
+ btn.innerHTML = actionArrow;
299
+ btn.setAttribute(
300
+ "aria-label",
301
+ inConfig ? "Remove this device from the config" : "Add this device to the config",
302
+ );
303
+ document.getElementById("deviceConfigTitle").innerHTML = inConfig
304
+ ? "Remove This Device"
305
+ : "Add This Device";
306
+ document.getElementById("deviceConfigText").innerHTML = inConfig
307
+ ? "Remove this device from the plugin config."
308
+ : "Add this device to the plugin config.";
309
+ };
310
+
311
+ const addDeviceToConfig = (config, acc) => {
312
+ if (!Array.isArray(config.devices)) {
313
+ config.devices = [];
314
+ }
315
+ // label is included so the entry is identifiable in the config editor
316
+ config.devices.push({
317
+ label: acc.displayName,
318
+ deviceId: acc.context.ecoDeviceId,
319
+ });
320
+ };
321
+
322
+ document.getElementById("deviceConfigBtn").addEventListener("click", async () => {
323
+ try {
324
+ const btn = document.getElementById("deviceConfigBtn");
325
+ const acc = selectedAccessory();
326
+ if (!acc) {
327
+ return;
328
+ }
329
+ const blocks = await getConfigBlocks();
330
+ const config = blocks[0];
331
+ const found = findInConfig(config, acc.context.ecoDeviceId);
332
+ if (btn.dataset.state === "add") {
333
+ // The config may have changed since the button was drawn, so
334
+ // re-check and never add a duplicate - just correct the button
335
+ if (found) {
336
+ setConfigButton(true);
337
+ setHideControl(!!config.devices[found.index].ignoreDevice);
338
+ homebridge.toast.info(acc.displayName + " is already in the config.", "Not Added");
339
+ return;
340
+ }
341
+ addDeviceToConfig(config, acc);
342
+ await homebridge.updatePluginConfig(blocks);
343
+ setConfigButton(true);
344
+ setHideControl(false);
345
+ homebridge.toast.success(acc.displayName + " added - press Save to apply.", "Added To Config");
346
+ } else {
347
+ if (!found) {
348
+ setConfigButton(false);
349
+ setHideControl(false);
350
+ homebridge.toast.info(acc.displayName + " is not in the config.", "Not Removed");
351
+ return;
352
+ }
353
+ const entry = config.devices[found.index];
354
+ const extraKeys = Object.keys(entry).filter(
355
+ (k) => !["deviceId", "label"].includes(k),
356
+ );
357
+ if (extraKeys.length > 0 && btn.dataset.confirm !== "yes") {
358
+ // The entry has settings beyond id and label which would be
359
+ // lost, so ask for a second click within five seconds
360
+ btn.dataset.confirm = "yes";
361
+ btn.classList.remove("btn-primary");
362
+ btn.classList.add("btn-danger");
363
+ btn.innerHTML = actionArrow;
364
+ btn.setAttribute("aria-label", "Confirm removing this device from the config");
365
+ document.getElementById("deviceConfigText").innerHTML =
366
+ "This entry has extra settings which will be lost. Click again to remove it.";
367
+ removeConfirmTimer = setTimeout(() => setConfigButton(true), 5000);
368
+ return;
369
+ }
370
+ config.devices.splice(found.index, 1);
371
+ if (config.devices.length === 0) {
372
+ delete config.devices;
373
+ }
374
+ await homebridge.updatePluginConfig(blocks);
375
+ setConfigButton(false);
376
+ setHideControl(false);
377
+ homebridge.toast.success(acc.displayName + " removed - press Save to apply.", "Removed From Config");
378
+ }
379
+ } catch (err) {
380
+ homebridge.toast.error(err.message, "Error");
381
+ }
382
+ });
383
+
384
+ document.getElementById("hideDeviceBtn").addEventListener("click", async () => {
385
+ try {
386
+ const acc = selectedAccessory();
387
+ if (!acc) {
388
+ return;
389
+ }
390
+ const blocks = await getConfigBlocks();
391
+ const config = blocks[0];
392
+ let found = findInConfig(config, acc.context.ecoDeviceId);
393
+ const currentlyHidden =
394
+ document.getElementById("hideDeviceBtn").dataset.hidden === "yes";
395
+ if (!currentlyHidden) {
396
+ // Hiding needs a config entry to carry the flag, so create one if
397
+ // the device is not in the config yet
398
+ if (!found) {
399
+ addDeviceToConfig(config, acc);
400
+ found = findInConfig(config, acc.context.ecoDeviceId);
401
+ }
402
+ config.devices[found.index].ignoreDevice = true;
403
+ await homebridge.updatePluginConfig(blocks);
404
+ setConfigButton(true);
405
+ setHideControl(true);
406
+ homebridge.toast.success(
407
+ acc.displayName + " will be hidden from HomeKit - press Save to apply.",
408
+ "Device Hidden",
409
+ );
410
+ } else {
411
+ if (found) {
412
+ // Drop the flag rather than storing ignoreDevice false, to keep
413
+ // the entry clean
414
+ delete config.devices[found.index].ignoreDevice;
415
+ await homebridge.updatePluginConfig(blocks);
416
+ }
417
+ setConfigButton(!!found);
418
+ setHideControl(false);
419
+ homebridge.toast.success(
420
+ acc.displayName + " will be shown in HomeKit - press Save to apply.",
421
+ "Device Unhidden",
422
+ );
423
+ }
424
+ } catch (err) {
425
+ homebridge.toast.error(err.message, "Error");
426
+ }
427
+ });
428
+
429
+ document.getElementById("addAllBtn").addEventListener("click", async () => {
430
+ try {
431
+ const blocks = await getConfigBlocks();
432
+ const config = blocks[0];
433
+ let added = 0;
434
+ let skipped = 0;
435
+ pickerAccessories.forEach((acc) => {
436
+ if (!acc.context?.ecoDeviceId) {
437
+ return;
438
+ }
439
+ if (findInConfig(config, acc.context.ecoDeviceId)) {
440
+ skipped += 1;
441
+ return;
442
+ }
443
+ addDeviceToConfig(config, acc);
444
+ added += 1;
445
+ });
446
+ if (added > 0) {
447
+ await homebridge.updatePluginConfig(blocks);
448
+ homebridge.toast.success(
449
+ added + " device(s) added, " + skipped + " already in the config - press Save to apply.",
450
+ "Added To Config",
451
+ );
452
+ } else {
453
+ homebridge.toast.info("All devices are already in the config.", "Nothing To Add");
454
+ }
455
+ const acc = selectedAccessory();
456
+ if (acc) {
457
+ const nowFound = findInConfig(config, acc.context.ecoDeviceId);
458
+ setConfigButton(!!nowFound);
459
+ if (nowFound) {
460
+ setHideControl(!!config.devices[nowFound.index].ignoreDevice);
461
+ }
462
+ }
463
+ } catch (err) {
464
+ homebridge.toast.error(err.message, "Error");
465
+ }
466
+ });
467
+ // ===== End device picker =====
263
468
  showIntro = () => {
264
469
  homebridge.disableSaveButton?.();
265
470
  const introContinue = document.getElementById("introContinue");
@@ -274,7 +479,9 @@
274
479
  };
275
480
  showDevices = async () => {
276
481
  homebridge.showSpinner();
277
- homebridge.disableSaveButton?.();
482
+ // The save button stays enabled here so entries staged by the picker
483
+ // buttons can be saved without switching back to the Settings tab
484
+ homebridge.enableSaveButton?.();
278
485
  homebridge.hideSchemaForm();
279
486
  document.getElementById("menuHome").classList.remove("btn-elegant");
280
487
  document.getElementById("menuHome").classList.add("btn-primary");
@@ -382,6 +589,7 @@
382
589
  : 0;
383
590
  });
384
591
  }
592
+ pickerAccessories = cachedAccessories;
385
593
  const deviceSelect = document.getElementById("deviceSelect");
386
594
  deviceSelect.innerHTML = "";
387
595
  cachedAccessories.forEach((a) => {
@@ -421,6 +629,11 @@
421
629
  : "";
422
630
  document.getElementById("deviceTable").style.display =
423
631
  "inline-table";
632
+ const configBlocks = await getConfigBlocks();
633
+ const configEntry = findInConfig(configBlocks[0], context.ecoDeviceId);
634
+ setConfigButton(!!configEntry);
635
+ setHideControl(!!(configEntry && configBlocks[0].devices[configEntry.index].ignoreDevice));
636
+ document.getElementById("configActionList").style.display = "block";
424
637
  homebridge.hideSpinner();
425
638
  };
426
639
  deviceSelect.addEventListener("change", (event) =>
@@ -433,6 +646,7 @@
433
646
  option.text = "No Devices";
434
647
  deviceSelect.add(option);
435
648
  deviceSelect.disabled = true;
649
+ document.getElementById("configActionList").style.display = "none";
436
650
  }
437
651
  }
438
652
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "Homebridge Ecovacs",
4
4
  "alias": "Deebot",
5
5
  "type": "module",
6
- "version": "8.1.4",
6
+ "version": "8.2.0-beta.1",
7
7
  "description": "Homebridge plugin to integrate Ecovacs Deebot/Yeedi devices into HomeKit.",
8
8
  "author": {
9
9
  "name": "bwp91",
@@ -39,6 +39,7 @@
39
39
  "keywords": [
40
40
  "homebridge",
41
41
  "homebridge-plugin",
42
+ "supports-matter",
42
43
  "homebridge-deebot",
43
44
  "homebridge-ecovacs",
44
45
  "hoobs",