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 +14 -0
- package/README.md +6 -6
- package/SECURITY.md +5 -0
- package/homebridge-ui/public/generac.css +3 -6
- package/homebridge-ui/public/index.css +3 -53
- package/homebridge-ui/public/js/copy.js +3 -0
- package/homebridge-ui/public/js/dom.js +4 -2
- package/homebridge-ui/public/js/main.js +1 -1
- package/homebridge-ui/public/js/sections/settings.js +23 -4
- package/package.json +2 -2
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
|
[](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
|
-
[](https://www.npmjs.com/package/homebridge-generac)
|
|
10
10
|
[](https://www.npmjs.com/package/homebridge-generac)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](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:**
|
|
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
|
|
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
|
|
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
|
|
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
|
|
153
|
+
Node 20, 22 or 24. Clone the repository, then:
|
|
154
154
|
|
|
155
155
|
```shell
|
|
156
156
|
npm ci
|
package/SECURITY.md
CHANGED
|
@@ -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)
|
|
223
|
-
|
|
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,
|
|
14
|
-
* the section keeps it open; opening it by click focuses the field and scrolls the host modal to the
|
|
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
|
-
|
|
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
|
-
/**
|
|
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": "
|
|
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": {
|