homebridge-philips-airplus-cloud 0.3.3 → 0.3.5
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 +8 -0
- package/README.md +1 -0
- package/config.schema.json +8 -1
- package/package.json +1 -1
- package/src/accessory.js +39 -0
- package/src/platform.js +9 -0
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.5 — 2026-10-11
|
|
6
|
+
|
|
7
|
+
- Option `labelRefresh`: if the Home app does not show the labels under the purifier's components in its combined view, set any new value and restart. About 40 s after startup the plugin renames the components in two steps (long name, then short label), replaying the rename that made Home show the labels before. Names set in the Home app are replaced by the defaults.
|
|
8
|
+
|
|
9
|
+
## 0.3.4 — 2026-10-11
|
|
10
|
+
|
|
11
|
+
- Fix (0.3.3 regression): on Homebridge 2.x a newly created accessory could be left out of HomeKit ("has the same UUID as existing accessory … Skipping duplicate"), because its settings were saved before it was registered. Accessories are now only updated after registration.
|
|
12
|
+
|
|
5
13
|
## 0.3.3 — 2026-10-11
|
|
6
14
|
|
|
7
15
|
- Fix: the plugin could stop updating when the cloud connection stayed open but stopped delivering data. If the purifier sends nothing for three status polls (at least 6 minutes), the connection is now rebuilt.
|
package/README.md
CHANGED
|
@@ -84,6 +84,7 @@ The refresh token rotates on every use; the plugin keeps the current one in `<ho
|
|
|
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
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) |
|
|
87
|
+
| `labelRefresh` | — | Change to any new value if the Home app does not show the labels under the components; the plugin renames them in two steps ~40 s after startup (Home-app renames are replaced) |
|
|
87
88
|
| `filterChangeThreshold` | `10` | Filter life (%) at or below which HomeKit shows "replace filter" |
|
|
88
89
|
| `pollInterval` | `120` | Seconds between safety-net status polls |
|
|
89
90
|
|
package/config.schema.json
CHANGED
|
@@ -128,6 +128,12 @@
|
|
|
128
128
|
"type": "string",
|
|
129
129
|
"placeholder": "e.g. 2",
|
|
130
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."
|
|
131
|
+
},
|
|
132
|
+
"labelRefresh": {
|
|
133
|
+
"title": "Refresh component labels (advanced)",
|
|
134
|
+
"type": "string",
|
|
135
|
+
"placeholder": "e.g. 1",
|
|
136
|
+
"description": "If the Home app does not show the labels under the purifier's components, type any new value and restart: about 40 seconds after startup the plugin renames the components in two steps so Home picks the labels up. Names you set in the Home app are replaced by the default ones."
|
|
131
137
|
}
|
|
132
138
|
}
|
|
133
139
|
},
|
|
@@ -172,7 +178,8 @@
|
|
|
172
178
|
"filterChangeThreshold",
|
|
173
179
|
"pollInterval",
|
|
174
180
|
"logDeviceData",
|
|
175
|
-
"homekitIdentity"
|
|
181
|
+
"homekitIdentity",
|
|
182
|
+
"labelRefresh"
|
|
176
183
|
]
|
|
177
184
|
}
|
|
178
185
|
]
|
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.
|
|
4
|
+
"version": "0.3.5",
|
|
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
|
@@ -90,6 +90,7 @@ class PurifierAccessory {
|
|
|
90
90
|
|
|
91
91
|
start() {
|
|
92
92
|
this.conn.start();
|
|
93
|
+
this._scheduleLabelRefresh();
|
|
93
94
|
const pollS = Math.max(30, Number(this.platform.config.pollInterval) || C.STATUS_POLL_INTERVAL_S);
|
|
94
95
|
// Watchdog: the cloud connection can stay open but stop delivering data.
|
|
95
96
|
// Every poll asks the device for its status, so if nothing has arrived for
|
|
@@ -119,7 +120,44 @@ class PurifierAccessory {
|
|
|
119
120
|
}, C.FILTER_POLL_INTERVAL_MS);
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
/**
|
|
124
|
+
* labelRefresh: replays what made the Home app show labels under each
|
|
125
|
+
* component in the purifier's combined view — the plugin renaming the
|
|
126
|
+
* components while Home is connected (as the 0.2.1 update did, from
|
|
127
|
+
* "<purifier> Air Quality" to "Air Quality"). Runs once per new value.
|
|
128
|
+
* Names given in the Home app are replaced by the default labels.
|
|
129
|
+
*/
|
|
130
|
+
_scheduleLabelRefresh() {
|
|
131
|
+
const want = String(this.platform.config.labelRefresh || '').trim();
|
|
132
|
+
if (!want || this.accessory.context.labelRefresh === want) return;
|
|
133
|
+
const { Characteristic } = this.hap;
|
|
134
|
+
const STEP_MS = 20000;
|
|
135
|
+
const targets = () => this.accessory.services.filter((x) => x.__label && x !== this.purifier);
|
|
136
|
+
const rename = (long) => {
|
|
137
|
+
for (const x of targets()) {
|
|
138
|
+
const name = long ? `${this.accessory.displayName} ${x.__label}` : x.__label;
|
|
139
|
+
x.updateCharacteristic(Characteristic.Name, name);
|
|
140
|
+
if (x.testCharacteristic(Characteristic.ConfiguredName)) x.updateCharacteristic(Characteristic.ConfiguredName, name);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
this._labelTimers = [
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
if (!targets().length) return;
|
|
146
|
+
this.log.info('labelRefresh: renaming components in two steps so the Home app shows their labels');
|
|
147
|
+
rename(true);
|
|
148
|
+
}, STEP_MS),
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
if (!targets().length) return;
|
|
151
|
+
rename(false);
|
|
152
|
+
this.accessory.context.labelRefresh = want;
|
|
153
|
+
this.platform.updateAccessory(this.accessory);
|
|
154
|
+
this.log.info('labelRefresh: done');
|
|
155
|
+
}, 2 * STEP_MS),
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
|
|
122
159
|
stop() {
|
|
160
|
+
(this._labelTimers || []).forEach(clearTimeout);
|
|
123
161
|
clearInterval(this.pollTimer);
|
|
124
162
|
clearInterval(this.filterTimer);
|
|
125
163
|
this.conn.stop();
|
|
@@ -189,6 +227,7 @@ class PurifierAccessory {
|
|
|
189
227
|
? this.accessory.getServiceById(type, subtype)
|
|
190
228
|
: this.accessory.services.find((x) => x.UUID === type.UUID && !x.subtype);
|
|
191
229
|
const s = existing || this.accessory.addService(type, label, subtype);
|
|
230
|
+
s.__label = label; // default label, used by labelRefresh (not persisted)
|
|
192
231
|
// Set the label on new services, and once on services created by older
|
|
193
232
|
// versions (which used long "<device> Sleep" names). Later user renames
|
|
194
233
|
// made in the Home app are kept.
|
package/src/platform.js
CHANGED
|
@@ -37,7 +37,14 @@ class PhilipsAirPlusPlatform {
|
|
|
37
37
|
this.cached.set(accessory.UUID, accessory);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Persist context changes. Only for accessories Homebridge already knows:
|
|
42
|
+
* updating one that is not registered yet adds it to Homebridge's cache,
|
|
43
|
+
* and the registration that follows is then rejected as a duplicate
|
|
44
|
+
* (Homebridge 2.x), leaving the accessory out of HomeKit.
|
|
45
|
+
*/
|
|
40
46
|
updateAccessory(accessory) {
|
|
47
|
+
if (!this.cached.has(accessory.UUID)) return;
|
|
41
48
|
this.api.updatePlatformAccessories([accessory]);
|
|
42
49
|
}
|
|
43
50
|
|
|
@@ -89,6 +96,7 @@ class PhilipsAirPlusPlatform {
|
|
|
89
96
|
const register = () => {
|
|
90
97
|
this.log.info(`Adding ${dev.name}`);
|
|
91
98
|
this.api.registerPlatformAccessories(C.PLUGIN_NAME, C.PLATFORM_NAME, [acc]);
|
|
99
|
+
this.cached.set(acc.UUID, acc);
|
|
92
100
|
};
|
|
93
101
|
if (handler.model) register();
|
|
94
102
|
else handler.onModelReady = register;
|
|
@@ -103,6 +111,7 @@ class PhilipsAirPlusPlatform {
|
|
|
103
111
|
if (stale.length) {
|
|
104
112
|
this.log.info(`Removing ${stale.map((a) => a.displayName).join(', ')}`);
|
|
105
113
|
this.api.unregisterPlatformAccessories(C.PLUGIN_NAME, C.PLATFORM_NAME, stale);
|
|
114
|
+
for (const a of stale) this.cached.delete(a.UUID);
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
}
|