@camstack/addon-provider-homematic 1.2.131 → 1.2.132

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
@@ -26173,11 +26173,14 @@ var HardwareClassIdSchema = _enum([
26173
26173
  "intel-n-series",
26174
26174
  "apple-m-base",
26175
26175
  "apple-m-pro",
26176
+ "amd-phoenix",
26176
26177
  "unknown"
26177
26178
  ]);
26178
26179
  var AcceleratorKindSchema = _enum([
26179
26180
  "intel-npu",
26180
26181
  "intel-igpu",
26182
+ "amd-igpu",
26183
+ "amd-npu",
26181
26184
  "x86-cpu",
26182
26185
  "arm-cpu",
26183
26186
  "apple-ane",
@@ -36795,7 +36798,13 @@ var TopologyNodeSchema = object({
36795
36798
  })).readonly(),
36796
36799
  processes: array(TopologyProcessSchema).readonly(),
36797
36800
  categories: array(TopologyCategorySchema).readonly(),
36798
- rootPackage: TopologyRootPackageSchema.nullable()
36801
+ rootPackage: TopologyRootPackageSchema.nullable(),
36802
+ /**
36803
+ * How an online AGENT reaches the hub (mesh mTLS Release A): `tls` once it has
36804
+ * completed a mutual-TLS handshake, `clear` otherwise. Absent for the hub,
36805
+ * for offline rows and when the hub cannot tell.
36806
+ */
36807
+ meshTransport: _enum(["tls", "clear"]).optional()
36799
36808
  });
36800
36809
  var CapUsageEdgeSchema = object({
36801
36810
  callerAddonId: string(),
@@ -37765,7 +37774,15 @@ var GpuInfoSchema = object({
37765
37774
  name: string(),
37766
37775
  memoryMB: number().optional()
37767
37776
  });
37768
- var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
37777
+ var NpuInfoSchema = object({
37778
+ type: _enum([
37779
+ "apple-ane",
37780
+ "intel-npu",
37781
+ "amd-xdna"
37782
+ ]),
37783
+ name: string().optional(),
37784
+ generation: _enum(["xdna1", "xdna2"]).optional()
37785
+ });
37769
37786
  var CoralInfoSchema = object({
37770
37787
  type: literal("coral-edgetpu"),
37771
37788
  bus: string().optional()
@@ -56693,6 +56710,8 @@ var STATE_SUMMARY_VARS = [
56693
56710
  [...RULE_TEXT],
56694
56711
  [...RULE_TEXT],
56695
56712
  [...RULE_TEXT],
56713
+ [...RULE_TEXT],
56714
+ [...RULE_TEXT],
56696
56715
  ([...RULE_TEXT], [...UPDATES]),
56697
56716
  ([...RULE_TEXT], [...PACKAGE_UPDATES]),
56698
56717
  ([...RULE_TEXT], [...PACKAGE_UPDATES]),
@@ -80251,6 +80270,19 @@ function sameConnection(a, b) {
80251
80270
  return a.host === b.host && a.enableHmipRf === b.enableHmipRf && a.enableBidcosRf === b.enableBidcosRf && a.enableGroups === b.enableGroups && a.enableBidcosWired === b.enableBidcosWired && a.callbackHost === b.callbackHost && a.callbackPort === b.callbackPort && a.username === b.username && a.password === b.password && a.tls === b.tls;
80252
80271
  }
80253
80272
  /**
80273
+ * Delays (ms) between background restore retries after a failed
80274
+ * `facade.start()` (e.g. CCU `ECONNREFUSED`). Six attempts ≈ 2+5+10+20+30+60 s.
80275
+ * Exported for specs that drive the schedule with fake timers.
80276
+ */
80277
+ var RESTORE_RETRY_DELAYS_MS = [
80278
+ 2e3,
80279
+ 5e3,
80280
+ 1e4,
80281
+ 2e4,
80282
+ 3e4,
80283
+ 6e4
80284
+ ];
80285
+ /**
80254
80286
  * Manages N live CCU connections ("brokers"), each backed by an
80255
80287
  * {@link HmIntegrationManager}. Replaces the old single-CCU
80256
80288
  * `homematic-gateway.ts` singleton.
@@ -80296,6 +80328,10 @@ var HmBrokerRegistry = class {
80296
80328
  * typically in tests) facade creation is skipped.
80297
80329
  */
80298
80330
  #useDefaultFactory;
80331
+ /** Pending restore-retry timers — cleared on {@link shutdown}. */
80332
+ #retryTimers = /* @__PURE__ */ new Map();
80333
+ /** True after {@link shutdown} — blocks further restore retries. */
80334
+ #shuttingDown = false;
80299
80335
  constructor(logger, deps = {}) {
80300
80336
  this.#logger = logger;
80301
80337
  this.#onBrokerConnected = deps.onBrokerConnected ?? (() => void 0);
@@ -80320,10 +80356,13 @@ var HmBrokerRegistry = class {
80320
80356
  * Restore previously-persisted broker entries on addon boot.
80321
80357
  *
80322
80358
  * Seeds the id counter from existing ids so future allocations never
80323
- * collide. Starts each manager best-effort: a failed start is logged as a
80324
- * warning and the manager is still tracked (it can be retried later).
80359
+ * collide. Starts each manager best-effort: a failed start is rolled back
80360
+ * out of `#managers` (so a later attempt can rebuild) and scheduled for a
80361
+ * bounded background retry — a transient CCU `ECONNREFUSED` must not leave
80362
+ * the broker dead until an operator restarts the addon.
80325
80363
  */
80326
80364
  async restore(entries) {
80365
+ this.#shuttingDown = false;
80327
80366
  for (const entry of entries) this.#seedCounter(entry.id);
80328
80367
  for (const entry of entries) try {
80329
80368
  await this.#startManager(entry);
@@ -80333,6 +80372,7 @@ var HmBrokerRegistry = class {
80333
80372
  tags: { brokerId: entry.id },
80334
80373
  meta: { error: msg }
80335
80374
  });
80375
+ this.#scheduleRestoreRetry(entry, 0);
80336
80376
  }
80337
80377
  }
80338
80378
  /**
@@ -80340,6 +80380,9 @@ var HmBrokerRegistry = class {
80340
80380
  * shutdown.
80341
80381
  */
80342
80382
  async shutdown() {
80383
+ this.#shuttingDown = true;
80384
+ for (const timer of this.#retryTimers.values()) clearTimeout(timer);
80385
+ this.#retryTimers.clear();
80343
80386
  const ids = Array.from(this.#managers.keys());
80344
80387
  await Promise.all(ids.map(async (id) => {
80345
80388
  const mgr = this.#managers.get(id);
@@ -80531,6 +80574,10 @@ var HmBrokerRegistry = class {
80531
80574
  * Build (via the factory), register, and start the manager for one entry.
80532
80575
  * If a manager for the same id already exists (e.g. a duplicate restore
80533
80576
  * call), skip construction and only re-index the integration FK.
80577
+ *
80578
+ * On `start()` failure the half-built manager is abandoned so a retry can
80579
+ * construct a fresh one — leaving it in `#managers` used to make every
80580
+ * subsequent `#startManager` a no-op.
80534
80581
  */
80535
80582
  async #startManager(entry) {
80536
80583
  if (this.#managers.has(entry.id)) {
@@ -80547,13 +80594,92 @@ var HmBrokerRegistry = class {
80547
80594
  });
80548
80595
  this.#managers.set(entry.id, mgr);
80549
80596
  if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
80550
- await mgr.start();
80597
+ try {
80598
+ await mgr.start();
80599
+ } catch (err) {
80600
+ await this.#abandonFailedStart(entry.id, mgr);
80601
+ throw err;
80602
+ }
80603
+ const pending = this.#retryTimers.get(entry.id);
80604
+ if (pending !== void 0) {
80605
+ clearTimeout(pending);
80606
+ this.#retryTimers.delete(entry.id);
80607
+ }
80551
80608
  this.#logger.info("HmBrokerRegistry: manager started", {
80552
80609
  tags: { brokerId: entry.id },
80553
80610
  meta: { name: entry.name }
80554
80611
  });
80555
80612
  }
80556
80613
  /**
80614
+ * Drop a manager that never reached `connected` so a later attempt can
80615
+ * rebuild it. Best-effort `stop()`; never throws.
80616
+ */
80617
+ async #abandonFailedStart(id, mgr) {
80618
+ this.#managers.delete(id);
80619
+ this.#facades.delete(id);
80620
+ homematicFacades.set(id, null);
80621
+ try {
80622
+ await mgr.stop();
80623
+ } catch (err) {
80624
+ const msg = err instanceof Error ? err.message : String(err);
80625
+ this.#logger.debug("HmBrokerRegistry: abandon stop failed", {
80626
+ tags: { brokerId: id },
80627
+ meta: { error: msg }
80628
+ });
80629
+ }
80630
+ }
80631
+ /**
80632
+ * Schedule attempt `attemptIndex` of {@link RESTORE_RETRY_DELAYS_MS} for
80633
+ * `entry`. No-ops when the registry is shutting down or the schedule is
80634
+ * exhausted (warns once at the end).
80635
+ */
80636
+ #scheduleRestoreRetry(entry, attemptIndex) {
80637
+ if (this.#shuttingDown) return;
80638
+ const delay = RESTORE_RETRY_DELAYS_MS[attemptIndex];
80639
+ if (delay === void 0) {
80640
+ this.#logger.warn("HmBrokerRegistry: restore retries exhausted", {
80641
+ tags: { brokerId: entry.id },
80642
+ meta: { attempts: RESTORE_RETRY_DELAYS_MS.length }
80643
+ });
80644
+ return;
80645
+ }
80646
+ const existing = this.#retryTimers.get(entry.id);
80647
+ if (existing !== void 0) clearTimeout(existing);
80648
+ const timer = setTimeout(() => {
80649
+ this.#retryTimers.delete(entry.id);
80650
+ this.#runRestoreRetry(entry, attemptIndex);
80651
+ }, delay);
80652
+ this.#retryTimers.set(entry.id, timer);
80653
+ this.#logger.info("HmBrokerRegistry: scheduling restore retry", {
80654
+ tags: { brokerId: entry.id },
80655
+ meta: {
80656
+ attempt: attemptIndex + 1,
80657
+ delayMs: delay
80658
+ }
80659
+ });
80660
+ }
80661
+ async #runRestoreRetry(entry, attemptIndex) {
80662
+ if (this.#shuttingDown) return;
80663
+ if (this.#managers.has(entry.id)) return;
80664
+ try {
80665
+ await this.#startManager(entry);
80666
+ this.#logger.info("HmBrokerRegistry: restore retry succeeded", {
80667
+ tags: { brokerId: entry.id },
80668
+ meta: { attempt: attemptIndex + 1 }
80669
+ });
80670
+ } catch (err) {
80671
+ const msg = err instanceof Error ? err.message : String(err);
80672
+ this.#logger.warn("HmBrokerRegistry: restore retry failed", {
80673
+ tags: { brokerId: entry.id },
80674
+ meta: {
80675
+ attempt: attemptIndex + 1,
80676
+ error: msg
80677
+ }
80678
+ });
80679
+ this.#scheduleRestoreRetry(entry, attemptIndex + 1);
80680
+ }
80681
+ }
80682
+ /**
80557
80683
  * Allocate the next monotonic id in the `hm_NNN` sequence and advance
80558
80684
  * the counter.
80559
80685
  */
@@ -83010,6 +83136,19 @@ var HmContainerDevice = class HmContainerDevice extends BaseDevice {
83010
83136
  }
83011
83137
  }
83012
83138
  /**
83139
+ * Stamp `feature-probe.lastProbedAt` only when the CCU device is on the
83140
+ * facade. An empty accessory-spec list + a stamped probe is how the kernel
83141
+ * orphan-prunes linked children (`device-cap-proxy` safety gate): a restore
83142
+ * that races a CCU `ECONNREFUSED` used to delete every accessory and fence
83143
+ * their identities for 5 minutes. Leaving `lastProbedAt === 0` until the
83144
+ * device is present keeps the gate closed; `reprobe()` on broker connect
83145
+ * completes the probe and spawns/reconciles children for real.
83146
+ */
83147
+ async onProbe() {
83148
+ if (this.resolveHmDevice() === null) return;
83149
+ await super.onProbe();
83150
+ }
83151
+ /**
83013
83152
  * Derive the accessory children LIVE from the facade descriptor. Each child
83014
83153
  * is a {@link HmEntitySpec} (the shared per-entity decomposition) wrapped into
83015
83154
  * an `AccessoryChildSpec`: the `stableIdSuffix` IS the spec `key`, the broker
@@ -83149,6 +83288,7 @@ var HomematicProviderAddon = class extends BaseDeviceProvider {
83149
83288
  this.registry.setOnBrokerConnected((brokerId) => {
83150
83289
  this.ctx.logger.info("Homematic: broker connected", { meta: { brokerId } });
83151
83290
  this.setBrokerDevicesOnline(brokerId, true);
83291
+ this.reprobeBrokerContainers(brokerId);
83152
83292
  });
83153
83293
  this.registry.setOnBrokerDisconnected((brokerId) => {
83154
83294
  this.setBrokerDevicesOnline(brokerId, false);
@@ -83543,6 +83683,28 @@ var HomematicProviderAddon = class extends BaseDeviceProvider {
83543
83683
  count: n
83544
83684
  } });
83545
83685
  }
83686
+ /**
83687
+ * Reprobe every adopted {@link HmContainerDevice} for `brokerId` so the
83688
+ * kernel re-runs accessory reconcile against the live facade. Best-effort:
83689
+ * one container's failure must not block the rest (same discipline as HA's
83690
+ * post-connect reconcile).
83691
+ */
83692
+ async reprobeBrokerContainers(brokerId) {
83693
+ const reg = this.ctx.kernel.deviceRegistry;
83694
+ if (!reg) return;
83695
+ const containers = reg.getAllForAddon(this.addonId).filter((d) => d instanceof HmContainerDevice && d.config.get("brokerId") === brokerId);
83696
+ for (const container of containers) try {
83697
+ await container.reprobe();
83698
+ } catch (err) {
83699
+ this.ctx.logger.warn("Homematic: container reprobe failed", {
83700
+ tags: { deviceId: container.id },
83701
+ meta: {
83702
+ brokerId,
83703
+ error: errMsg(err)
83704
+ }
83705
+ });
83706
+ }
83707
+ }
83546
83708
  /** Return the live registry or throw if the addon is not initialised. */
83547
83709
  requireRegistry() {
83548
83710
  if (!this.registry) throw new Error("Homematic provider not initialised");
package/dist/addon.mjs CHANGED
@@ -26174,11 +26174,14 @@ var HardwareClassIdSchema = _enum([
26174
26174
  "intel-n-series",
26175
26175
  "apple-m-base",
26176
26176
  "apple-m-pro",
26177
+ "amd-phoenix",
26177
26178
  "unknown"
26178
26179
  ]);
26179
26180
  var AcceleratorKindSchema = _enum([
26180
26181
  "intel-npu",
26181
26182
  "intel-igpu",
26183
+ "amd-igpu",
26184
+ "amd-npu",
26182
26185
  "x86-cpu",
26183
26186
  "arm-cpu",
26184
26187
  "apple-ane",
@@ -36796,7 +36799,13 @@ var TopologyNodeSchema = object({
36796
36799
  })).readonly(),
36797
36800
  processes: array(TopologyProcessSchema).readonly(),
36798
36801
  categories: array(TopologyCategorySchema).readonly(),
36799
- rootPackage: TopologyRootPackageSchema.nullable()
36802
+ rootPackage: TopologyRootPackageSchema.nullable(),
36803
+ /**
36804
+ * How an online AGENT reaches the hub (mesh mTLS Release A): `tls` once it has
36805
+ * completed a mutual-TLS handshake, `clear` otherwise. Absent for the hub,
36806
+ * for offline rows and when the hub cannot tell.
36807
+ */
36808
+ meshTransport: _enum(["tls", "clear"]).optional()
36800
36809
  });
36801
36810
  var CapUsageEdgeSchema = object({
36802
36811
  callerAddonId: string(),
@@ -37766,7 +37775,15 @@ var GpuInfoSchema = object({
37766
37775
  name: string(),
37767
37776
  memoryMB: number().optional()
37768
37777
  });
37769
- var NpuInfoSchema = object({ type: _enum(["apple-ane", "intel-npu"]) });
37778
+ var NpuInfoSchema = object({
37779
+ type: _enum([
37780
+ "apple-ane",
37781
+ "intel-npu",
37782
+ "amd-xdna"
37783
+ ]),
37784
+ name: string().optional(),
37785
+ generation: _enum(["xdna1", "xdna2"]).optional()
37786
+ });
37770
37787
  var CoralInfoSchema = object({
37771
37788
  type: literal("coral-edgetpu"),
37772
37789
  bus: string().optional()
@@ -56694,6 +56711,8 @@ var STATE_SUMMARY_VARS = [
56694
56711
  [...RULE_TEXT],
56695
56712
  [...RULE_TEXT],
56696
56713
  [...RULE_TEXT],
56714
+ [...RULE_TEXT],
56715
+ [...RULE_TEXT],
56697
56716
  ([...RULE_TEXT], [...UPDATES]),
56698
56717
  ([...RULE_TEXT], [...PACKAGE_UPDATES]),
56699
56718
  ([...RULE_TEXT], [...PACKAGE_UPDATES]),
@@ -80252,6 +80271,19 @@ function sameConnection(a, b) {
80252
80271
  return a.host === b.host && a.enableHmipRf === b.enableHmipRf && a.enableBidcosRf === b.enableBidcosRf && a.enableGroups === b.enableGroups && a.enableBidcosWired === b.enableBidcosWired && a.callbackHost === b.callbackHost && a.callbackPort === b.callbackPort && a.username === b.username && a.password === b.password && a.tls === b.tls;
80253
80272
  }
80254
80273
  /**
80274
+ * Delays (ms) between background restore retries after a failed
80275
+ * `facade.start()` (e.g. CCU `ECONNREFUSED`). Six attempts ≈ 2+5+10+20+30+60 s.
80276
+ * Exported for specs that drive the schedule with fake timers.
80277
+ */
80278
+ var RESTORE_RETRY_DELAYS_MS = [
80279
+ 2e3,
80280
+ 5e3,
80281
+ 1e4,
80282
+ 2e4,
80283
+ 3e4,
80284
+ 6e4
80285
+ ];
80286
+ /**
80255
80287
  * Manages N live CCU connections ("brokers"), each backed by an
80256
80288
  * {@link HmIntegrationManager}. Replaces the old single-CCU
80257
80289
  * `homematic-gateway.ts` singleton.
@@ -80297,6 +80329,10 @@ var HmBrokerRegistry = class {
80297
80329
  * typically in tests) facade creation is skipped.
80298
80330
  */
80299
80331
  #useDefaultFactory;
80332
+ /** Pending restore-retry timers — cleared on {@link shutdown}. */
80333
+ #retryTimers = /* @__PURE__ */ new Map();
80334
+ /** True after {@link shutdown} — blocks further restore retries. */
80335
+ #shuttingDown = false;
80300
80336
  constructor(logger, deps = {}) {
80301
80337
  this.#logger = logger;
80302
80338
  this.#onBrokerConnected = deps.onBrokerConnected ?? (() => void 0);
@@ -80321,10 +80357,13 @@ var HmBrokerRegistry = class {
80321
80357
  * Restore previously-persisted broker entries on addon boot.
80322
80358
  *
80323
80359
  * Seeds the id counter from existing ids so future allocations never
80324
- * collide. Starts each manager best-effort: a failed start is logged as a
80325
- * warning and the manager is still tracked (it can be retried later).
80360
+ * collide. Starts each manager best-effort: a failed start is rolled back
80361
+ * out of `#managers` (so a later attempt can rebuild) and scheduled for a
80362
+ * bounded background retry — a transient CCU `ECONNREFUSED` must not leave
80363
+ * the broker dead until an operator restarts the addon.
80326
80364
  */
80327
80365
  async restore(entries) {
80366
+ this.#shuttingDown = false;
80328
80367
  for (const entry of entries) this.#seedCounter(entry.id);
80329
80368
  for (const entry of entries) try {
80330
80369
  await this.#startManager(entry);
@@ -80334,6 +80373,7 @@ var HmBrokerRegistry = class {
80334
80373
  tags: { brokerId: entry.id },
80335
80374
  meta: { error: msg }
80336
80375
  });
80376
+ this.#scheduleRestoreRetry(entry, 0);
80337
80377
  }
80338
80378
  }
80339
80379
  /**
@@ -80341,6 +80381,9 @@ var HmBrokerRegistry = class {
80341
80381
  * shutdown.
80342
80382
  */
80343
80383
  async shutdown() {
80384
+ this.#shuttingDown = true;
80385
+ for (const timer of this.#retryTimers.values()) clearTimeout(timer);
80386
+ this.#retryTimers.clear();
80344
80387
  const ids = Array.from(this.#managers.keys());
80345
80388
  await Promise.all(ids.map(async (id) => {
80346
80389
  const mgr = this.#managers.get(id);
@@ -80532,6 +80575,10 @@ var HmBrokerRegistry = class {
80532
80575
  * Build (via the factory), register, and start the manager for one entry.
80533
80576
  * If a manager for the same id already exists (e.g. a duplicate restore
80534
80577
  * call), skip construction and only re-index the integration FK.
80578
+ *
80579
+ * On `start()` failure the half-built manager is abandoned so a retry can
80580
+ * construct a fresh one — leaving it in `#managers` used to make every
80581
+ * subsequent `#startManager` a no-op.
80535
80582
  */
80536
80583
  async #startManager(entry) {
80537
80584
  if (this.#managers.has(entry.id)) {
@@ -80548,13 +80595,92 @@ var HmBrokerRegistry = class {
80548
80595
  });
80549
80596
  this.#managers.set(entry.id, mgr);
80550
80597
  if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
80551
- await mgr.start();
80598
+ try {
80599
+ await mgr.start();
80600
+ } catch (err) {
80601
+ await this.#abandonFailedStart(entry.id, mgr);
80602
+ throw err;
80603
+ }
80604
+ const pending = this.#retryTimers.get(entry.id);
80605
+ if (pending !== void 0) {
80606
+ clearTimeout(pending);
80607
+ this.#retryTimers.delete(entry.id);
80608
+ }
80552
80609
  this.#logger.info("HmBrokerRegistry: manager started", {
80553
80610
  tags: { brokerId: entry.id },
80554
80611
  meta: { name: entry.name }
80555
80612
  });
80556
80613
  }
80557
80614
  /**
80615
+ * Drop a manager that never reached `connected` so a later attempt can
80616
+ * rebuild it. Best-effort `stop()`; never throws.
80617
+ */
80618
+ async #abandonFailedStart(id, mgr) {
80619
+ this.#managers.delete(id);
80620
+ this.#facades.delete(id);
80621
+ homematicFacades.set(id, null);
80622
+ try {
80623
+ await mgr.stop();
80624
+ } catch (err) {
80625
+ const msg = err instanceof Error ? err.message : String(err);
80626
+ this.#logger.debug("HmBrokerRegistry: abandon stop failed", {
80627
+ tags: { brokerId: id },
80628
+ meta: { error: msg }
80629
+ });
80630
+ }
80631
+ }
80632
+ /**
80633
+ * Schedule attempt `attemptIndex` of {@link RESTORE_RETRY_DELAYS_MS} for
80634
+ * `entry`. No-ops when the registry is shutting down or the schedule is
80635
+ * exhausted (warns once at the end).
80636
+ */
80637
+ #scheduleRestoreRetry(entry, attemptIndex) {
80638
+ if (this.#shuttingDown) return;
80639
+ const delay = RESTORE_RETRY_DELAYS_MS[attemptIndex];
80640
+ if (delay === void 0) {
80641
+ this.#logger.warn("HmBrokerRegistry: restore retries exhausted", {
80642
+ tags: { brokerId: entry.id },
80643
+ meta: { attempts: RESTORE_RETRY_DELAYS_MS.length }
80644
+ });
80645
+ return;
80646
+ }
80647
+ const existing = this.#retryTimers.get(entry.id);
80648
+ if (existing !== void 0) clearTimeout(existing);
80649
+ const timer = setTimeout(() => {
80650
+ this.#retryTimers.delete(entry.id);
80651
+ this.#runRestoreRetry(entry, attemptIndex);
80652
+ }, delay);
80653
+ this.#retryTimers.set(entry.id, timer);
80654
+ this.#logger.info("HmBrokerRegistry: scheduling restore retry", {
80655
+ tags: { brokerId: entry.id },
80656
+ meta: {
80657
+ attempt: attemptIndex + 1,
80658
+ delayMs: delay
80659
+ }
80660
+ });
80661
+ }
80662
+ async #runRestoreRetry(entry, attemptIndex) {
80663
+ if (this.#shuttingDown) return;
80664
+ if (this.#managers.has(entry.id)) return;
80665
+ try {
80666
+ await this.#startManager(entry);
80667
+ this.#logger.info("HmBrokerRegistry: restore retry succeeded", {
80668
+ tags: { brokerId: entry.id },
80669
+ meta: { attempt: attemptIndex + 1 }
80670
+ });
80671
+ } catch (err) {
80672
+ const msg = err instanceof Error ? err.message : String(err);
80673
+ this.#logger.warn("HmBrokerRegistry: restore retry failed", {
80674
+ tags: { brokerId: entry.id },
80675
+ meta: {
80676
+ attempt: attemptIndex + 1,
80677
+ error: msg
80678
+ }
80679
+ });
80680
+ this.#scheduleRestoreRetry(entry, attemptIndex + 1);
80681
+ }
80682
+ }
80683
+ /**
80558
80684
  * Allocate the next monotonic id in the `hm_NNN` sequence and advance
80559
80685
  * the counter.
80560
80686
  */
@@ -83011,6 +83137,19 @@ var HmContainerDevice = class HmContainerDevice extends BaseDevice {
83011
83137
  }
83012
83138
  }
83013
83139
  /**
83140
+ * Stamp `feature-probe.lastProbedAt` only when the CCU device is on the
83141
+ * facade. An empty accessory-spec list + a stamped probe is how the kernel
83142
+ * orphan-prunes linked children (`device-cap-proxy` safety gate): a restore
83143
+ * that races a CCU `ECONNREFUSED` used to delete every accessory and fence
83144
+ * their identities for 5 minutes. Leaving `lastProbedAt === 0` until the
83145
+ * device is present keeps the gate closed; `reprobe()` on broker connect
83146
+ * completes the probe and spawns/reconciles children for real.
83147
+ */
83148
+ async onProbe() {
83149
+ if (this.resolveHmDevice() === null) return;
83150
+ await super.onProbe();
83151
+ }
83152
+ /**
83014
83153
  * Derive the accessory children LIVE from the facade descriptor. Each child
83015
83154
  * is a {@link HmEntitySpec} (the shared per-entity decomposition) wrapped into
83016
83155
  * an `AccessoryChildSpec`: the `stableIdSuffix` IS the spec `key`, the broker
@@ -83150,6 +83289,7 @@ var HomematicProviderAddon = class extends BaseDeviceProvider {
83150
83289
  this.registry.setOnBrokerConnected((brokerId) => {
83151
83290
  this.ctx.logger.info("Homematic: broker connected", { meta: { brokerId } });
83152
83291
  this.setBrokerDevicesOnline(brokerId, true);
83292
+ this.reprobeBrokerContainers(brokerId);
83153
83293
  });
83154
83294
  this.registry.setOnBrokerDisconnected((brokerId) => {
83155
83295
  this.setBrokerDevicesOnline(brokerId, false);
@@ -83544,6 +83684,28 @@ var HomematicProviderAddon = class extends BaseDeviceProvider {
83544
83684
  count: n
83545
83685
  } });
83546
83686
  }
83687
+ /**
83688
+ * Reprobe every adopted {@link HmContainerDevice} for `brokerId` so the
83689
+ * kernel re-runs accessory reconcile against the live facade. Best-effort:
83690
+ * one container's failure must not block the rest (same discipline as HA's
83691
+ * post-connect reconcile).
83692
+ */
83693
+ async reprobeBrokerContainers(brokerId) {
83694
+ const reg = this.ctx.kernel.deviceRegistry;
83695
+ if (!reg) return;
83696
+ const containers = reg.getAllForAddon(this.addonId).filter((d) => d instanceof HmContainerDevice && d.config.get("brokerId") === brokerId);
83697
+ for (const container of containers) try {
83698
+ await container.reprobe();
83699
+ } catch (err) {
83700
+ this.ctx.logger.warn("Homematic: container reprobe failed", {
83701
+ tags: { deviceId: container.id },
83702
+ meta: {
83703
+ brokerId,
83704
+ error: errMsg(err)
83705
+ }
83706
+ });
83707
+ }
83708
+ }
83547
83709
  /** Return the live registry or throw if the addon is not initialised. */
83548
83710
  requireRegistry() {
83549
83711
  if (!this.registry) throw new Error("Homematic provider not initialised");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homematic",
3
- "version": "1.2.131",
3
+ "version": "1.2.132",
4
4
  "description": "Homematic / HomematicIP (CCU3 / RaspberryMatic) device-provider addon for CamStack — wraps the nodehomematic library",
5
5
  "keywords": [
6
6
  "camstack",