homebridge-dyson-solarcycle-morph 1.7.5 → 1.7.7

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
@@ -5,6 +5,25 @@ All notable changes to this project are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.7.7] — 2026-10-14
9
+
10
+ ### Fixed
11
+
12
+ - **"Add and pair" did nothing when the account also held a device that is not
13
+ a light**, such as a fan or a vacuum. Writing the "Not a Bluetooth light"
14
+ note into the list replaced the buttons with copies that had no click
15
+ handler. The list is now written once and wired afterwards.
16
+ - **Messages about your lights appear next to the lights.** Pairing, unpairing
17
+ and failures to find lights used to report in the account box above, out of
18
+ sight of the button that was pressed.
19
+
20
+ ## [1.7.6] — 2026-10-01
21
+
22
+ ### Changed
23
+
24
+ - **The README shows the settings page**, in light and dark, so it is clear
25
+ before installing what is set where.
26
+
8
27
  ## [1.7.5] — 2026-09-25
9
28
 
10
29
  ### Changed
package/README.md CHANGED
@@ -158,6 +158,10 @@ own once the link does.
158
158
 
159
159
  Everything except the lights is optional.
160
160
 
161
+ <p align="center">
162
+ <img src="https://raw.githubusercontent.com/rummeyer/homebridge-dyson-solarcycle-morph/main/assets/screenshots/settings.png" width="520" alt="The settings page in the Homebridge UI: the MyDyson account, finding the lights, where they are, and one lamp's settings: address, serial, location, daytime, age adjustment and switches">
163
+ </p>
164
+
161
165
  | Setting | |
162
166
  |---|---|
163
167
  | **Name** | What the plugin is called in the Homebridge log |
@@ -87,6 +87,7 @@
87
87
  </p>
88
88
  <button type="button" class="btn btn-secondary" id="morph-find" disabled>Find my lights</button>
89
89
  <div id="morph-found" class="mt-3"></div>
90
+ <div id="morph-lights-message" class="alert morph-msg morph-hidden"></div>
90
91
  <hr>
91
92
  <h6>Configured</h6>
92
93
  <div id="morph-status">…</div>
@@ -118,11 +119,13 @@
118
119
  // block; the account fields above are owned here and left out of the layout.
119
120
  homebridge.showSchemaForm();
120
121
 
121
- const show = (text, kind) => {
122
- $('morph-message').textContent = text;
123
- $('morph-message').className = `alert morph-msg alert-${kind}`;
122
+ // Each card has its own message box, so the answer to a button appears next
123
+ // to it rather than out of sight in the card above.
124
+ const show = (text, kind, where = 'morph-message') => {
125
+ $(where).textContent = text;
126
+ $(where).className = `alert morph-msg alert-${kind}`;
124
127
  };
125
- const clearMessage = () => { $('morph-message').className = 'alert morph-msg morph-hidden'; };
128
+ const clearMessage = (where = 'morph-message') => { $(where).className = 'alert morph-msg morph-hidden'; };
126
129
 
127
130
  /** The plugin's config block, created on first run. */
128
131
  async function readConfig() {
@@ -263,9 +266,9 @@
263
266
  for (const button of document.querySelectorAll('.morph-unpair')) {
264
267
  button.addEventListener('click', () => armThenRun(button, 'Unpair', async () => {
265
268
  const { message } = await homebridge.request('/forget', { serial: button.dataset.serial });
266
- show(message, 'success');
269
+ show(message, 'success', 'morph-lights-message');
267
270
  await refreshStatus();
268
- }));
271
+ }), 'morph-lights-message');
269
272
  }
270
273
  }
271
274
 
@@ -275,7 +278,7 @@
275
278
  * The host iframe swallows window.confirm, so the first click turns the
276
279
  * button into the confirmation rather than opening a dialog that never shows.
277
280
  */
278
- function armThenRun(button, label, task) {
281
+ function armThenRun(button, label, task, where) {
279
282
  if (button.dataset.armed !== 'yes') {
280
283
  button.dataset.armed = 'yes';
281
284
  button.textContent = 'Are you sure?';
@@ -289,17 +292,17 @@
289
292
  }, 5000);
290
293
  return;
291
294
  }
292
- void busy(button, task);
295
+ void busy(button, task, where);
293
296
  }
294
297
 
295
- /** Run `task` with the spinner up and the button disabled. */
296
- async function busy(button, task) {
298
+ /** Run `task` with the spinner up and the button disabled; failures are shown in `where`. */
299
+ async function busy(button, task, where) {
297
300
  button.disabled = true;
298
301
  homebridge.showSpinner();
299
302
  try {
300
303
  await task();
301
304
  } catch (err) {
302
- show(err.message ?? String(err), 'danger');
305
+ show(err.message ?? String(err), 'danger', where);
303
306
  } finally {
304
307
  homebridge.hideSpinner();
305
308
  button.disabled = false;
@@ -327,13 +330,12 @@
327
330
 
328
331
  $('morph-find').addEventListener('click', () => busy($('morph-find'), async () => {
329
332
  clearMessage();
333
+ clearMessage('morph-lights-message');
330
334
  const { lights, skipped } = await homebridge.request('/lights', { account: account() });
331
335
 
332
- if (lights.length === 0) {
333
- $('morph-found').innerHTML =
334
- '<span class="text-muted">No lights on this Dyson account. Register the lamp in the MyDyson app first.</span>';
335
- } else {
336
- $('morph-found').innerHTML = lights.map((light) => {
336
+ let html = lights.length === 0
337
+ ? '<span class="text-muted">No lights on this Dyson account. Register the lamp in the MyDyson app first.</span>'
338
+ : lights.map((light) => {
337
339
  const reachable = Boolean(light.mac);
338
340
  return `<div class="mb-2">
339
341
  <code>${esc(light.serial)}</code>
@@ -348,24 +350,27 @@
348
350
  </div>`;
349
351
  }).join('');
350
352
 
351
- for (const button of document.querySelectorAll('.morph-add')) {
352
- button.addEventListener('click', () => void busy(button, () => addAndPair(button.dataset)));
353
- }
354
- }
355
-
356
353
  // Saying what was left out is what stops someone adding their vacuum.
357
354
  if (skipped.length) {
358
- $('morph-found').innerHTML +=
359
- `<p class="text-muted small mt-3 mb-0">Not a Bluetooth light, so not offered: ` +
355
+ html += `<p class="text-muted small mt-3 mb-0">Not a Bluetooth light, so not offered: ` +
360
356
  skipped.map((d) => `${esc(d.name)} (${esc(d.category ?? 'unknown')})`).join(', ') + '</p>';
361
357
  }
362
- }));
358
+
359
+ // Written once, then wired: assigning innerHTML again afterwards (even with
360
+ // +=) replaces the buttons with copies that have no listeners, so they
361
+ // look fine and do nothing.
362
+ $('morph-found').innerHTML = html;
363
+ for (const button of document.querySelectorAll('.morph-add')) {
364
+ button.addEventListener('click', () =>
365
+ void busy(button, () => addAndPair(button.dataset), 'morph-lights-message'));
366
+ }
367
+ }, 'morph-lights-message'));
363
368
 
364
369
  async function addAndPair({ mac, serial, name }) {
365
370
  const configs = await readConfig();
366
371
  const lights = configs[0].lights;
367
372
  // Re-adding a light should correct its address, not duplicate it.
368
- const existing = lights.find((l) => (l.serial ?? '').trim().toUpperCase() === serial.toUpperCase());
373
+ const existing = lights.find((l) => (l?.serial ?? '').trim().toUpperCase() === serial.toUpperCase());
369
374
  if (existing) {
370
375
  existing.mac = mac;
371
376
  } else {
@@ -374,7 +379,7 @@
374
379
  await homebridge.updatePluginConfig(configs);
375
380
 
376
381
  const { message } = await homebridge.request('/pair', { account: account(), serial });
377
- show(message, 'success');
382
+ show(message, 'success', 'morph-lights-message');
378
383
  await refreshStatus();
379
384
  }
380
385
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-dyson-solarcycle-morph",
3
3
  "displayName": "Dyson Solarcycle Morph",
4
- "version": "1.7.5",
4
+ "version": "1.7.7",
5
5
  "description": "Homebridge plugin for the Bluetooth LE controlled Dyson Solarcycle/Lightcycle Morph desk light",
6
6
  "license": "MIT",
7
7
  "author": "Oliver Rummeyer",