homebridge-generac 0.1.0-beta.1 → 1.0.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 CHANGED
@@ -4,6 +4,20 @@ All notable changes to homebridge-generac are listed here. The format follows Ke
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 1.0.0 (September 26, 2026)
8
+
9
+ First stable release, after a soak of the beta on a live install since September 16, 2026.
10
+
11
+ ### Added
12
+
13
+ - Node 24 support. The plugin runs on Node 20, 22 and 24, and CI tests all three.
14
+ - After Confirm, the Reset dialog is replaced in place by a done state that says to click Save and restart Homebridge, and the Reset link is hidden until the page reloads.
15
+
16
+ ### Fixed
17
+
18
+ - The Reset dialog in the Homebridge UI dark theme: it now uses the host's card colours instead of a white panel.
19
+ - The Reset confirmation accepts RESET in any case.
20
+
7
21
  ## 0.1.0-beta.1 (September 15, 2026)
8
22
 
9
23
  First beta of Generac for Homebridge, a ground-up successor to homebridge-mobilelink.
package/README.md CHANGED
@@ -6,7 +6,7 @@ Do not claim it. Once the plugin is verified, replace this comment with the badg
6
6
  [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
7
7
  -->
8
8
 
9
- [![npm version](https://img.shields.io/npm/v/homebridge-generac/beta)](https://www.npmjs.com/package/homebridge-generac)
9
+ [![npm version](https://img.shields.io/npm/v/homebridge-generac)](https://www.npmjs.com/package/homebridge-generac)
10
10
  [![npm downloads](https://img.shields.io/npm/dt/homebridge-generac)](https://www.npmjs.com/package/homebridge-generac)
11
11
  [![License](https://img.shields.io/github/license/arodbuilds/homebridge-generac)](LICENSE)
12
12
  [![Build and Lint](https://github.com/arodbuilds/homebridge-generac/actions/workflows/build.yml/badge.svg)](https://github.com/arodbuilds/homebridge-generac/actions/workflows/build.yml)
@@ -15,7 +15,7 @@ A [Homebridge](https://homebridge.io) plugin that shows the standby generators o
15
15
 
16
16
  Not affiliated with or endorsed by Generac Power Systems, Inc. Generac and Mobile Link are its trademarks. Uses Generac's undocumented Mobile Link API, which can change without notice.
17
17
 
18
- > **Status:** Beta. Please report what works and what does not in the [issue tracker](https://github.com/arodbuilds/homebridge-generac/issues).
18
+ > **Status:** 1.0.0, released September 26, 2026.
19
19
 
20
20
  ## Contents
21
21
 
@@ -37,16 +37,16 @@ Not affiliated with or endorsed by Generac Power Systems, Inc. Generac and Mobil
37
37
  ## Requirements
38
38
 
39
39
  - Homebridge 1.8 or 2.x, with the Homebridge UI for the settings page.
40
- - Node 20 or later.
40
+ - Node 20, 22 or 24.
41
41
  - A Generac Mobile Link account with at least one generator on it.
42
42
  - If the account predates April 21, 2026, a password reset since then. Generac moved Mobile Link sign-in to a new system on that date and did not carry older passwords across, so an older password is rejected until it is reset in the Mobile Link app.
43
43
 
44
44
  ## Install
45
45
 
46
- Search for "Generac" under Plugins in the Homebridge UI and install the beta, or from a shell on the Homebridge host:
46
+ Search for "Generac" under Plugins in the Homebridge UI and install it, or from a shell on the Homebridge host:
47
47
 
48
48
  ```shell
49
- npm i -g homebridge-generac@beta
49
+ npm i -g homebridge-generac
50
50
  ```
51
51
 
52
52
  Then open the plugin's settings from the Plugins page. Everything in the setup below happens on that page. The host's Save button writes config.json and Homebridge restarts the plugin.
@@ -150,7 +150,7 @@ Every sensor also reports Status Active, which is off while the unit is not resp
150
150
 
151
151
  ## Development
152
152
 
153
- Node 20 or later. Clone the repository, then:
153
+ Node 20, 22 or 24. Clone the repository, then:
154
154
 
155
155
  ```shell
156
156
  npm ci
package/SECURITY.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## Supported versions
4
4
 
5
+ | Version | Supported |
6
+ |---|---|
7
+ | 1.0.x | Yes |
8
+ | 0.1.0-beta.x | No |
9
+
5
10
  Only the newest release receives security fixes. Older versions do not.
6
11
 
7
12
  ## Reporting a vulnerability
@@ -219,15 +219,12 @@
219
219
  margin-bottom: 1rem;
220
220
  }
221
221
 
222
- /* The Reset dialog, in the page flow directly below the Reset link (SPEC section 11.2): a bordered panel in the
223
- card's colours, never a fixed overlay. */
222
+ /* The Reset dialog, in the page flow directly below the Reset link (SPEC section 11.2), never a fixed overlay. It
223
+ carries Bootstrap's `card` class, so its fill, border and text are the host's card surface in both themes; this
224
+ rule sets none of them (the host's dark theme leaves `--bs-body-bg` white and paints `.card` itself). */
224
225
  .generac-ui .ns-inline-dialog {
225
226
  margin-top: 0.75rem;
226
227
  max-width: 28rem;
227
- border: 1px solid var(--bs-border-color, rgba(128, 128, 128, 0.35));
228
- border-radius: 0.5rem;
229
- background: var(--bs-body-bg, #fff);
230
- color: var(--bs-body-color, inherit);
231
228
  }
232
229
 
233
230
  .generac-ui .ns-inline-dialog-title {
@@ -81,9 +81,7 @@ body[class*="config-ui-x-dark-mode"] .generac-ui .card {
81
81
  colour, never Bootstrap's dimmed copy of the enabled button and never the host's dark fill. Pointer events stay
82
82
  on so the not-allowed cursor shows (a disabled button fires no click either way). */
83
83
  .generac-ui .btn:disabled,
84
- .generac-ui .btn.disabled,
85
- .ns-modal .btn:disabled,
86
- .ns-modal .btn.disabled {
84
+ .generac-ui .btn.disabled {
87
85
  opacity: 1;
88
86
  color: var(--bs-secondary-color, currentColor);
89
87
  border-color: var(--bs-border-color, currentColor);
@@ -419,9 +417,7 @@ body[class*="config-ui-x-dark-mode"] .generac-ui .btn-link {
419
417
  /* Touch targets: every button, text buttons included, is at least 44px tall on touch devices. */
420
418
  @media (pointer: coarse) {
421
419
  .generac-ui .btn,
422
- .generac-ui .btn-close,
423
- .ns-modal .btn,
424
- .ns-modal .btn-close {
420
+ .generac-ui .btn-close {
425
421
  min-height: 44px;
426
422
  display: inline-flex;
427
423
  align-items: center;
@@ -430,59 +426,13 @@ body[class*="config-ui-x-dark-mode"] .generac-ui .btn-link {
430
426
  }
431
427
 
432
428
  body.ns-touch .generac-ui .btn,
433
- body.ns-touch .generac-ui .btn-close,
434
- body.ns-touch .ns-modal .btn,
435
- body.ns-touch .ns-modal .btn-close {
429
+ body.ns-touch .generac-ui .btn-close {
436
430
  min-height: 44px;
437
431
  display: inline-flex;
438
432
  align-items: center;
439
433
  justify-content: center;
440
434
  }
441
435
 
442
- /* Modal drawn by the page (the Reset dialog). */
443
- .ns-modal-backdrop {
444
- position: fixed;
445
- inset: 0;
446
- z-index: 50;
447
- background: rgba(0, 0, 0, 0.55);
448
- display: flex;
449
- align-items: center;
450
- justify-content: center;
451
- padding: 1rem;
452
- }
453
-
454
- .ns-modal {
455
- background: var(--bs-body-bg, #fff);
456
- color: var(--bs-body-color, inherit);
457
- border-radius: 0.5rem;
458
- width: 100%;
459
- max-width: 28rem;
460
- max-height: 100%;
461
- overflow: auto;
462
- box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.3);
463
- }
464
-
465
- .ns-modal-header {
466
- display: flex;
467
- justify-content: space-between;
468
- align-items: center;
469
- padding: 0.75rem 1rem;
470
- border-bottom: 1px solid var(--bs-border-color, rgba(128, 128, 128, 0.35));
471
- }
472
-
473
- .ns-modal-body {
474
- padding: 1rem;
475
- }
476
-
477
- .ns-modal-actions {
478
- display: flex;
479
- flex-wrap: wrap;
480
- justify-content: flex-end;
481
- gap: 0.5rem;
482
- padding: 0.75rem 1rem;
483
- border-top: 1px solid var(--bs-border-color, rgba(128, 128, 128, 0.35));
484
- }
485
-
486
436
  /* Version and credit footer: one line of secondary text that wraps between its items on narrow screens. */
487
437
  .ns-footer {
488
438
  margin-top: 1.5rem;
@@ -135,6 +135,9 @@ export const SETTINGS = {
135
135
  'Removes every generator and its sensors from the Home app.',
136
136
  'Clears all settings on this page.',
137
137
  ],
138
+ resetDoneTitle: 'Reset done',
139
+ resetDoneBody: 'Click Save, then restart Homebridge to remove the generators from the Home app. '
140
+ + 'Reconnect your Mobile Link account afterwards if you want them back.',
138
141
  };
139
142
  /** Shell strings (SPEC 11.3 F): the disclosure, the Reset dialog, the password toggle, the credit footer and relative times. */
140
143
  export const SHELL = {
@@ -223,10 +223,12 @@ export function inlineConfirm(opts) {
223
223
  /**
224
224
  * A dialog in the page flow (the Reset dialog), drawn where it is opened rather than over the page: a fixed
225
225
  * overlay would pin to the iframe's own top, off-screen when the opener sits at the bottom of a scrolled host
226
- * modal. Escape closes it; `onClose` runs on every close, including the caller's.
226
+ * modal. Escape closes it; `onClose` runs on every close, including the caller's. It is a Bootstrap `card`, so
227
+ * the host paints it like every other card in both themes: the dark theme sets the card's fill and text itself and
228
+ * leaves `--bs-body-bg` at its light value, so a panel of its own would be white on the dark page (SPEC section 11.2).
227
229
  */
228
230
  export function inlineDialog(opts) {
229
- const dialog = el('div', { class: 'ns-inline-dialog', role: 'dialog', 'aria-label': opts.title });
231
+ const dialog = el('div', { class: 'card ns-inline-dialog', role: 'dialog', 'aria-label': opts.title });
230
232
  let onKey = () => undefined;
231
233
  const close = () => {
232
234
  document.removeEventListener('keydown', onKey);
@@ -33,7 +33,7 @@ export class Page {
33
33
  this.config = config;
34
34
  this.root = root;
35
35
  this.status = null;
36
- this.ui = { flow: null, disconnectOpen: false, resetOpen: false, rename: null, checkingSince: null };
36
+ this.ui = { flow: null, disconnectOpen: false, resetOpen: false, resetDone: false, rename: null, checkingSince: null };
37
37
  this.containers = new Map();
38
38
  this.touched = new Set();
39
39
  /** The messages currently shown inline, by path. A focused field only ever loses its message, never gains one. */
@@ -10,14 +10,17 @@ import { DEFAULTS, emptyConfig } from '../model.js';
10
10
  import { hasOneDecimal } from '../validate.js';
11
11
  /**
12
12
  * The Reset dialog: the three lines from SPEC section 11.3 E, "Type RESET to confirm.", Confirm disabled until
13
- * typed. It renders inline directly below the Reset link (SPEC section 11.2) and is page state, so a redraw of
14
- * the section keeps it open; opening it by click focuses the field and scrolls the host modal to the dialog.
13
+ * RESET is typed in any case. It renders inline directly below the Reset link (SPEC section 11.2) and is page state,
14
+ * so a redraw of the section keeps it open; opening it by click focuses the field and scrolls the host modal to the
15
+ * dialog. Confirm redraws the page with the done state in the dialog's place and scrolls the host to it.
15
16
  */
16
17
  function resetDialog(app, opened) {
17
18
  const confirmInput = el('input', { type: 'text', class: 'form-control', autocomplete: 'off', spellcheck: 'false', id: 'gn-reset-confirm' });
18
19
  let close = () => undefined;
19
20
  const confirm = button(SHELL.resetConfirm, () => {
20
21
  close();
22
+ // The done state replaces the dialog in place when the page is redrawn below (SPEC section 11.3 E).
23
+ app.ui.resetDone = true;
21
24
  // Sign out and forget the saved state on the server; the platform removes the accessories on its next start.
22
25
  void callServer('/reset').then(() => app.refreshStatus());
23
26
  if (app.status) {
@@ -28,10 +31,15 @@ function resetDialog(app, opened) {
28
31
  app.ui.rename = null;
29
32
  app.replaceConfig(emptyConfig());
30
33
  toastSuccess(SHELL.resetDone);
34
+ const done = document.querySelector('.gn-reset-done');
35
+ if (done) {
36
+ reveal(done);
37
+ }
31
38
  }, 'btn btn-danger btn-sm');
32
39
  confirm.disabled = true;
33
40
  confirmInput.addEventListener('input', () => {
34
- confirm.disabled = confirmInput.value.trim() !== 'RESET';
41
+ // RESET in any case (SPEC section 11.3 E); the prompt still reads "Type RESET to confirm."
42
+ confirm.disabled = confirmInput.value.trim().toUpperCase() !== 'RESET';
35
43
  });
36
44
  const dialog = inlineDialog({
37
45
  title: SHELL.resetTitle,
@@ -50,9 +58,20 @@ function resetDialog(app, opened) {
50
58
  }
51
59
  return dialog.el;
52
60
  }
53
- /** The Reset link, with the dialog directly below it while open. */
61
+ /**
62
+ * The done state after Confirm (SPEC section 11.3 E): the dialog's place on the page, a card with the title and one
63
+ * line, no buttons. It stays until the page reloads.
64
+ */
65
+ function resetDoneState() {
66
+ return el('div', { class: 'card ns-inline-dialog gn-reset-done', role: 'status' }, el('div', { class: 'ns-inline-dialog-title fw-semibold' }, SETTINGS.resetDoneTitle), el('div', { class: 'ns-inline-dialog-body' }, el('p', { class: 'mb-0' }, SETTINGS.resetDoneBody)));
67
+ }
68
+ /** The Reset link, with the dialog directly below it while open; after Confirm, only the done state. */
54
69
  function resetControl(app) {
55
70
  const holder = el('div', { class: 'gn-reset' });
71
+ if (app.ui.resetDone) {
72
+ holder.appendChild(resetDoneState());
73
+ return holder;
74
+ }
56
75
  const link = dangerLinkButton(SHELL.reset, () => {
57
76
  if (app.ui.resetOpen) {
58
77
  return;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "homebridge-generac",
3
3
  "displayName": "Generac",
4
4
  "type": "module",
5
- "version": "0.1.0-beta.1",
5
+ "version": "1.0.0",
6
6
  "description": "Generac Mobile Link standby generators in HomeKit: running, fault and maintenance sensors plus starting-battery health.",
7
7
  "author": "Alex Rodriguez (https://alex-rodriguez.com)",
8
8
  "license": "Apache-2.0",
@@ -40,7 +40,7 @@
40
40
  "SECURITY.md"
41
41
  ],
42
42
  "engines": {
43
- "node": "^20.18.0 || ^22.10.0",
43
+ "node": "^20.18.0 || ^22.10.0 || ^24.0.0",
44
44
  "homebridge": "^1.8.0 || ^2.0.0-beta.0"
45
45
  },
46
46
  "scripts": {