homebridge-tydom 0.30.0 → 0.30.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/README.md +27 -0
- package/dist/index.mjs +75 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -236,6 +236,33 @@ Nothing in your config needs to change, and no accessory is re-registered: rooms
|
|
|
236
236
|
|
|
237
237
|
## Troubleshooting
|
|
238
238
|
|
|
239
|
+
### Duplicate accessories, or `Cannot serialize accessory`
|
|
240
|
+
|
|
241
|
+
Versions up to v0.30.0 handed each newly discovered accessory to Homebridge for
|
|
242
|
+
caching before registering it, which made the cache write fail:
|
|
243
|
+
|
|
244
|
+
```text
|
|
245
|
+
Failed to save cached accessories to disk: Cannot serialize accessory 'Salon' - missing associated plugin
|
|
246
|
+
Accessory 'Salon' has the same UUID as existing accessory 'Salon'. Skipping duplicate.
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
v0.30.1 fixes the cause, and Homebridge rewrites one entry per accessory on the
|
|
250
|
+
first scan after upgrading — so most installations recover on their own after a
|
|
251
|
+
restart. An accessory that was duplicated and then removed from the gateway can
|
|
252
|
+
be left orphaned in the cache, which only a reset clears. If the warnings
|
|
253
|
+
persist, stop Homebridge and delete the cache file once:
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
rm ~/.homebridge/accessories/cachedAccessories
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
On a child bridge the file is named `cachedAccessories.<bridge-username>`. This
|
|
260
|
+
re-registers every accessory, so rooms, names and automations for Tydom devices
|
|
261
|
+
have to be set up again — which is why it is a last resort rather than a routine
|
|
262
|
+
upgrade step.
|
|
263
|
+
|
|
264
|
+
### Debugging
|
|
265
|
+
|
|
239
266
|
Set `debug` to `true` in the config, or start Homebridge with:
|
|
240
267
|
|
|
241
268
|
```bash
|
package/dist/index.mjs
CHANGED
|
@@ -4903,6 +4903,16 @@ var TydomPlatform = class {
|
|
|
4903
4903
|
* that made per-accessory teardown impossible. The platform fans out instead.
|
|
4904
4904
|
*/
|
|
4905
4905
|
companions = /* @__PURE__ */ new Map();
|
|
4906
|
+
/**
|
|
4907
|
+
* The `device` handlers still in flight.
|
|
4908
|
+
*
|
|
4909
|
+
* The controller announces every device synchronously from `scan`, but the
|
|
4910
|
+
* handler is async and only reaches `accessories.set` after two awaits, so
|
|
4911
|
+
* nothing it registers has landed when `scan` resolves. The startup sweep and
|
|
4912
|
+
* the count used to run right there — which is why a fully populated gateway
|
|
4913
|
+
* reported `Properly loaded 0-accessories`.
|
|
4914
|
+
*/
|
|
4915
|
+
pendingDevices = /* @__PURE__ */ new Set();
|
|
4906
4916
|
controller;
|
|
4907
4917
|
/**
|
|
4908
4918
|
* The endpoint-facing API, handed to every accessory.
|
|
@@ -4950,9 +4960,12 @@ var TydomPlatform = class {
|
|
|
4950
4960
|
this.stop();
|
|
4951
4961
|
});
|
|
4952
4962
|
this.controller.on("device", (context) => {
|
|
4953
|
-
this.handleControllerDevice(context).catch((err) => {
|
|
4963
|
+
const pending = this.handleControllerDevice(context).catch((err) => {
|
|
4954
4964
|
this.log.error(`Failed to handle device ${context.deviceId}: ${stringifyError(err)}`);
|
|
4965
|
+
}).finally(() => {
|
|
4966
|
+
this.pendingDevices.delete(pending);
|
|
4955
4967
|
});
|
|
4968
|
+
this.pendingDevices.add(pending);
|
|
4956
4969
|
});
|
|
4957
4970
|
this.controller.on("update", this.handleControllerDataUpdate.bind(this));
|
|
4958
4971
|
this.controller.on("notification", this.handleControllerNotification.bind(this));
|
|
@@ -4992,14 +5005,51 @@ var TydomPlatform = class {
|
|
|
4992
5005
|
}
|
|
4993
5006
|
}
|
|
4994
5007
|
await this.controller.scan();
|
|
5008
|
+
await this.settlePendingDevices();
|
|
4995
5009
|
this.cleanupAccessoriesIds.forEach((accessoryId) => {
|
|
4996
5010
|
const accessory = this.accessories.get(accessoryId);
|
|
4997
5011
|
if (!accessory) return;
|
|
4998
5012
|
this.log.warn(`Deleting missing accessory with id=${styleNumber(accessoryId)}`);
|
|
4999
5013
|
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
5014
|
+
this.forgetAccessory(accessoryId);
|
|
5000
5015
|
});
|
|
5001
5016
|
this.log.info(`Properly loaded ${this.accessories.size}-accessories`);
|
|
5002
5017
|
}
|
|
5018
|
+
/**
|
|
5019
|
+
* Drain the announced-device handlers.
|
|
5020
|
+
*
|
|
5021
|
+
* Looping rather than awaiting once covers a handler that itself announces
|
|
5022
|
+
* more work; the `finally` that empties the set is attached before
|
|
5023
|
+
* `allSettled` subscribes, so the loop always terminates.
|
|
5024
|
+
*/
|
|
5025
|
+
async settlePendingDevices() {
|
|
5026
|
+
while (this.pendingDevices.size > 0) await Promise.allSettled(this.pendingDevices);
|
|
5027
|
+
}
|
|
5028
|
+
/**
|
|
5029
|
+
* Drop every trace of an accessory the platform no longer owns.
|
|
5030
|
+
*
|
|
5031
|
+
* `unregisterPlatformAccessories` only tells Homebridge; the platform's own
|
|
5032
|
+
* tables used to keep the entry. That inflated the `Properly loaded` count by
|
|
5033
|
+
* every accessory the sweep had just removed, and left the handler holding
|
|
5034
|
+
* its timers and the companion links pointing at an accessory that is gone.
|
|
5035
|
+
*
|
|
5036
|
+
* `pruneCompanions` is false when the accessory is about to be rebuilt under
|
|
5037
|
+
* the same UUID: the links are keyed by the primary but written by the
|
|
5038
|
+
* companion's own pass, and the controller announces each accessory once.
|
|
5039
|
+
*/
|
|
5040
|
+
forgetAccessory(id, { pruneCompanions = true } = {}) {
|
|
5041
|
+
this.accessories.delete(id);
|
|
5042
|
+
this.handlers.get(id)?.dispose();
|
|
5043
|
+
this.handlers.delete(id);
|
|
5044
|
+
if (!pruneCompanions) return;
|
|
5045
|
+
this.companions.delete(id);
|
|
5046
|
+
for (const [primaryId, companionIds] of this.companions) {
|
|
5047
|
+
if (!companionIds.includes(id)) continue;
|
|
5048
|
+
const remaining = companionIds.filter((companionId) => companionId !== id);
|
|
5049
|
+
if (remaining.length > 0) this.companions.set(primaryId, remaining);
|
|
5050
|
+
else this.companions.delete(primaryId);
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
5003
5053
|
async handleControllerDevice(context) {
|
|
5004
5054
|
const { name, deviceId, category, accessoryId } = context;
|
|
5005
5055
|
const id = this.api.hap.uuid.generate(accessoryId);
|
|
@@ -5015,8 +5065,10 @@ var TydomPlatform = class {
|
|
|
5015
5065
|
} else {
|
|
5016
5066
|
this.log.warn(`Deleting accessory with new category with id=${styleNumber(accessoryId)}`);
|
|
5017
5067
|
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]);
|
|
5068
|
+
this.forgetAccessory(id, { pruneCompanions: false });
|
|
5018
5069
|
}
|
|
5019
5070
|
const accessory = await this.createAccessory(name, id, category, context);
|
|
5071
|
+
await this.configureHandler(accessory, context);
|
|
5020
5072
|
this.accessories.set(id, accessory);
|
|
5021
5073
|
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
5022
5074
|
}
|
|
@@ -5063,18 +5115,37 @@ var TydomPlatform = class {
|
|
|
5063
5115
|
this.log.info(`Creating accessory named=${styleString(accessoryName)}, deviceId="${styleNumber(context.deviceId)} (id=${styleKeyword(id)})"`);
|
|
5064
5116
|
const accessory = new PlatformAccessory(accessoryName, id, category);
|
|
5065
5117
|
Object.assign(accessory.context, context);
|
|
5066
|
-
await this.updateAccessory(accessory, context);
|
|
5067
5118
|
return accessory;
|
|
5068
5119
|
}
|
|
5120
|
+
/**
|
|
5121
|
+
* Re-configure an accessory Homebridge already knows about, and persist it.
|
|
5122
|
+
*
|
|
5123
|
+
* Only ever call this on an accessory that came back from `configureAccessory`
|
|
5124
|
+
* or that has already been through `registerPlatformAccessories` — those are
|
|
5125
|
+
* the only two ways an accessory gets its plugin association, and without one
|
|
5126
|
+
* the cache write below throws and takes every other accessory down with it.
|
|
5127
|
+
*/
|
|
5069
5128
|
async updateAccessory(accessory, context) {
|
|
5070
5129
|
const { displayName: accessoryName, UUID: id } = accessory;
|
|
5071
5130
|
this.log.info(`Updating accessory named=${styleString(accessoryName)}, deviceId=${styleNumber(context.deviceId)} (id=${styleKeyword(id)})"`);
|
|
5131
|
+
if (!await this.configureHandler(accessory, context)) return;
|
|
5132
|
+
this.api.updatePlatformAccessories([accessory]);
|
|
5133
|
+
}
|
|
5134
|
+
/**
|
|
5135
|
+
* Attach (or re-attach) the handler and its companion links.
|
|
5136
|
+
*
|
|
5137
|
+
* Deliberately touches no Homebridge persistence, so it is safe to run on an
|
|
5138
|
+
* accessory that has not been registered yet. Returns whether a handler was
|
|
5139
|
+
* actually wired, so callers can skip persisting a category they cannot serve.
|
|
5140
|
+
*/
|
|
5141
|
+
async configureHandler(accessory, context) {
|
|
5142
|
+
const { displayName: accessoryName, UUID: id } = accessory;
|
|
5072
5143
|
Object.assign(accessory.context, context);
|
|
5073
5144
|
assert(this.apiClient);
|
|
5074
5145
|
const deviceType = this.resolveDeviceType(context, accessory.category);
|
|
5075
5146
|
if (!deviceType) {
|
|
5076
5147
|
this.log.warn(`Skipping accessory named=${styleString(accessoryName)}: no handler for category=${styleNumber(accessory.category)}`);
|
|
5077
|
-
return;
|
|
5148
|
+
return false;
|
|
5078
5149
|
}
|
|
5079
5150
|
if (context.companionOf) {
|
|
5080
5151
|
const primaryId = this.api.hap.uuid.generate(context.companionOf);
|
|
@@ -5094,7 +5165,7 @@ var TydomPlatform = class {
|
|
|
5094
5165
|
});
|
|
5095
5166
|
}
|
|
5096
5167
|
}));
|
|
5097
|
-
|
|
5168
|
+
return true;
|
|
5098
5169
|
}
|
|
5099
5170
|
configureAccessory(accessory) {
|
|
5100
5171
|
this.log.debug(`Found cached accessory with id="${accessory.UUID}"`);
|