homebridge-withings-environment-data 0.3.3 → 1.1.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 ADDED
@@ -0,0 +1,106 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## Unreleased
8
+
9
+ ## [1.1.0] - 2026-07-27
10
+
11
+ ### Added
12
+ - Air Quality threshold config fields (`airQualityExcellentMaxPpm`,
13
+ `airQualityGoodMaxPpm`, `airQualityFairMaxPpm`,
14
+ `airQualityInferiorMaxPpm`) to make the AirQualitySensor's ppm
15
+ boundaries configurable instead of hardcoded
16
+
17
+ ### Changed
18
+ - ntfy notifications for an authentication failure now use a fixed,
19
+ generic message instead of the raw internal error text; other poll
20
+ failures still send the raw error message. The Homebridge log still
21
+ always shows the detailed error either way.
22
+
23
+ ## [1.0.0] - 2026-07-26
24
+
25
+ ### Changed
26
+ - README's license/changelog links now point to full GitHub URLs instead
27
+ of relative paths
28
+ - package.json now sets `author`, `homepage`, `bugs`, and `repository`, and
29
+ includes README/LICENSE/CHANGELOG in `files`
30
+
31
+ ## [0.3.3] - 2026-07-26
32
+
33
+ ### Added
34
+ - CHANGELOG.md (this file)
35
+
36
+ ### Changed
37
+ - README restructured to match project conventions (versioned title,
38
+ Requirements/Usage sections, license/changelog links, Configuration/How
39
+ authentication works/When it stops working/Getting the trust cookie
40
+ nested under Usage)
41
+
42
+ ### Fixed
43
+ - LICENSE copyright name typo
44
+
45
+ ## [0.3.2] - 2026-07-26
46
+
47
+ ### Fixed
48
+ - Removed an inaccurate claim from the ntfy notification body about the
49
+ Home app continuing to show the last known reading
50
+
51
+ ## [0.3.1] - 2026-07-25
52
+
53
+ ### Added
54
+ - Optional `ntfyTopic` config field: sends a push notification via
55
+ ntfy.sh the first time a poll fails, not repeated on every failure in
56
+ the same streak
57
+
58
+ ### Changed
59
+ - Broadened package.json keywords for discoverability
60
+ - Simplified the README's authentication section wording
61
+
62
+ ## [0.2.0] - 2026-07-25
63
+
64
+ ### Added
65
+ - CO2/AirQuality/Temperature characteristics now use `onGet` handlers
66
+ backed by a cached last-known reading; the Home app shows "No Response"
67
+ once consecutive failures exceed a configurable threshold
68
+ (`noResponseAfterMissedPolls`, default 2, minimum 0), instead of relying
69
+ on `StatusFault`, which isn't reliably surfaced by the stock Home app
70
+
71
+ ### Changed
72
+ - Replaced per-poll email/password login with a long-lived `session_key`
73
+ reused across polls and persisted across Homebridge restarts, falling
74
+ back to a full login only when that session actually expires (fixes an
75
+ account lockout caused by logging in fresh every 30 minutes)
76
+
77
+ ## [0.1.5] - 2026-07-23
78
+
79
+ ### Changed
80
+ - Replaced `.npmignore` with an explicit `"files"` whitelist in
81
+ package.json
82
+
83
+ ## [0.1.4] - 2026-07-23
84
+
85
+ ### Added
86
+ - `.npmignore` to keep editor/session folders and repo-only files out of
87
+ the published npm package
88
+
89
+ ## [0.1.3] - 2026-07-23
90
+
91
+ ### Added
92
+ - Homebridge platform plugin exposing CO2 (CarbonDioxideSensor +
93
+ AirQualitySensor) and room temperature (TemperatureSensor) as HomeKit
94
+ sensors, replacing the standalone scraper script
95
+ - Config UI based configuration instead of a `.env` file
96
+
97
+ ### Changed
98
+ - Simplified required config: dropped `w_uuid`, `deviceId`, `userId`, and
99
+ `appliver`; device/user IDs are now auto-discovered after login
100
+ - README cleaned up: deduplicated trust-cookie capture instructions,
101
+ added a plain-text credential storage warning, updated install
102
+ instructions for npm
103
+
104
+ ### Fixed
105
+ - Corrected the real "trusted device" marker to the `2fa_token_<hash>`
106
+ cookie instead of `w_uuid`
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Homebridge Withings Environment Data v0.3.3
1
+ # Homebridge Withings Environment Data v1.1.0
2
2
 
3
3
  **This Homebridge plugin has been 100% vibe coded with Claude.**
4
4
 
@@ -41,7 +41,8 @@ accessory:
41
41
  - **CarbonDioxideSensor**: precise CO2 level in ppm, plus a normal/abnormal
42
42
  detected alert based on a configurable threshold.
43
43
  - **AirQualitySensor**: the same CO2 reading mapped to HomeKit's
44
- Excellent/Good/Fair/Inferior/Poor category.
44
+ Excellent/Good/Fair/Inferior/Poor category, based on configurable ppm
45
+ thresholds.
45
46
  - **TemperatureSensor**: room temperature in °C.
46
47
 
47
48
  These appear automatically in the Home app after Homebridge restarts, no
@@ -68,6 +69,9 @@ Fields:
68
69
  matching the scale's own upload cadence).
69
70
  - **CO2 Detected Threshold (ppm)**: ppm above which the CarbonDioxideSensor
70
71
  reports "abnormal" (default 1000).
72
+ - **Air Quality: Excellent/Good/Fair/Inferior below (ppm)**: the four ppm
73
+ boundaries the AirQualitySensor category is based on (defaults 800, 1000,
74
+ 1500, 2000).
71
75
  - **ntfy Topic (optional)**: if set, sends a push notification via
72
76
  [ntfy.sh](https://ntfy.sh) to this topic the first time a poll fails
73
77
  (not repeated on every subsequent failure in the same streak; only once
@@ -126,5 +130,5 @@ it once via DevTools:
126
130
 
127
131
  ## License and changelog
128
132
 
129
- - [LICENSE](LICENSE)
130
- - [CHANGELOG](CHANGELOG.md)
133
+ - [LICENSE](https://github.com/LuudJacobs/homebridge-withings-environment-data/blob/main/LICENSE)
134
+ - [CHANGELOG](https://github.com/LuudJacobs/homebridge-withings-environment-data/blob/main/CHANGELOG.md)
@@ -49,6 +49,34 @@
49
49
  "default": 1000,
50
50
  "minimum": 400
51
51
  },
52
+ "airQualityExcellentMaxPpm": {
53
+ "title": "Air Quality: Excellent below (ppm)",
54
+ "type": "integer",
55
+ "default": 800,
56
+ "minimum": 1,
57
+ "description": "CO2 readings below this are reported as Excellent air quality."
58
+ },
59
+ "airQualityGoodMaxPpm": {
60
+ "title": "Air Quality: Good below (ppm)",
61
+ "type": "integer",
62
+ "default": 1000,
63
+ "minimum": 1,
64
+ "description": "CO2 readings below this are reported as Good."
65
+ },
66
+ "airQualityFairMaxPpm": {
67
+ "title": "Air Quality: Fair below (ppm)",
68
+ "type": "integer",
69
+ "default": 1500,
70
+ "minimum": 1,
71
+ "description": "CO2 readings below this are reported as Fair."
72
+ },
73
+ "airQualityInferiorMaxPpm": {
74
+ "title": "Air Quality: Inferior below (ppm)",
75
+ "type": "integer",
76
+ "default": 2000,
77
+ "minimum": 1,
78
+ "description": "CO2 readings below this are reported as Inferior. Anything above is reported as Poor."
79
+ },
52
80
  "noResponseAfterMissedPolls": {
53
81
  "title": "No Response After Missed Polls",
54
82
  "type": "integer",
package/index.js CHANGED
@@ -63,11 +63,11 @@ async function fetchLatest({ cookieHeader, sessionToken, deviceId, userId }) {
63
63
  };
64
64
  }
65
65
 
66
- function mapCo2ToAirQuality(ppm, AirQuality) {
67
- if (ppm < 800) return AirQuality.EXCELLENT;
68
- if (ppm < 1000) return AirQuality.GOOD;
69
- if (ppm < 1500) return AirQuality.FAIR;
70
- if (ppm < 2000) return AirQuality.INFERIOR;
66
+ function mapCo2ToAirQuality(ppm, AirQuality, thresholds) {
67
+ if (ppm < thresholds.excellentMaxPpm) return AirQuality.EXCELLENT;
68
+ if (ppm < thresholds.goodMaxPpm) return AirQuality.GOOD;
69
+ if (ppm < thresholds.fairMaxPpm) return AirQuality.FAIR;
70
+ if (ppm < thresholds.inferiorMaxPpm) return AirQuality.INFERIOR;
71
71
  return AirQuality.POOR;
72
72
  }
73
73
 
@@ -216,7 +216,13 @@ class WithingsEnvironmentDataPlatform {
216
216
 
217
217
  async poll() {
218
218
  try {
219
- const { cookieHeader, sessionToken } = await this.authenticate();
219
+ let cookieHeader, sessionToken;
220
+ try {
221
+ ({ cookieHeader, sessionToken } = await this.authenticate());
222
+ } catch (err) {
223
+ err.isAuthFailure = true;
224
+ throw err;
225
+ }
220
226
 
221
227
  if (!this.deviceId || !this.userId) {
222
228
  const discovered = await discoverDevice(cookieHeader);
@@ -252,7 +258,10 @@ class WithingsEnvironmentDataPlatform {
252
258
 
253
259
  if (!this.hasNotifiedFailure) {
254
260
  this.hasNotifiedFailure = true;
255
- await this.sendNtfyNotification(err.message);
261
+ const notificationMessage = err.isAuthFailure
262
+ ? 'Authentication for Withings account failed. Check the Homebridge logs for details.'
263
+ : err.message;
264
+ await this.sendNtfyNotification(notificationMessage);
256
265
  }
257
266
  }
258
267
  }
@@ -282,6 +291,19 @@ class WithingsEnvironmentDataPlatform {
282
291
  return Number.isFinite(threshold) && threshold >= 0 ? threshold : 2;
283
292
  }
284
293
 
294
+ getAirQualityThresholds() {
295
+ const excellentMaxPpm = Number(this.config.airQualityExcellentMaxPpm);
296
+ const goodMaxPpm = Number(this.config.airQualityGoodMaxPpm);
297
+ const fairMaxPpm = Number(this.config.airQualityFairMaxPpm);
298
+ const inferiorMaxPpm = Number(this.config.airQualityInferiorMaxPpm);
299
+ return {
300
+ excellentMaxPpm: Number.isFinite(excellentMaxPpm) && excellentMaxPpm > 0 ? excellentMaxPpm : 800,
301
+ goodMaxPpm: Number.isFinite(goodMaxPpm) && goodMaxPpm > 0 ? goodMaxPpm : 1000,
302
+ fairMaxPpm: Number.isFinite(fairMaxPpm) && fairMaxPpm > 0 ? fairMaxPpm : 1500,
303
+ inferiorMaxPpm: Number.isFinite(inferiorMaxPpm) && inferiorMaxPpm > 0 ? inferiorMaxPpm : 2000,
304
+ };
305
+ }
306
+
285
307
  isStale() {
286
308
  return !this.hasEverSucceeded || this.missedCycles > this.getNoResponseThreshold();
287
309
  }
@@ -306,7 +328,7 @@ class WithingsEnvironmentDataPlatform {
306
328
 
307
329
  getAirQualityOrThrow() {
308
330
  this.throwIfStale();
309
- return mapCo2ToAirQuality(this.lastReading.co2, this.Characteristic.AirQuality);
331
+ return mapCo2ToAirQuality(this.lastReading.co2, this.Characteristic.AirQuality, this.getAirQualityThresholds());
310
332
  }
311
333
 
312
334
  getTemperatureOrThrow() {
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "homebridge-withings-environment-data",
3
- "version": "0.3.3",
3
+ "version": "1.1.0",
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": [
7
7
  "index.js",
8
8
  "lib",
9
- "config.schema.json"
9
+ "config.schema.json",
10
+ "README.md",
11
+ "LICENSE",
12
+ "CHANGELOG.md"
10
13
  ],
11
14
  "keywords": [
12
15
  "homebridge-plugin",
@@ -18,9 +21,18 @@
18
21
  "air-quality",
19
22
  "homekit"
20
23
  ],
24
+ "author": "Luud Jacobs",
25
+ "license": "MIT",
26
+ "homepage": "https://github.com/LuudJacobs/homebridge-withings-environment-data#readme",
27
+ "bugs": {
28
+ "url": "https://github.com/LuudJacobs/homebridge-withings-environment-data/issues"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/LuudJacobs/homebridge-withings-environment-data.git"
33
+ },
21
34
  "engines": {
22
35
  "homebridge": ">=1.6.0",
23
36
  "node": ">=18"
24
- },
25
- "license": "MIT"
37
+ }
26
38
  }