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 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.0
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, the sensors
78
- show "No Response" in the Home app, and (if ntfy Topic is set) a
79
- notification is sent. Default 4. This is separate from poll failures.
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
- this.sessionKey = this.loadSessionKey();
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
- loadSessionKey() {
134
+ loadPersistedState() {
126
135
  try {
127
136
  const raw = fs.readFileSync(this.sessionStatePath, 'utf8');
128
- return JSON.parse(raw).sessionKey ?? null;
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 null;
142
+ return {};
134
143
  }
135
144
  }
136
145
 
137
- saveSessionKey(sessionKey) {
146
+ persistState() {
138
147
  try {
139
- fs.writeFileSync(this.sessionStatePath, JSON.stringify({ sessionKey }));
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.saveSessionKey(result.sessionKey);
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 && !this.staleDataWarned) {
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
- await this.sendNtfyNotification(
362
- `Temperature and/or CO2 readings haven't been updated since ${date} at ${time}.`,
363
- 'Homebridge: Withings Environment Data is out of date'
364
- );
365
- } else if (!stale && this.staleDataWarned) {
366
- this.staleDataWarned = false;
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.0",
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": [