homebridge-generac 0.1.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +200 -0
  3. package/NOTICE +20 -0
  4. package/README.md +179 -0
  5. package/SECURITY.md +17 -0
  6. package/config.schema.json +133 -0
  7. package/dist/accessory.d.ts +54 -0
  8. package/dist/accessory.js +191 -0
  9. package/dist/accessory.js.map +1 -0
  10. package/dist/api.d.ts +43 -0
  11. package/dist/api.js +124 -0
  12. package/dist/api.js.map +1 -0
  13. package/dist/attention.d.ts +18 -0
  14. package/dist/attention.js +40 -0
  15. package/dist/attention.js.map +1 -0
  16. package/dist/auth.d.ts +56 -0
  17. package/dist/auth.js +412 -0
  18. package/dist/auth.js.map +1 -0
  19. package/dist/captures.d.ts +9 -0
  20. package/dist/captures.js +42 -0
  21. package/dist/captures.js.map +1 -0
  22. package/dist/cli.d.ts +2 -0
  23. package/dist/cli.js +116 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/exercise.d.ts +42 -0
  26. package/dist/exercise.js +115 -0
  27. package/dist/exercise.js.map +1 -0
  28. package/dist/files.d.ts +5 -0
  29. package/dist/files.js +28 -0
  30. package/dist/files.js.map +1 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.js +6 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/model.d.ts +74 -0
  35. package/dist/model.js +167 -0
  36. package/dist/model.js.map +1 -0
  37. package/dist/platform.d.ts +117 -0
  38. package/dist/platform.js +596 -0
  39. package/dist/platform.js.map +1 -0
  40. package/dist/settings.d.ts +75 -0
  41. package/dist/settings.js +115 -0
  42. package/dist/settings.js.map +1 -0
  43. package/dist/state.d.ts +28 -0
  44. package/dist/state.js +43 -0
  45. package/dist/state.js.map +1 -0
  46. package/dist/types.d.ts +98 -0
  47. package/dist/types.js +48 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/ui/server.d.ts +96 -0
  50. package/dist/ui/server.js +328 -0
  51. package/dist/ui/server.js.map +1 -0
  52. package/homebridge-ui/public/generac-banner.png +0 -0
  53. package/homebridge-ui/public/generac.css +255 -0
  54. package/homebridge-ui/public/index.css +510 -0
  55. package/homebridge-ui/public/index.html +14 -0
  56. package/homebridge-ui/public/js/api.js +31 -0
  57. package/homebridge-ui/public/js/app.js +1 -0
  58. package/homebridge-ui/public/js/card.js +27 -0
  59. package/homebridge-ui/public/js/copy.js +167 -0
  60. package/homebridge-ui/public/js/dom.js +247 -0
  61. package/homebridge-ui/public/js/footer.js +26 -0
  62. package/homebridge-ui/public/js/format.js +62 -0
  63. package/homebridge-ui/public/js/main.js +288 -0
  64. package/homebridge-ui/public/js/mark.js +34 -0
  65. package/homebridge-ui/public/js/model.js +120 -0
  66. package/homebridge-ui/public/js/sections/account.js +272 -0
  67. package/homebridge-ui/public/js/sections/generators.js +165 -0
  68. package/homebridge-ui/public/js/sections/settings.js +114 -0
  69. package/homebridge-ui/public/js/validate.js +45 -0
  70. package/homebridge-ui/server.js +8 -0
  71. package/package.json +70 -0
@@ -0,0 +1,191 @@
1
+ import { batteryPercent } from './model.js';
2
+ /**
3
+ * One Generac generator as a HomeKit accessory.
4
+ *
5
+ * Deliberately not an Outlet or Switch: HomeKit would then read "off" while the
6
+ * unit sits healthy in Ready, and a fault would have nowhere to go but a thrown
7
+ * error (which the Home app renders as "No Response"). Contact sensors give
8
+ * three independently automatable, notifiable booleans, and StatusFault /
9
+ * StatusActive carry the health signals HomeKit already understands.
10
+ *
11
+ * Running contact OPEN while the engine is running
12
+ * Fault contact OPEN on warning / alarm / stopped (see FaultOptions)
13
+ * Maintenance Due contact OPEN when Generac flags service
14
+ * Exercising contact OPEN while an exercise is detected, live or retroactively (optional, SPEC section 7)
15
+ * Battery starting-battery voltage mapped to level + low-battery flag
16
+ */
17
+ export class GeneratorAccessory {
18
+ platform;
19
+ accessory;
20
+ running;
21
+ fault;
22
+ maintenance;
23
+ /** Present only while `exerciseSensor` is on; the platform drives it through `setExercising`. */
24
+ exercising;
25
+ battery;
26
+ state = null;
27
+ exerciseOpen = false;
28
+ displayName;
29
+ constructor(platform, accessory, initial, displayName) {
30
+ this.platform = platform;
31
+ this.accessory = accessory;
32
+ const { Service, Characteristic } = platform.api.hap;
33
+ this.displayName = accessory.displayName;
34
+ accessory
35
+ .getService(Service.AccessoryInformation)
36
+ .setCharacteristic(Characteristic.Manufacturer, 'Generac')
37
+ .setCharacteristic(Characteristic.Model, initial.model)
38
+ .setCharacteristic(Characteristic.SerialNumber, initial.serial)
39
+ .setCharacteristic(Characteristic.FirmwareRevision, platform.firmware);
40
+ this.running = this.contact('Running', 'running');
41
+ this.fault = this.contact('Fault', 'fault');
42
+ this.maintenance = this.contact('Maintenance Due', 'maintenance');
43
+ const cachedExercising = accessory.getServiceById(Service.ContactSensor, 'exercising');
44
+ if (platform.config.exerciseSensor) {
45
+ this.exercising = this.contact('Exercising', 'exercising');
46
+ this.exercising.getCharacteristic(Characteristic.ContactSensorState).onGet(() => this.contactState(this.exerciseOpen));
47
+ }
48
+ else {
49
+ this.exercising = null;
50
+ if (cachedExercising) {
51
+ // The setting was turned off: the cached service goes, like the Attention needed sensor does.
52
+ accessory.removeService(cachedExercising);
53
+ }
54
+ }
55
+ this.battery =
56
+ accessory.getService(Service.Battery) ?? accessory.addService(Service.Battery, `${this.displayName} Battery`);
57
+ this.battery.setCharacteristic(Characteristic.ChargingState, Characteristic.ChargingState.NOT_CHARGEABLE);
58
+ this.battery.getCharacteristic(Characteristic.BatteryLevel).onGet(() => batteryPercent(this.state?.batteryVoltage ?? null));
59
+ this.battery.getCharacteristic(Characteristic.StatusLowBattery).onGet(() => this.lowBattery());
60
+ // Cached-state getters. Never throw from here: a failed poll is reported
61
+ // through StatusActive/StatusFault, not by breaking the accessory.
62
+ this.running.getCharacteristic(Characteristic.ContactSensorState).onGet(() => this.contactState(this.state?.running));
63
+ this.fault.getCharacteristic(Characteristic.ContactSensorState).onGet(() => this.contactState(this.state?.fault));
64
+ this.maintenance.getCharacteristic(Characteristic.ContactSensorState).onGet(() => this.contactState(this.state?.maintenanceDue));
65
+ // A cached accessory may carry a display name from before an override was
66
+ // added or changed. Bring it up to date in place.
67
+ this.setDisplayName(displayName);
68
+ this.update(initial);
69
+ }
70
+ contact(label, subtype) {
71
+ const { Service, Characteristic } = this.platform.api.hap;
72
+ const name = `${this.displayName} ${label}`;
73
+ const svc = this.accessory.getServiceById(Service.ContactSensor, subtype) ??
74
+ this.accessory.addService(Service.ContactSensor, name, subtype);
75
+ svc.setCharacteristic(Characteristic.Name, name);
76
+ if (!svc.testCharacteristic(Characteristic.ConfiguredName)) {
77
+ svc.addOptionalCharacteristic(Characteristic.ConfiguredName);
78
+ }
79
+ if (!svc.getCharacteristic(Characteristic.ConfiguredName).value) {
80
+ svc.setCharacteristic(Characteristic.ConfiguredName, name);
81
+ }
82
+ svc.addOptionalCharacteristic(Characteristic.StatusActive);
83
+ svc.addOptionalCharacteristic(Characteristic.StatusFault);
84
+ return svc;
85
+ }
86
+ /**
87
+ * Apply a display name (the `generators[]` override or the Mobile Link name).
88
+ * Service names follow. ConfiguredName follows only while it still holds the
89
+ * name this plugin gave it, so a rename made in the Home app sticks.
90
+ * Returns true when anything changed, so the platform can persist the cache.
91
+ */
92
+ setDisplayName(name) {
93
+ if (name === this.displayName) {
94
+ return false;
95
+ }
96
+ const { Characteristic } = this.platform.api.hap;
97
+ const previous = this.displayName;
98
+ this.displayName = name;
99
+ this.accessory.displayName = name;
100
+ for (const [svc, label] of this.contacts()) {
101
+ const oldName = `${previous} ${label}`;
102
+ const newName = `${name} ${label}`;
103
+ svc.updateCharacteristic(Characteristic.Name, newName);
104
+ const configured = svc.getCharacteristic(Characteristic.ConfiguredName).value;
105
+ if (!configured || configured === oldName) {
106
+ svc.updateCharacteristic(Characteristic.ConfiguredName, newName);
107
+ }
108
+ }
109
+ this.battery.updateCharacteristic(Characteristic.Name, `${name} Battery`);
110
+ return true;
111
+ }
112
+ /** Every contact sensor with its label, the optional Exercising one included. */
113
+ contacts() {
114
+ const out = [
115
+ [this.running, 'Running'],
116
+ [this.fault, 'Fault'],
117
+ [this.maintenance, 'Maintenance Due'],
118
+ ];
119
+ if (this.exercising) {
120
+ out.push([this.exercising, 'Exercising']);
121
+ }
122
+ return out;
123
+ }
124
+ contactState(open) {
125
+ const { Characteristic } = this.platform.api.hap;
126
+ return open ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED;
127
+ }
128
+ lowBattery() {
129
+ const { Characteristic } = this.platform.api.hap;
130
+ const v = this.state?.batteryVoltage;
131
+ const low = v !== null && v !== undefined && v <= this.platform.config.batteryLowVoltage;
132
+ return low ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
133
+ }
134
+ /** Push a fresh state into HomeKit. Always updates; never gated on identity fields. */
135
+ update(next) {
136
+ const { Characteristic } = this.platform.api.hap;
137
+ const prev = this.state;
138
+ this.state = next;
139
+ const active = next.connected;
140
+ const faultFlag = next.fault ? Characteristic.StatusFault.GENERAL_FAULT : Characteristic.StatusFault.NO_FAULT;
141
+ for (const [svc, open] of [
142
+ [this.running, next.running],
143
+ [this.fault, next.fault],
144
+ [this.maintenance, next.maintenanceDue],
145
+ ]) {
146
+ svc.updateCharacteristic(Characteristic.ContactSensorState, this.contactState(open));
147
+ }
148
+ for (const [svc] of this.contacts()) {
149
+ svc.updateCharacteristic(Characteristic.StatusActive, active);
150
+ svc.updateCharacteristic(Characteristic.StatusFault, faultFlag);
151
+ }
152
+ this.battery.updateCharacteristic(Characteristic.BatteryLevel, batteryPercent(next.batteryVoltage));
153
+ this.battery.updateCharacteristic(Characteristic.StatusLowBattery, this.lowBattery());
154
+ if (!prev || prev.status !== next.status || prev.fault !== next.fault || prev.connected !== next.connected) {
155
+ const bits = [
156
+ `status=${next.statusLabel}`,
157
+ `connected=${next.connected}`,
158
+ next.batteryVoltage !== null ? `battery=${next.batteryVoltage}V` : null,
159
+ next.fault ? `fault=[${next.faultReasons.join('; ')}]` : null,
160
+ next.maintenanceDue ? 'maintenance due' : null,
161
+ ].filter(Boolean);
162
+ this.platform.log.info(`${this.displayName}: ${bits.join(' ')}`);
163
+ }
164
+ }
165
+ /** The last state pushed to HomeKit. */
166
+ get current() {
167
+ return this.state;
168
+ }
169
+ /** Opens or closes the Exercising sensor (SPEC section 7, service 6). A no-op without the sensor. */
170
+ setExercising(open) {
171
+ const { Characteristic } = this.platform.api.hap;
172
+ const changed = open !== this.exerciseOpen;
173
+ this.exerciseOpen = open;
174
+ this.exercising?.updateCharacteristic(Characteristic.ContactSensorState, this.contactState(open));
175
+ if (changed && this.exercising) {
176
+ this.platform.log.info(`${this.displayName}: Exercising sensor ${open ? 'open' : 'closed'}`);
177
+ }
178
+ }
179
+ /** Whether the Exercising sensor is open. */
180
+ get isExercising() {
181
+ return this.exerciseOpen;
182
+ }
183
+ /** Mark the accessory unreachable after repeated poll failures without changing its last state. */
184
+ markUnreachable() {
185
+ const { Characteristic } = this.platform.api.hap;
186
+ for (const [svc] of this.contacts()) {
187
+ svc.updateCharacteristic(Characteristic.StatusActive, false);
188
+ }
189
+ }
190
+ }
191
+ //# sourceMappingURL=accessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessory.js","sourceRoot":"","sources":["../src/accessory.ts"],"names":[],"mappings":"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"}
package/dist/api.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { DPoPKey, InvalidGrantError, type LoginOptions } from './auth.js';
2
+ import type { RawApparatus, RawApparatusDetail, StoredCredentials } from './types.js';
3
+ export declare function readCredentials(file: string): StoredCredentials | null;
4
+ /** Atomic (temp file plus rename), mode 600. See SPEC section 4.4. */
5
+ export declare function writeCredentials(file: string, creds: StoredCredentials): void;
6
+ export declare class ApiError extends Error {
7
+ readonly status: number;
8
+ readonly endpoint: string;
9
+ constructor(message: string, status: number, endpoint: string);
10
+ }
11
+ export interface ClientOptions {
12
+ log?: LoginOptions['log'];
13
+ /** Refresh this many seconds before the access token actually expires. */
14
+ refreshSkewSeconds?: number;
15
+ }
16
+ /**
17
+ * Holds one access token and refreshes it only when needed. The original
18
+ * plugin re-logged-in on every poll; Generac has publicly complained about
19
+ * exactly that traffic, so this client is deliberately frugal.
20
+ */
21
+ export declare class MobileLinkClient {
22
+ private readonly key;
23
+ private readonly refreshToken;
24
+ private readonly opts;
25
+ private accessToken;
26
+ private expiresAt;
27
+ private refreshing;
28
+ constructor(key: DPoPKey, refreshToken: string, opts?: ClientOptions);
29
+ static fromCredentials(creds: StoredCredentials, opts?: ClientOptions): MobileLinkClient;
30
+ private token;
31
+ /**
32
+ * NOTE: Bearer, not DPoP. The token is DPoP-bound (cnf.jkt) but Generac's
33
+ * resource server does not implement DPoP and returns 401 to the
34
+ * `Authorization: DPoP` scheme. Probed by the ha-generac maintainers on
35
+ * June 25, 2026. Do not "fix" this by sending a proof here.
36
+ */
37
+ private get;
38
+ listApparatus(): Promise<RawApparatus[]>;
39
+ apparatusDetails(id: number): Promise<RawApparatusDetail | null>;
40
+ }
41
+ export { InvalidGrantError };
42
+ /** Exponential backoff with jitter, capped. Returns milliseconds to wait. */
43
+ export declare function backoffMs(consecutiveFailures: number, baseMs: number, maxMs: number): number;
package/dist/api.js ADDED
@@ -0,0 +1,124 @@
1
+ import fs from 'node:fs';
2
+ import { DPoPKey, InvalidGrantError, refreshAccessToken, USER_AGENT_API } from './auth.js';
3
+ import { writeFileAtomic } from './files.js';
4
+ const API_BASE = 'https://app.mobilelinkgen.com/api/v5';
5
+ // ---------------------------------------------------------------------------
6
+ // Credentials on disk
7
+ // ---------------------------------------------------------------------------
8
+ export function readCredentials(file) {
9
+ if (!fs.existsSync(file)) {
10
+ return null;
11
+ }
12
+ const c = JSON.parse(fs.readFileSync(file, 'utf8'));
13
+ if (!c.refresh_token || !c.dpop_private_key_pem) {
14
+ return null;
15
+ }
16
+ return c;
17
+ }
18
+ /** Atomic (temp file plus rename), mode 600. See SPEC section 4.4. */
19
+ export function writeCredentials(file, creds) {
20
+ writeFileAtomic(file, JSON.stringify(creds, null, 2) + '\n', 0o600);
21
+ }
22
+ // ---------------------------------------------------------------------------
23
+ // HTTP client
24
+ // ---------------------------------------------------------------------------
25
+ export class ApiError extends Error {
26
+ status;
27
+ endpoint;
28
+ constructor(message, status, endpoint) {
29
+ super(message);
30
+ this.status = status;
31
+ this.endpoint = endpoint;
32
+ this.name = 'ApiError';
33
+ }
34
+ }
35
+ /**
36
+ * Holds one access token and refreshes it only when needed. The original
37
+ * plugin re-logged-in on every poll; Generac has publicly complained about
38
+ * exactly that traffic, so this client is deliberately frugal.
39
+ */
40
+ export class MobileLinkClient {
41
+ key;
42
+ refreshToken;
43
+ opts;
44
+ accessToken = null;
45
+ expiresAt = 0;
46
+ refreshing = null;
47
+ constructor(key, refreshToken, opts = {}) {
48
+ this.key = key;
49
+ this.refreshToken = refreshToken;
50
+ this.opts = opts;
51
+ }
52
+ static fromCredentials(creds, opts) {
53
+ return new MobileLinkClient(DPoPKey.fromPem(creds.dpop_private_key_pem), creds.refresh_token, opts);
54
+ }
55
+ async token() {
56
+ const skew = (this.opts.refreshSkewSeconds ?? 120) * 1000;
57
+ if (this.accessToken && Date.now() < this.expiresAt - skew) {
58
+ return this.accessToken;
59
+ }
60
+ if (!this.refreshing) {
61
+ this.refreshing = refreshAccessToken(this.key, this.refreshToken, this.opts.log)
62
+ .then((t) => {
63
+ this.accessToken = t.access_token;
64
+ this.expiresAt = Date.now() + t.expires_in * 1000;
65
+ return t.access_token;
66
+ })
67
+ .finally(() => {
68
+ this.refreshing = null;
69
+ });
70
+ }
71
+ return this.refreshing;
72
+ }
73
+ /**
74
+ * NOTE: Bearer, not DPoP. The token is DPoP-bound (cnf.jkt) but Generac's
75
+ * resource server does not implement DPoP and returns 401 to the
76
+ * `Authorization: DPoP` scheme. Probed by the ha-generac maintainers on
77
+ * June 25, 2026. Do not "fix" this by sending a proof here.
78
+ */
79
+ async get(endpoint) {
80
+ const token = await this.token();
81
+ const res = await fetch(`${API_BASE}${endpoint}`, {
82
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json', 'User-Agent': USER_AGENT_API },
83
+ });
84
+ if (res.status === 204) {
85
+ return null;
86
+ }
87
+ const text = await res.text();
88
+ if (res.status === 401) {
89
+ // Token was rejected mid-lifetime. Drop it so the next call refreshes.
90
+ this.accessToken = null;
91
+ throw new ApiError(`GET ${endpoint}: 401 unauthorized`, 401, endpoint);
92
+ }
93
+ if (res.status !== 200) {
94
+ throw new ApiError(`GET ${endpoint}: HTTP ${res.status} ${text.slice(0, 200)}`, res.status, endpoint);
95
+ }
96
+ try {
97
+ return JSON.parse(text);
98
+ }
99
+ catch {
100
+ throw new ApiError(`GET ${endpoint}: non-JSON body`, res.status, endpoint);
101
+ }
102
+ }
103
+ async listApparatus() {
104
+ const list = await this.get('/Apparatus/list');
105
+ if (!Array.isArray(list)) {
106
+ throw new ApiError('/Apparatus/list: expected an array', 200, '/Apparatus/list');
107
+ }
108
+ return list;
109
+ }
110
+ async apparatusDetails(id) {
111
+ return this.get(`/Apparatus/details/${id}`);
112
+ }
113
+ }
114
+ export { InvalidGrantError };
115
+ // ---------------------------------------------------------------------------
116
+ // Backoff
117
+ // ---------------------------------------------------------------------------
118
+ /** Exponential backoff with jitter, capped. Returns milliseconds to wait. */
119
+ export function backoffMs(consecutiveFailures, baseMs, maxMs) {
120
+ const exp = Math.min(maxMs, baseMs * 2 ** Math.max(0, consecutiveFailures - 1));
121
+ const jitter = exp * (0.8 + Math.random() * 0.4);
122
+ return Math.round(Math.min(maxMs, jitter));
123
+ }
124
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,cAAc,EAAqB,MAAM,WAAW,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,QAAQ,GAAG,sCAAsC,CAAC;AAExD,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAA+B,CAAC;IAClF,IAAI,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC,oBAAoB,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,CAAsB,CAAC;AAChC,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,KAAwB;IACrE,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC;AACtE,CAAC;AAED,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGf;IACA;IAHlB,YACE,OAAe,EACC,MAAc,EACd,QAAgB;QAEhC,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,WAAM,GAAN,MAAM,CAAQ;QACd,aAAQ,GAAR,QAAQ,CAAQ;QAGhC,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAQD;;;;GAIG;AACH,MAAM,OAAO,gBAAgB;IAMR;IACA;IACA;IAPX,WAAW,GAAkB,IAAI,CAAC;IAClC,SAAS,GAAG,CAAC,CAAC;IACd,UAAU,GAA2B,IAAI,CAAC;IAElD,YACmB,GAAY,EACZ,YAAoB,EACpB,OAAsB,EAAE;QAFxB,QAAG,GAAH,GAAG,CAAS;QACZ,iBAAY,GAAZ,YAAY,CAAQ;QACpB,SAAI,GAAJ,IAAI,CAAoB;IACxC,CAAC;IAEJ,MAAM,CAAC,eAAe,CAAC,KAAwB,EAAE,IAAoB;QACnE,OAAO,IAAI,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACtG,CAAC;IAEO,KAAK,CAAC,KAAK;QACjB,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC;QAC1D,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,EAAE,CAAC;YAC3D,OAAO,IAAI,CAAC,WAAW,CAAC;QAC1B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,IAAI,CAAC,UAAU,GAAG,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;iBAC7E,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;gBACV,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,YAAY,CAAC;gBAClC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC;gBAClD,OAAO,CAAC,CAAC,YAAY,CAAC;YACxB,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE;gBACZ,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACzB,CAAC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,GAAG,CAAI,QAAgB;QACnC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACjC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,GAAG,QAAQ,EAAE,EAAE;YAChD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,YAAY,EAAE,cAAc,EAAE;SACxG,CAAC,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,uEAAuE;YACvE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,MAAM,IAAI,QAAQ,CAAC,OAAO,QAAQ,oBAAoB,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;QACzE,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,QAAQ,CAAC,OAAO,QAAQ,UAAU,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACxG,CAAC;QACD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,QAAQ,CAAC,OAAO,QAAQ,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAAiB,iBAAiB,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,QAAQ,CAAC,oCAAoC,EAAE,GAAG,EAAE,iBAAiB,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,EAAU;QAC/B,OAAO,IAAI,CAAC,GAAG,CAAqB,sBAAsB,EAAE,EAAE,CAAC,CAAC;IAClE,CAAC;CACF;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,mBAA2B,EAAE,MAAc,EAAE,KAAa;IAClF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,mBAAmB,GAAG,CAAC,CAAC,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { PlatformAccessory } from 'homebridge';
2
+ import type { GeneracPlatform } from './platform.js';
3
+ export declare const ATTENTION_NAME = "Generac Attention Needed";
4
+ /**
5
+ * Optional platform-level OccupancySensor (SPEC section 7). Occupancy is
6
+ * detected while the plugin is in Reconnect needed, so a HomeKit automation or
7
+ * notification can tell the user to sign in again.
8
+ */
9
+ export declare class AttentionAccessory {
10
+ private readonly platform;
11
+ readonly accessory: PlatformAccessory;
12
+ private readonly sensor;
13
+ private needed;
14
+ constructor(platform: GeneracPlatform, accessory: PlatformAccessory);
15
+ private value;
16
+ set(needed: boolean): void;
17
+ get isNeeded(): boolean;
18
+ }
@@ -0,0 +1,40 @@
1
+ export const ATTENTION_NAME = 'Generac Attention Needed';
2
+ /**
3
+ * Optional platform-level OccupancySensor (SPEC section 7). Occupancy is
4
+ * detected while the plugin is in Reconnect needed, so a HomeKit automation or
5
+ * notification can tell the user to sign in again.
6
+ */
7
+ export class AttentionAccessory {
8
+ platform;
9
+ accessory;
10
+ sensor;
11
+ needed = false;
12
+ constructor(platform, accessory) {
13
+ this.platform = platform;
14
+ this.accessory = accessory;
15
+ const { Service, Characteristic } = platform.api.hap;
16
+ accessory
17
+ .getService(Service.AccessoryInformation)
18
+ .setCharacteristic(Characteristic.Manufacturer, 'Generac for Homebridge')
19
+ .setCharacteristic(Characteristic.Model, 'Attention needed')
20
+ .setCharacteristic(Characteristic.SerialNumber, 'attention')
21
+ .setCharacteristic(Characteristic.FirmwareRevision, platform.firmware);
22
+ this.sensor = accessory.getService(Service.OccupancySensor) ?? accessory.addService(Service.OccupancySensor, ATTENTION_NAME);
23
+ this.sensor.setCharacteristic(Characteristic.Name, ATTENTION_NAME);
24
+ this.sensor.getCharacteristic(Characteristic.OccupancyDetected).onGet(() => this.value());
25
+ this.set(false);
26
+ }
27
+ value() {
28
+ const { Characteristic } = this.platform.api.hap;
29
+ return this.needed ? Characteristic.OccupancyDetected.OCCUPANCY_DETECTED : Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED;
30
+ }
31
+ set(needed) {
32
+ const { Characteristic } = this.platform.api.hap;
33
+ this.needed = needed;
34
+ this.sensor.updateCharacteristic(Characteristic.OccupancyDetected, this.value());
35
+ }
36
+ get isNeeded() {
37
+ return this.needed;
38
+ }
39
+ }
40
+ //# sourceMappingURL=attention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention.js","sourceRoot":"","sources":["../src/attention.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,cAAc,GAAG,0BAA0B,CAAC;AAEzD;;;;GAIG;AACH,MAAM,OAAO,kBAAkB;IAKV;IACR;IALM,MAAM,CAAU;IACzB,MAAM,GAAG,KAAK,CAAC;IAEvB,YACmB,QAAyB,EACjC,SAA4B;QADpB,aAAQ,GAAR,QAAQ,CAAiB;QACjC,cAAS,GAAT,SAAS,CAAmB;QAErC,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QACrD,SAAS;aACN,UAAU,CAAC,OAAO,CAAC,oBAAoB,CAAE;aACzC,iBAAiB,CAAC,cAAc,CAAC,YAAY,EAAE,wBAAwB,CAAC;aACxE,iBAAiB,CAAC,cAAc,CAAC,KAAK,EAAE,kBAAkB,CAAC;aAC3D,iBAAiB,CAAC,cAAc,CAAC,YAAY,EAAE,WAAW,CAAC;aAC3D,iBAAiB,CAAC,cAAc,CAAC,gBAAgB,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAEzE,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;QAC7H,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,cAAc,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;QACnE,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAC1F,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC;IAEO,KAAK;QACX,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QACjD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,CAAC,iBAAiB,CAAC,sBAAsB,CAAC;IACrI,CAAC;IAED,GAAG,CAAC,MAAe;QACjB,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QACjD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,cAAc,CAAC,iBAAiB,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CACF"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The Auth0 and DPoP login flow in this file is ported from binarydev/ha-generac
3
+ * (Auth0 flow by sslivins, MFA handling by pjordanandrsn, Apache-2.0; see NOTICE).
4
+ * It was validated end to end against a live Mobile Link account with SMS MFA
5
+ * on September 15, 2026.
6
+ */
7
+ export declare const AUTH0_DOMAIN = "auth.ecobee.com";
8
+ export declare const USER_AGENT_API = "mobilelink/86535 CFNetwork/3860.500.112 Darwin/25.4.0";
9
+ export type MfaType = 'sms' | 'otp' | 'email';
10
+ export interface TokenResponse {
11
+ access_token: string;
12
+ refresh_token?: string;
13
+ expires_in: number;
14
+ token_type: string;
15
+ scope?: string;
16
+ }
17
+ export interface LoginOptions {
18
+ /** Called when Auth0 presents a code-based MFA challenge. Return the code. */
19
+ mfaPrompt: (type: MfaType, attempt: number) => Promise<string>;
20
+ /** Optional step logger for diagnostics. Never receives secrets. */
21
+ log?: (step: string, message: string) => void;
22
+ /** Optional sink for the HTML of a failed step. */
23
+ onFailureBody?: (step: string, status: number, body: string) => void;
24
+ }
25
+ export interface LoginResult {
26
+ key: DPoPKey;
27
+ refreshToken: string;
28
+ tokens: TokenResponse;
29
+ }
30
+ export declare class AuthError extends Error {
31
+ readonly step: string;
32
+ constructor(message: string, step: string);
33
+ }
34
+ /** Refresh token has been revoked or expired server-side. Needs a new login. */
35
+ export declare class InvalidGrantError extends AuthError {
36
+ constructor(message: string);
37
+ }
38
+ export declare class DPoPKey {
39
+ private readonly privateKey;
40
+ readonly jwk: {
41
+ crv: string;
42
+ kty: string;
43
+ x: string;
44
+ y: string;
45
+ };
46
+ readonly thumbprint: string;
47
+ private constructor();
48
+ static generate(): DPoPKey;
49
+ static fromPem(pem: string): DPoPKey;
50
+ toPem(): string;
51
+ signProof(htm: string, htu: string, nonce?: string | null): string;
52
+ }
53
+ export declare function extractAuth0ErrorCode(html: string): string | null;
54
+ export declare function challengeType(pathname: string): MfaType | null;
55
+ export declare function login(email: string, password: string, opts: LoginOptions): Promise<LoginResult>;
56
+ export declare function refreshAccessToken(key: DPoPKey, refreshToken: string, log?: LoginOptions['log']): Promise<TokenResponse>;