homebridge-mynest 2.1.1 → 2.1.2
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 +7 -0
- package/dist/api/session.d.ts +1 -1
- package/dist/api/session.js +25 -9
- package/dist/errors/index.d.ts +16 -2
- package/dist/errors/index.js +18 -3
- package/dist/nest-signals.d.ts +5 -3
- package/dist/nest-signals.js +16 -9
- package/dist/platform.js +39 -9
- package/docs/PROTOCOL.md +1 -1
- package/docs/README-DETAILED.md +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here. From the first release onward, [release-please](https://github.com/googleapis/release-please) owns this file — do not hand-edit version headings after `0.1.0` ships.
|
|
4
4
|
|
|
5
|
+
## [2.1.2](https://github.com/tbaur/homebridge-mynest/compare/v2.1.1...v2.1.2) (2026-10-26)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* page auth-failed when Nest rejects the token ([#72](https://github.com/tbaur/homebridge-mynest/issues/72)) ([d69bf69](https://github.com/tbaur/homebridge-mynest/commit/d69bf69e1e67e4f75e8a5148edcbd2c933499ee4))
|
|
11
|
+
|
|
5
12
|
## [2.1.1](https://github.com/tbaur/homebridge-mynest/compare/v2.1.0...v2.1.1) (2026-10-26)
|
|
6
13
|
|
|
7
14
|
|
package/dist/api/session.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export declare function authenticatedHeaders(session: NestSession): Record<strin
|
|
|
33
33
|
/**
|
|
34
34
|
* Exchange the configured access token for a usable session.
|
|
35
35
|
*
|
|
36
|
-
* @throws {AuthenticationError} When Nest rejects the token.
|
|
36
|
+
* @throws {AuthenticationError} When Nest rejects the token (HTTP 400 or 401).
|
|
37
37
|
* @throws {SessionShapeError} When the response is accepted but omits a field
|
|
38
38
|
* the plugin needs, or names a transport host off Nest infrastructure. Either
|
|
39
39
|
* means Nest changed the contract; a new token would not help.
|
package/dist/api/session.js
CHANGED
|
@@ -48,26 +48,42 @@ function authenticatedHeaders(session) {
|
|
|
48
48
|
'Content-Type': 'application/json',
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Fetch `GET /session`, reporting HTTP 400 or 401 as a rejected token.
|
|
53
|
+
*
|
|
54
|
+
* @throws {AuthenticationError} On HTTP 400 or 401.
|
|
55
|
+
*/
|
|
56
|
+
async function requestSession(options) {
|
|
57
|
+
try {
|
|
58
|
+
return await (0, http_1.requestJson)(options.endpoints.sessionUrl, {
|
|
59
|
+
method: 'GET',
|
|
60
|
+
headers: sessionHeaders(options.accessToken),
|
|
61
|
+
timeoutMs: settings_1.SESSION_TIMEOUT_MS,
|
|
62
|
+
fetchImpl: options.fetchImpl,
|
|
63
|
+
signal: options.signal,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (!(0, errors_1.isSessionTokenRejection)(error) || error instanceof errors_1.AuthenticationError) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
throw new errors_1.AuthenticationError(error.message, { cause: error, status: error.httpStatus });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
51
73
|
/**
|
|
52
74
|
* Exchange the configured access token for a usable session.
|
|
53
75
|
*
|
|
54
|
-
* @throws {AuthenticationError} When Nest rejects the token.
|
|
76
|
+
* @throws {AuthenticationError} When Nest rejects the token (HTTP 400 or 401).
|
|
55
77
|
* @throws {SessionShapeError} When the response is accepted but omits a field
|
|
56
78
|
* the plugin needs, or names a transport host off Nest infrastructure. Either
|
|
57
79
|
* means Nest changed the contract; a new token would not help.
|
|
58
80
|
*/
|
|
59
81
|
async function openSession(options) {
|
|
60
|
-
const { accessToken,
|
|
82
|
+
const { accessToken, log } = options;
|
|
61
83
|
if (log.debugEnabled) {
|
|
62
84
|
log.debug(`Opening a Nest session with the configured token ${(0, sanitizers_1.previewSecret)(accessToken)}`);
|
|
63
85
|
}
|
|
64
|
-
const body = await (
|
|
65
|
-
method: 'GET',
|
|
66
|
-
headers: sessionHeaders(accessToken),
|
|
67
|
-
timeoutMs: settings_1.SESSION_TIMEOUT_MS,
|
|
68
|
-
fetchImpl: options.fetchImpl,
|
|
69
|
-
signal: options.signal,
|
|
70
|
-
});
|
|
86
|
+
const body = await requestSession(options);
|
|
71
87
|
const token = typeof body.access_token === 'string' ? body.access_token : undefined;
|
|
72
88
|
// Only a scalar. `String()` accepts anything, so an object would become
|
|
73
89
|
// `'[object Object]'` — truthy, so it passes the missing-field check below and
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -28,7 +28,8 @@ export declare class ConfigurationError extends NestError {
|
|
|
28
28
|
readonly isRetryable = false;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Nest rejected the access token
|
|
31
|
+
* Nest rejected the access token: HTTP 401 anywhere, or HTTP 400 from the
|
|
32
|
+
* session endpoint (see {@link isSessionTokenRejection}).
|
|
32
33
|
*
|
|
33
34
|
* Not retryable: the token is copied by hand from a browser session and only a
|
|
34
35
|
* new one will help. Retrying a rejected token is also how an account attracts
|
|
@@ -37,11 +38,24 @@ export declare class ConfigurationError extends NestError {
|
|
|
37
38
|
export declare class AuthenticationError extends NestError {
|
|
38
39
|
readonly code = "AUTH_ERROR";
|
|
39
40
|
readonly isRetryable = false;
|
|
40
|
-
readonly httpStatus
|
|
41
|
+
readonly httpStatus: number;
|
|
41
42
|
constructor(message?: string, options?: {
|
|
42
43
|
cause?: Error;
|
|
44
|
+
status?: number;
|
|
43
45
|
});
|
|
44
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether a failed session open proves the configured token is unusable.
|
|
49
|
+
*
|
|
50
|
+
* The session endpoint takes nothing but the token, and Nest answers a
|
|
51
|
+
* missing or bad one with 400 (`invalid_request`) or 401. Every other failure
|
|
52
|
+
* retries, because a 403 or an unfamiliar code can be an edge block that
|
|
53
|
+
* clears on its own. Retrying never hides an outage: the alarm feed goes stale
|
|
54
|
+
* and alarm-feed-stale pages whatever the cause.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isSessionTokenRejection(error: unknown): error is NestError & {
|
|
57
|
+
httpStatus: number;
|
|
58
|
+
};
|
|
45
59
|
/**
|
|
46
60
|
* Nest answered HTTP 403 Forbidden.
|
|
47
61
|
*
|
package/dist/errors/index.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.CircuitBreakerError = exports.ObserveStreamError = exports.ApiParseError = exports.ApiResponseError = exports.RateLimitError = exports.TimeoutError = exports.NetworkError = exports.SessionShapeError = exports.ForbiddenError = exports.AuthenticationError = exports.ConfigurationError = exports.NestError = void 0;
|
|
12
|
+
exports.isSessionTokenRejection = isSessionTokenRejection;
|
|
12
13
|
exports.isFatalTransportError = isFatalTransportError;
|
|
13
14
|
exports.isCircuitBreakerFailure = isCircuitBreakerFailure;
|
|
14
15
|
exports.parseRetryAfterMs = parseRetryAfterMs;
|
|
@@ -42,7 +43,8 @@ class ConfigurationError extends NestError {
|
|
|
42
43
|
}
|
|
43
44
|
exports.ConfigurationError = ConfigurationError;
|
|
44
45
|
/**
|
|
45
|
-
* Nest rejected the access token
|
|
46
|
+
* Nest rejected the access token: HTTP 401 anywhere, or HTTP 400 from the
|
|
47
|
+
* session endpoint (see {@link isSessionTokenRejection}).
|
|
46
48
|
*
|
|
47
49
|
* Not retryable: the token is copied by hand from a browser session and only a
|
|
48
50
|
* new one will help. Retrying a rejected token is also how an account attracts
|
|
@@ -51,12 +53,25 @@ exports.ConfigurationError = ConfigurationError;
|
|
|
51
53
|
class AuthenticationError extends NestError {
|
|
52
54
|
code = 'AUTH_ERROR';
|
|
53
55
|
isRetryable = false;
|
|
54
|
-
httpStatus
|
|
56
|
+
httpStatus;
|
|
55
57
|
constructor(message = 'Nest rejected the configured access token. Sign in at https://home.nest.com, open https://home.nest.com/session, and copy a fresh "access_token".', options) {
|
|
56
|
-
super(message, options);
|
|
58
|
+
super(message, options?.cause ? { cause: options.cause } : undefined);
|
|
59
|
+
this.httpStatus = options?.status ?? 401;
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
exports.AuthenticationError = AuthenticationError;
|
|
63
|
+
/**
|
|
64
|
+
* Whether a failed session open proves the configured token is unusable.
|
|
65
|
+
*
|
|
66
|
+
* The session endpoint takes nothing but the token, and Nest answers a
|
|
67
|
+
* missing or bad one with 400 (`invalid_request`) or 401. Every other failure
|
|
68
|
+
* retries, because a 403 or an unfamiliar code can be an edge block that
|
|
69
|
+
* clears on its own. Retrying never hides an outage: the alarm feed goes stale
|
|
70
|
+
* and alarm-feed-stale pages whatever the cause.
|
|
71
|
+
*/
|
|
72
|
+
function isSessionTokenRejection(error) {
|
|
73
|
+
return error instanceof NestError && (error.httpStatus === 400 || error.httpStatus === 401);
|
|
74
|
+
}
|
|
60
75
|
/**
|
|
61
76
|
* Nest answered HTTP 403 Forbidden.
|
|
62
77
|
*
|
package/dist/nest-signals.d.ts
CHANGED
|
@@ -47,10 +47,12 @@ export declare class NestSignals {
|
|
|
47
47
|
#private;
|
|
48
48
|
constructor(writer: SignalWriter, graceMs?: number);
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* Write auth-failed and alarm-feed-stale clear, and start the alarm feed grace.
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
52
|
+
* Call once, before the first request. The write lists this plugin in
|
|
53
|
+
* homebridge-pagerduty even if Nest never answers, and the grace timer pages
|
|
54
|
+
* for a feed that is already down at launch. Smoke and CO wait for a fresh
|
|
55
|
+
* feed, so a restart cannot clear a real alarm.
|
|
54
56
|
*/
|
|
55
57
|
start(): void;
|
|
56
58
|
/** Nest accepted the token. Clears auth-failed. */
|
package/dist/nest-signals.js
CHANGED
|
@@ -58,16 +58,20 @@ class NestSignals {
|
|
|
58
58
|
this.#graceMs = graceMs;
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Write auth-failed and alarm-feed-stale clear, and start the alarm feed grace.
|
|
62
62
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
63
|
+
* Call once, before the first request. The write lists this plugin in
|
|
64
|
+
* homebridge-pagerduty even if Nest never answers, and the grace timer pages
|
|
65
|
+
* for a feed that is already down at launch. Smoke and CO wait for a fresh
|
|
66
|
+
* feed, so a restart cannot clear a real alarm.
|
|
65
67
|
*/
|
|
66
68
|
start() {
|
|
67
|
-
if (this.#isClosed || this.#hasStarted) {
|
|
69
|
+
if (this.#isClosed || this.#hasStarted || this.#hasStopped) {
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
70
72
|
this.#hasStarted = true;
|
|
73
|
+
this.#writeAuthFailedClear();
|
|
74
|
+
this.#writeFeedStale(false);
|
|
71
75
|
this.#armStaleTimer();
|
|
72
76
|
}
|
|
73
77
|
/** Nest accepted the token. Clears auth-failed. */
|
|
@@ -75,11 +79,7 @@ class NestSignals {
|
|
|
75
79
|
if (this.#isClosed || this.#hasStopped) {
|
|
76
80
|
return;
|
|
77
81
|
}
|
|
78
|
-
this.#
|
|
79
|
-
active: false,
|
|
80
|
-
severity: 'error',
|
|
81
|
-
summary: exports.TOKEN_REJECTED_SUMMARY,
|
|
82
|
-
});
|
|
82
|
+
this.#writeAuthFailedClear();
|
|
83
83
|
}
|
|
84
84
|
/** Record the Protects HomeKit is showing and whether their alarm feed is fresh. */
|
|
85
85
|
update(protects, isFeedAvailable) {
|
|
@@ -132,6 +132,13 @@ class NestSignals {
|
|
|
132
132
|
this.#clearStaleTimer();
|
|
133
133
|
this.#writer.close();
|
|
134
134
|
}
|
|
135
|
+
#writeAuthFailedClear() {
|
|
136
|
+
this.#writer.write(exports.SIGNAL_AUTH_FAILED, {
|
|
137
|
+
active: false,
|
|
138
|
+
severity: 'error',
|
|
139
|
+
summary: exports.TOKEN_REJECTED_SUMMARY,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
135
142
|
#writeFeedStale(active) {
|
|
136
143
|
this.#writer.write(exports.SIGNAL_ALARM_FEED_STALE, {
|
|
137
144
|
active,
|
package/dist/platform.js
CHANGED
|
@@ -66,7 +66,32 @@ const UPDATE_COALESCE_MS = 250;
|
|
|
66
66
|
* only remaining signal that HomeKit's values are frozen.
|
|
67
67
|
*/
|
|
68
68
|
const FATAL_REMINDER_MS = 60 * 60_000;
|
|
69
|
-
|
|
69
|
+
function tokenRejectedMessage(status) {
|
|
70
|
+
return `Nest rejected the token (HTTP ${status}). Get a new token from https://home.nest.com/session and restart Homebridge.`;
|
|
71
|
+
}
|
|
72
|
+
/** A few words for the startup retry line; the full error goes to debug. */
|
|
73
|
+
function describeStartupFailure(error) {
|
|
74
|
+
if (error instanceof errors_1.CircuitBreakerError) {
|
|
75
|
+
return 'paused after repeated failures';
|
|
76
|
+
}
|
|
77
|
+
if (error instanceof errors_1.TimeoutError) {
|
|
78
|
+
return 'timed out';
|
|
79
|
+
}
|
|
80
|
+
if (error instanceof errors_1.NetworkError) {
|
|
81
|
+
return 'network error';
|
|
82
|
+
}
|
|
83
|
+
if (error instanceof errors_1.ApiParseError) {
|
|
84
|
+
return 'unexpected response';
|
|
85
|
+
}
|
|
86
|
+
if (error instanceof errors_1.NestError && error.httpStatus !== undefined) {
|
|
87
|
+
return `HTTP ${error.httpStatus}`;
|
|
88
|
+
}
|
|
89
|
+
return (0, sanitizers_1.sanitizeError)(error);
|
|
90
|
+
}
|
|
91
|
+
function formatDelay(ms) {
|
|
92
|
+
const seconds = Math.round(ms / 1000);
|
|
93
|
+
return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
|
|
94
|
+
}
|
|
70
95
|
function stopReasonFor(error) {
|
|
71
96
|
if (error instanceof errors_1.SessionShapeError) {
|
|
72
97
|
return 'session';
|
|
@@ -203,6 +228,9 @@ class MyNestPlatform {
|
|
|
203
228
|
for (const warning of warnings) {
|
|
204
229
|
this.#log.warn(warning);
|
|
205
230
|
}
|
|
231
|
+
if (resolved.allowThermostatControl) {
|
|
232
|
+
this.#log.info('Thermostat control enabled');
|
|
233
|
+
}
|
|
206
234
|
}
|
|
207
235
|
catch (error) {
|
|
208
236
|
// Homebridge keeps running other platforms, so this must not throw. The
|
|
@@ -252,9 +280,6 @@ class MyNestPlatform {
|
|
|
252
280
|
this.#holdUnpublishedProtectAlarms();
|
|
253
281
|
this.#signals?.start();
|
|
254
282
|
this.#startedAt = Date.now();
|
|
255
|
-
if (this.#config.allowThermostatControl) {
|
|
256
|
-
this.#log.info('Thermostat control enabled');
|
|
257
|
-
}
|
|
258
283
|
const diagnostics = this.#diagnostics;
|
|
259
284
|
const transport = new transport_1.NestTransport({
|
|
260
285
|
accessToken: this.#config.accessToken,
|
|
@@ -302,8 +327,8 @@ class MyNestPlatform {
|
|
|
302
327
|
if (this.#isShuttingDown) {
|
|
303
328
|
return;
|
|
304
329
|
}
|
|
305
|
-
this.#log.
|
|
306
|
-
this.#scheduleStartRetry();
|
|
330
|
+
this.#log.debug(`Startup failed: ${(0, sanitizers_1.sanitizeError)(error)}`);
|
|
331
|
+
this.#scheduleStartRetry(describeStartupFailure(error));
|
|
307
332
|
return;
|
|
308
333
|
}
|
|
309
334
|
this.#startRetryAttempt = 0;
|
|
@@ -320,7 +345,7 @@ class MyNestPlatform {
|
|
|
320
345
|
* the plugin permanently idle — and cache-restored Protect tiles keep whatever
|
|
321
346
|
* smoke/CO value Homebridge persisted, with no handler to mark them faulted.
|
|
322
347
|
*/
|
|
323
|
-
#scheduleStartRetry() {
|
|
348
|
+
#scheduleStartRetry(reason) {
|
|
324
349
|
if (this.#isShuttingDown || this.#hasFatal || this.#startRetryTimer) {
|
|
325
350
|
return;
|
|
326
351
|
}
|
|
@@ -329,6 +354,7 @@ class MyNestPlatform {
|
|
|
329
354
|
this.#holdUnpublishedProtectAlarms();
|
|
330
355
|
this.#startRetryAttempt++;
|
|
331
356
|
const delayMs = (0, retry_1.computeBackoffMs)(this.#startRetryAttempt);
|
|
357
|
+
this.#log.warn(`Could not connect to Nest (${reason}), retrying in ${formatDelay(delayMs)}`);
|
|
332
358
|
this.#startRetryTimer = setTimeout(() => {
|
|
333
359
|
this.#startRetryTimer = null;
|
|
334
360
|
if (this.#isShuttingDown || this.#hasFatal) {
|
|
@@ -393,7 +419,11 @@ class MyNestPlatform {
|
|
|
393
419
|
}
|
|
394
420
|
this.#hasFatal = true;
|
|
395
421
|
const isSessionShape = error instanceof errors_1.SessionShapeError;
|
|
396
|
-
|
|
422
|
+
const message = error instanceof errors_1.AuthenticationError
|
|
423
|
+
? tokenRejectedMessage(error.httpStatus)
|
|
424
|
+
: error.message;
|
|
425
|
+
this.#log.error(message);
|
|
426
|
+
this.#log.debug(`Stopped by: ${(0, sanitizers_1.sanitizeError)(error)}`);
|
|
397
427
|
this.#signals?.noteStopped(stopReasonFor(error));
|
|
398
428
|
// Mark Protect smoke/CO inactive/faulted while handlers can still refresh.
|
|
399
429
|
// `#stop` sets `#isShuttingDown` and drops `#scheduleUpdate`, so the
|
|
@@ -417,7 +447,7 @@ class MyNestPlatform {
|
|
|
417
447
|
// the single error line above scrolls out of the log within hours.
|
|
418
448
|
const reminder = isSessionShape
|
|
419
449
|
? 'Still stopped: unsupported Nest session format. Readings are frozen.'
|
|
420
|
-
: `Still stopped: ${
|
|
450
|
+
: `Still stopped: ${message}`;
|
|
421
451
|
this.#fatalReminderTimer = setInterval(() => {
|
|
422
452
|
this.#log.error(reminder);
|
|
423
453
|
}, FATAL_REMINDER_MS);
|
package/docs/PROTOCOL.md
CHANGED
|
@@ -29,7 +29,7 @@ HomeKit accessories are unregistered only when Nest has confirmed a device is go
|
|
|
29
29
|
| Observe never connected this session | Do not unregister (REST-only view cannot prove thermostats are gone) |
|
|
30
30
|
| Cached accessory not named by Nest since the restart | Two settled Observe snapshots that both omit it before prune |
|
|
31
31
|
| Device absent from the Observe ∪ REST inventory after the above | Unregister from HomeKit |
|
|
32
|
-
| Expired or
|
|
32
|
+
| Expired, revoked or missing access token (HTTP 401 anywhere, HTTP 400 from `/session`, or a session this plugin cannot use) | Stop updates; **keep** accessories. Any other failure, including HTTP 403 from either host, retries and does not stop the plugin, including when both transports exhaust their budget. Each transport probes again after the cooldown. Nest Account auth is a manually pasted session token, so unregistering would bounce rooms and automations until the user pastes a fresh token and restarts |
|
|
33
33
|
|
|
34
34
|
Unusable plugin config at startup (missing/invalid token shape, etc.) **keeps** the cached accessories, for the same reason an expired token does. A typo, a half-edited `config.json`, or a truncated token would otherwise destroy every room assignment, scene membership, and automation target in the Home app — and fixing the config does not bring them back, because HomeKit treats the re-registered accessories as new devices. A config error cannot warrant a more destructive response than a revoked token. The plugin logs why and publishes nothing until it is restarted with a usable config.
|
|
35
35
|
|
package/docs/README-DETAILED.md
CHANGED
|
@@ -131,10 +131,10 @@ Each condition below then opens a PagerDuty alert while it is present, and resol
|
|
|
131
131
|
| --- | --- | --- |
|
|
132
132
|
| `smoke` | critical | Any Protect reports a smoke warning or emergency |
|
|
133
133
|
| `co` | critical | Any Protect reports a carbon monoxide warning or emergency |
|
|
134
|
-
| `alarm-feed-stale` | error | The Protect alarm feed has been unavailable for 5 minutes, so HomeKit may not show a smoke or CO alarm |
|
|
135
|
-
| `auth-failed` | error | The plugin stopped because Nest rejected the token, the token in the config is missing or invalid, or Nest changed its session format |
|
|
134
|
+
| `alarm-feed-stale` | error | The Protect alarm feed has been unavailable for 5 minutes, whatever the cause, so HomeKit may not show a smoke or CO alarm |
|
|
135
|
+
| `auth-failed` | error | The plugin stopped because Nest rejected the token (HTTP 400 or 401), the token in the config is missing or invalid, or Nest changed its session format |
|
|
136
136
|
|
|
137
|
-
Each signal covers the whole house. The summary does not name the Protect, so check the Nest app for which one is alarming. While the alarm feed is stale, `smoke` and `co` keep their last value. They do not clear until fresh readings arrive. While `auth-failed` is active, `alarm-feed-stale` stays off, so a stopped plugin sends one page.
|
|
137
|
+
Each signal covers the whole house. The summary does not name the Protect, so check the Nest app for which one is alarming. While the alarm feed is stale, `smoke` and `co` keep their last value. They do not clear until fresh readings arrive. While `auth-failed` is active, `alarm-feed-stale` stays off, so a stopped plugin sends one page. Any other failure, such as HTTP 403, an unfamiliar error code or a network outage, keeps retrying and pages `alarm-feed-stale` if it lasts 5 minutes. The plugin writes `auth-failed` and `alarm-feed-stale` at startup, so homebridge-pagerduty lists it even if Nest never answers.
|
|
138
138
|
|
|
139
139
|
## Troubleshooting
|
|
140
140
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-mynest",
|
|
3
3
|
"displayName": "Homebridge My Nest",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.2",
|
|
5
5
|
"description": "Homebridge plugin for Nest thermostats (with Eco Mode), Nest Protect, and Nest Temperature Sensors using a Nest Account access token",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|