@camstack/addon-provider-homematic 1.2.61 → 1.2.62

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/dist/addon.js CHANGED
@@ -12831,7 +12831,26 @@ var DiscoveryCandidateSchema = object({
12831
12831
  * identity ahead of adoption. Rendering metadata (unit, precision)
12832
12832
  * flows live through the cap STATUS SLICE after adoption.
12833
12833
  */
12834
- sourceInfo: SourceInfoSchema.optional()
12834
+ sourceInfo: SourceInfoSchema.optional(),
12835
+ /**
12836
+ * Set when this candidate is a device the provider ALREADY owns.
12837
+ *
12838
+ * A scan cannot generally produce the identity a device was onboarded under
12839
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
12840
+ * comparison never matches and an owned device looks addable. Re-adopting one
12841
+ * overwrites its config with scan-derived values — that is how a Home Hub's
12842
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
12843
+ * three child cameras offline for four hours.
12844
+ *
12845
+ * A provider that can recognise its own devices says so here. Absent means
12846
+ * "not recognised", which is not the same as "known to be new" — a provider
12847
+ * that cannot tell simply never sets it.
12848
+ */
12849
+ alreadyOnboarded: boolean().optional(),
12850
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
12851
+ onboardedDeviceId: number().optional(),
12852
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
12853
+ onboardedName: string().optional()
12835
12854
  });
12836
12855
  /**
12837
12856
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -33069,6 +33088,22 @@ var BaseDeviceProvider = class extends BaseAddon {
33069
33088
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
33070
33089
  * `getStatus().error`.
33071
33090
  */
33091
+ /**
33092
+ * Repair a row's PERSISTED config blob immediately before it is restored.
33093
+ * Default: no-op — most providers have nothing to heal.
33094
+ *
33095
+ * This exists because a restored device self-hydrates from the DB: `create()`
33096
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
33097
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
33098
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
33099
+ * been emptied failed all four bounded attempts against fields
33100
+ * (`host`, `password`) it inherits from its parent and never dials itself.
33101
+ *
33102
+ * Implementations get every saved row, so a child can read its parent's blob.
33103
+ * A heal that throws is treated like any other restore failure: retried under
33104
+ * the bound, then reported — never swallowed.
33105
+ */
33106
+ async healSavedConfig(_saved, _allSaved) {}
33072
33107
  async onRestoreDevices(savedDevices) {
33073
33108
  const restored = /* @__PURE__ */ new Set();
33074
33109
  const failures = [];
@@ -33077,6 +33112,7 @@ var BaseDeviceProvider = class extends BaseAddon {
33077
33112
  const Class = this.deviceClasses[saved.type];
33078
33113
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33079
33114
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33115
+ await this.healSavedConfig(saved, savedDevices);
33080
33116
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33081
33117
  restored.add(saved.id);
33082
33118
  };
package/dist/addon.mjs CHANGED
@@ -12832,7 +12832,26 @@ var DiscoveryCandidateSchema = object({
12832
12832
  * identity ahead of adoption. Rendering metadata (unit, precision)
12833
12833
  * flows live through the cap STATUS SLICE after adoption.
12834
12834
  */
12835
- sourceInfo: SourceInfoSchema.optional()
12835
+ sourceInfo: SourceInfoSchema.optional(),
12836
+ /**
12837
+ * Set when this candidate is a device the provider ALREADY owns.
12838
+ *
12839
+ * A scan cannot generally produce the identity a device was onboarded under
12840
+ * (Reolink keys on `mac-<mac>`, learned at adopt time), so a stableId
12841
+ * comparison never matches and an owned device looks addable. Re-adopting one
12842
+ * overwrites its config with scan-derived values — that is how a Home Hub's
12843
+ * Baichuan port was overwritten with its ONVIF port, taking the hub and its
12844
+ * three child cameras offline for four hours.
12845
+ *
12846
+ * A provider that can recognise its own devices says so here. Absent means
12847
+ * "not recognised", which is not the same as "known to be new" — a provider
12848
+ * that cannot tell simply never sets it.
12849
+ */
12850
+ alreadyOnboarded: boolean().optional(),
12851
+ /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
12852
+ onboardedDeviceId: number().optional(),
12853
+ /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
12854
+ onboardedName: string().optional()
12836
12855
  });
12837
12856
  /**
12838
12857
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -33070,6 +33089,22 @@ var BaseDeviceProvider = class extends BaseAddon {
33070
33089
  * failed — logged at ERROR with `tags.deviceId` and surfaced via
33071
33090
  * `getStatus().error`.
33072
33091
  */
33092
+ /**
33093
+ * Repair a row's PERSISTED config blob immediately before it is restored.
33094
+ * Default: no-op — most providers have nothing to heal.
33095
+ *
33096
+ * This exists because a restored device self-hydrates from the DB: `create()`
33097
+ * passes `{}` and `BaseDevice` parses the stored blob against the device
33098
+ * schema. A blob that lost a REQUIRED field therefore fails restore forever,
33099
+ * and no later pass revisits it — a hub-adopted Reolink camera whose blob had
33100
+ * been emptied failed all four bounded attempts against fields
33101
+ * (`host`, `password`) it inherits from its parent and never dials itself.
33102
+ *
33103
+ * Implementations get every saved row, so a child can read its parent's blob.
33104
+ * A heal that throws is treated like any other restore failure: retried under
33105
+ * the bound, then reported — never swallowed.
33106
+ */
33107
+ async healSavedConfig(_saved, _allSaved) {}
33073
33108
  async onRestoreDevices(savedDevices) {
33074
33109
  const restored = /* @__PURE__ */ new Set();
33075
33110
  const failures = [];
@@ -33078,6 +33113,7 @@ var BaseDeviceProvider = class extends BaseAddon {
33078
33113
  const Class = this.deviceClasses[saved.type];
33079
33114
  if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
33080
33115
  if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
33116
+ await this.healSavedConfig(saved, savedDevices);
33081
33117
  await this.ctx.kernel.devices.create(saved.stableId, Class, {}, saved.parentDeviceId);
33082
33118
  restored.add(saved.id);
33083
33119
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homematic",
3
- "version": "1.2.61",
3
+ "version": "1.2.62",
4
4
  "description": "Homematic / HomematicIP (CCU3 / RaspberryMatic) device-provider addon for CamStack — wraps the nodehomematic library",
5
5
  "keywords": [
6
6
  "camstack",