homebridge-withings-environment-data 1.2.0 → 1.2.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.
- package/CHANGELOG.md +16 -0
- package/README.md +5 -4
- package/index.js +45 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## [1.2.1] - 2026-08-02
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- The stale-data log warning now logs on every poll while data remains
|
|
13
|
+
stale (matching how missed poll cycles are already logged), instead of
|
|
14
|
+
only once per stale streak.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- The stale-data ntfy notification no longer re-fires every time
|
|
18
|
+
Homebridge restarts while the same stale reading is still the newest one
|
|
19
|
+
on record. The "already notified about this" state, and the last-known
|
|
20
|
+
reading date it applies to, are now both persisted to disk instead of
|
|
21
|
+
living only in memory (a first fix persisted only the former, which
|
|
22
|
+
still misfired if the first poll after a restart happened to fail), and
|
|
23
|
+
still reset correctly once a fresher reading comes in.
|
|
24
|
+
|
|
9
25
|
## [1.2.0] - 2026-07-31
|
|
10
26
|
|
|
11
27
|
### Added
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Homebridge Withings Environment Data v1.2.
|
|
1
|
+
# Homebridge Withings Environment Data v1.2.1
|
|
2
2
|
|
|
3
3
|
**This Homebridge plugin has been 100% vibe coded with Claude.**
|
|
4
4
|
|
|
@@ -74,9 +74,10 @@ Fields:
|
|
|
74
74
|
1500, 2000).
|
|
75
75
|
- **Stale Data Warning Threshold (hours)**: if the newest reading from the
|
|
76
76
|
scale itself (not the plugin's poll) is older than this many hours — e.g.
|
|
77
|
-
nobody's stood on the scale in a while — a warning is logged
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
nobody's stood on the scale in a while — a warning is logged on every
|
|
78
|
+
poll for as long as it stays stale, the sensors show "No Response" in the
|
|
79
|
+
Home app, and (if ntfy Topic is set) a single notification is sent for
|
|
80
|
+
that stale reading. Default 4. This is separate from poll failures.
|
|
80
81
|
- **ntfy Topic (optional)**: if set, sends a push notification via
|
|
81
82
|
[ntfy.sh](https://ntfy.sh) to this topic the first time a poll fails
|
|
82
83
|
(not repeated on every subsequent failure in the same streak; only once
|
package/index.js
CHANGED
|
@@ -103,12 +103,6 @@ class WithingsEnvironmentDataPlatform {
|
|
|
103
103
|
// missed poll — reset once a poll succeeds again.
|
|
104
104
|
this.hasNotifiedFailure = false;
|
|
105
105
|
|
|
106
|
-
// Unix seconds of the newest reading actually seen so far (from the
|
|
107
|
-
// Withings measurement itself, not from a successful poll) — used to
|
|
108
|
-
// detect the scale going quiet rather than the plugin failing to poll.
|
|
109
|
-
// Warn/notify once per stale streak, reset once a fresher reading comes in.
|
|
110
|
-
this.lastReadingDate = null;
|
|
111
|
-
this.staleDataWarned = false;
|
|
112
106
|
this.warnedMissingReadingDate = false;
|
|
113
107
|
|
|
114
108
|
// The long-lived (~1 week) session_key that lets us skip email/password/2FA
|
|
@@ -117,26 +111,48 @@ class WithingsEnvironmentDataPlatform {
|
|
|
117
111
|
// it, since repeatedly hitting the password endpoint appears to be heavily
|
|
118
112
|
// throttled by Withings.
|
|
119
113
|
this.sessionStatePath = path.join(this.api.user.storagePath(), SESSION_STATE_FILENAME);
|
|
120
|
-
|
|
114
|
+
const state = this.loadPersistedState();
|
|
115
|
+
this.sessionKey = state.sessionKey ?? null;
|
|
116
|
+
// Unix seconds of the newest reading actually seen so far (from the Withings
|
|
117
|
+
// measurement itself, not from a successful poll) — used to detect the scale
|
|
118
|
+
// going quiet rather than the plugin failing to poll. Persisted and restored
|
|
119
|
+
// immediately on boot (rather than waiting on the first poll to repopulate
|
|
120
|
+
// it) so a restart can't be mistaken for "no reading yet" and wrongly clear
|
|
121
|
+
// an active stale-warning below.
|
|
122
|
+
this.lastReadingDate = state.lastReadingDate ?? null;
|
|
123
|
+
// The readingDate (unix seconds) we last sent a stale-data *notification* for,
|
|
124
|
+
// or null if not currently in a notified state. The log warning itself repeats
|
|
125
|
+
// every poll while data stays stale (like missed-poll-cycle failures do), but
|
|
126
|
+
// this gates the ntfy notification to once per distinct stale reading, and is
|
|
127
|
+
// persisted so a Homebridge restart doesn't re-notify about a reading we've
|
|
128
|
+
// already notified about — it only resets once a fresher reading comes in.
|
|
129
|
+
this.staleNotifiedForReadingDate = state.staleNotifiedForReadingDate ?? null;
|
|
121
130
|
|
|
122
131
|
this.api.on('didFinishLaunching', () => this.discoverDevices());
|
|
123
132
|
}
|
|
124
133
|
|
|
125
|
-
|
|
134
|
+
loadPersistedState() {
|
|
126
135
|
try {
|
|
127
136
|
const raw = fs.readFileSync(this.sessionStatePath, 'utf8');
|
|
128
|
-
return JSON.parse(raw)
|
|
137
|
+
return JSON.parse(raw);
|
|
129
138
|
} catch (err) {
|
|
130
139
|
if (err.code !== 'ENOENT') {
|
|
131
140
|
this.log.warn(`Could not read session state file: ${err.message}`);
|
|
132
141
|
}
|
|
133
|
-
return
|
|
142
|
+
return {};
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
|
|
137
|
-
|
|
146
|
+
persistState() {
|
|
138
147
|
try {
|
|
139
|
-
fs.writeFileSync(
|
|
148
|
+
fs.writeFileSync(
|
|
149
|
+
this.sessionStatePath,
|
|
150
|
+
JSON.stringify({
|
|
151
|
+
sessionKey: this.sessionKey,
|
|
152
|
+
lastReadingDate: this.lastReadingDate,
|
|
153
|
+
staleNotifiedForReadingDate: this.staleNotifiedForReadingDate,
|
|
154
|
+
})
|
|
155
|
+
);
|
|
140
156
|
} catch (err) {
|
|
141
157
|
this.log.warn(`Could not persist session state file: ${err.message}`);
|
|
142
158
|
}
|
|
@@ -161,7 +177,7 @@ class WithingsEnvironmentDataPlatform {
|
|
|
161
177
|
if (result.sessionKey) {
|
|
162
178
|
if (result.sessionKey !== this.sessionKey) {
|
|
163
179
|
this.sessionKey = result.sessionKey;
|
|
164
|
-
this.
|
|
180
|
+
this.persistState();
|
|
165
181
|
this.log.info('Withings full login succeeded; cached the new session for future polls.');
|
|
166
182
|
} else {
|
|
167
183
|
this.log.info('Withings full login succeeded; session token unchanged.');
|
|
@@ -354,16 +370,22 @@ class WithingsEnvironmentDataPlatform {
|
|
|
354
370
|
async checkStaleData() {
|
|
355
371
|
const stale = this.isDataStale();
|
|
356
372
|
|
|
357
|
-
if (stale
|
|
358
|
-
this.staleDataWarned = true;
|
|
373
|
+
if (stale) {
|
|
359
374
|
const { date, time } = this.formatStaleDateTime(this.lastReadingDate);
|
|
360
375
|
this.log.warn(`Withings data is stale: last recorded at ${date} ${time}`);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
376
|
+
|
|
377
|
+
const alreadyNotifiedForThisReading = this.staleNotifiedForReadingDate === this.lastReadingDate;
|
|
378
|
+
if (!alreadyNotifiedForThisReading) {
|
|
379
|
+
this.staleNotifiedForReadingDate = this.lastReadingDate;
|
|
380
|
+
this.persistState();
|
|
381
|
+
await this.sendNtfyNotification(
|
|
382
|
+
`Temperature and/or CO2 readings haven't been updated since ${date} at ${time}.`,
|
|
383
|
+
'Homebridge: Withings Environment Data is out of date'
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
} else if (this.staleNotifiedForReadingDate !== null) {
|
|
387
|
+
this.staleNotifiedForReadingDate = null;
|
|
388
|
+
this.persistState();
|
|
367
389
|
this.log.info('Withings: fresh data has been recorded.');
|
|
368
390
|
}
|
|
369
391
|
}
|
|
@@ -411,8 +433,9 @@ class WithingsEnvironmentDataPlatform {
|
|
|
411
433
|
'Withings measurement data has no recognizable date field; stale data detection is disabled until this is fixed.'
|
|
412
434
|
);
|
|
413
435
|
}
|
|
414
|
-
} else if (readingDate !== null && readingDate !== undefined) {
|
|
436
|
+
} else if (readingDate !== null && readingDate !== undefined && readingDate !== this.lastReadingDate) {
|
|
415
437
|
this.lastReadingDate = readingDate;
|
|
438
|
+
this.persistState();
|
|
416
439
|
}
|
|
417
440
|
|
|
418
441
|
if (co2 !== null && co2 !== undefined) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-withings-environment-data",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Homebridge plugin exposing ambient CO2/air-quality and room temperature readings from a Withings WS-50 scale as HomeKit sensors",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|