homebridge-philips-airplus-cloud 0.3.0 → 0.3.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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  The Home Assistant integration in `custom_components/` is unchanged from upstream and keeps its own history.
4
4
 
5
+ ## 0.3.2 — 2026-10-11
6
+
7
+ - Option `homekitIdentity`: gives the purifier a new HomeKit identity when the Home app keeps a stuck record of it (rooms and names cannot be changed). The previous accessory is removed automatically.
8
+
9
+ ## 0.3.1 — 2026-10-11
10
+
11
+ - Fix: on a fresh install the purifier started with the reduced profile ("Model AC0651 is not in models.yaml") because the cloud device list gives the model without its `/10` suffix. The short code now matches its `models.yaml` entry, and an unknown code waits for the exact one reported by the device.
12
+
5
13
  ## 0.3.0 — 2026-10-11
6
14
 
7
15
  - Published on npm as `homebridge-philips-airplus-cloud` (the name `homebridge-philips-airplus` belongs to an unrelated humidifier plugin). Installs from the earlier `.tgz` or git builds were named `homebridge-philips-airplus`: uninstall that first.
package/README.md CHANGED
@@ -83,6 +83,7 @@ The refresh token rotates on every use; the plugin keeps the current one in `<ho
83
83
  | `displayLight` | `true` | Show the display light |
84
84
  | `displayLightProperty` | — | Property key of the display light, if it is not detected (see the `Status properties reported by the device` log line) |
85
85
  | `logDeviceData` | `false` | Diagnostics: at startup, log every port and the cloud shadow the purifier publishes |
86
+ | `homekitIdentity` | — | Change to any new value to give the purifier a new HomeKit identity (use only if the Home app refuses to move or rename it; reassign the room afterwards) |
86
87
  | `filterChangeThreshold` | `10` | Filter life (%) at or below which HomeKit shows "replace filter" |
87
88
  | `pollInterval` | `120` | Seconds between safety-net status polls |
88
89
 
@@ -122,6 +122,12 @@
122
122
  "type": "boolean",
123
123
  "default": false,
124
124
  "description": "At startup, ask the purifier for all its ports and cloud shadow and write them to the log. Turn off again afterwards."
125
+ },
126
+ "homekitIdentity": {
127
+ "title": "HomeKit identity (advanced)",
128
+ "type": "string",
129
+ "placeholder": "e.g. 2",
130
+ "description": "Only if the Home app refuses to move or rename the purifier: type any new value (e.g. 2, then 3 next time) and restart. Home will see the purifier as a new accessory; reassign its room and check your scenes afterwards."
125
131
  }
126
132
  }
127
133
  },
@@ -165,7 +171,8 @@
165
171
  "displayLightProperty",
166
172
  "filterChangeThreshold",
167
173
  "pollInterval",
168
- "logDeviceData"
174
+ "logDeviceData",
175
+ "homekitIdentity"
169
176
  ]
170
177
  }
171
178
  ]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-philips-airplus-cloud",
3
3
  "displayName": "Philips Air+",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "description": "Homebridge plugin for Philips Air+ / PureProtect air purifiers via the Philips (Versuni) cloud — the same MQTT protocol used by the Air+ app and Google Home.",
6
6
  "main": "src/index.js",
7
7
  "license": "MIT",
package/src/accessory.js CHANGED
@@ -78,9 +78,12 @@ class PurifierAccessory {
78
78
  });
79
79
  this.conn.on('message', (m, topic) => this._onMessage(m, topic));
80
80
 
81
+ // The cached/cloud model code is only a hint: if it does not match a
82
+ // models.yaml entry, wait for the device to report its exact code on the
83
+ // Config port instead of starting with the reduced fallback profile.
81
84
  const known = accessory.context.model;
82
- if (known) this._applyModel(known, false);
83
- else this.log.info('Model not known yet; waiting for the device to report it');
85
+ if (known && findModel(known)) this._applyModel(known, false);
86
+ else this.log.info(`Model ${known ? `${known} ` : ''}not identified yet; waiting for the device to report it`);
84
87
 
85
88
  this._setInfo();
86
89
  }
package/src/models.js CHANGED
@@ -24,14 +24,22 @@ function loadModels(file = MODELS_FILE) {
24
24
  return cache;
25
25
  }
26
26
 
27
- /** Exact match first, then prefix match (e.g. "AC3221/10" -> "AC3221"). */
27
+ /**
28
+ * Exact match first, then prefix match either way:
29
+ * - "AC3221/10" -> "AC3221" (device reports a longer code than the yaml key)
30
+ * - "AC0651" -> "AC0651/10" (the cloud device list omits the /xx suffix)
31
+ */
28
32
  function findModel(ctn) {
29
33
  if (!ctn) return null;
34
+ const code = String(ctn).trim().toUpperCase();
30
35
  const models = loadModels();
31
- if (models[ctn]) return { key: ctn, ...models[ctn] };
32
- for (const [key, cfg] of Object.entries(models)) {
33
- if (String(ctn).startsWith(key)) return { key, ...cfg };
34
- }
36
+ const entries = Object.entries(models);
37
+ const exact = entries.find(([key]) => key.toUpperCase() === code);
38
+ if (exact) return { key: exact[0], ...exact[1] };
39
+ const longer = entries.find(([key]) => code.startsWith(key.toUpperCase()));
40
+ if (longer) return { key: longer[0], ...longer[1] };
41
+ const base = entries.find(([key]) => key.toUpperCase().startsWith(`${code}/`));
42
+ if (base) return { key: base[0], ...base[1] };
35
43
  return null;
36
44
  }
37
45
 
package/src/platform.js CHANGED
@@ -66,7 +66,10 @@ class PhilipsAirPlusPlatform {
66
66
 
67
67
  const keep = new Set();
68
68
  for (const dev of wanted) {
69
- const hbUuid = this.api.hap.uuid.generate(`${C.PLATFORM_NAME}:${dev.uuid}`);
69
+ // homekitIdentity: changing it gives the accessory a brand-new HomeKit
70
+ // identity, for when the Home app has a stuck record of the old one.
71
+ const identity = String(this.config.homekitIdentity || '').trim();
72
+ const hbUuid = this.api.hap.uuid.generate(`${C.PLATFORM_NAME}:${dev.uuid}${identity ? `:${identity}` : ''}`);
70
73
  keep.add(hbUuid);
71
74
  let acc = this.cached.get(hbUuid);
72
75
  if (!acc) {