@riddix/hamh 2.0.50 → 2.0.51

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.
@@ -1767,8 +1767,8 @@ var init_Cancelable = __esm({
1767
1767
  };
1768
1768
  return result;
1769
1769
  }
1770
- static set logger(logger247) {
1771
- this.#logger = logger247;
1770
+ static set logger(logger251) {
1771
+ this.#logger = logger251;
1772
1772
  }
1773
1773
  static get logger() {
1774
1774
  return this.#logger;
@@ -3475,13 +3475,13 @@ var init_Logger = __esm({
3475
3475
  *
3476
3476
  * @deprecated use {@link destinations}
3477
3477
  */
3478
- static addLogger(identifier, logger247, options) {
3478
+ static addLogger(identifier, logger251, options) {
3479
3479
  if (identifier in this.destinations) {
3480
3480
  throw new ImplementationError(`Logger "${identifier}" already exists`);
3481
3481
  }
3482
3482
  const dest = LogDestination({ name: identifier });
3483
3483
  const legacy = adaptDestinationToLegacy(dest);
3484
- legacy.log = logger247;
3484
+ legacy.log = logger251;
3485
3485
  if (options?.defaultLogLevel !== void 0) {
3486
3486
  legacy.defaultLogLevel = options.defaultLogLevel;
3487
3487
  }
@@ -5687,8 +5687,8 @@ var init_Construction = __esm({
5687
5687
  }
5688
5688
  }
5689
5689
  #unhandledError(...args) {
5690
- const logger247 = Logger.get(this.#subject.constructor.name);
5691
- logger247.error(...args);
5690
+ const logger251 = Logger.get(this.#subject.constructor.name);
5691
+ logger251.error(...args);
5692
5692
  }
5693
5693
  #createErrorHandler(name) {
5694
5694
  return (e) => {
@@ -11712,18 +11712,18 @@ var init_FileHandleTracker = __esm({
11712
11712
  logger12 = Logger.get("FileHandleTracker");
11713
11713
  ((FileHandleTracker2) => {
11714
11714
  const openHandles = /* @__PURE__ */ new Set();
11715
- const registry2 = new FinalizationRegistry((info) => {
11715
+ const registry3 = new FinalizationRegistry((info) => {
11716
11716
  logger12.warn(`File handle GC'd without close: "${info.purpose}" for ${info.path}`);
11717
11717
  });
11718
11718
  function register(handle) {
11719
11719
  const info = { purpose: handle.purpose, path: handle.path };
11720
- registry2.register(handle, info, handle);
11720
+ registry3.register(handle, info, handle);
11721
11721
  const ref = new WeakRef(handle);
11722
11722
  openHandles.add(ref);
11723
11723
  }
11724
11724
  FileHandleTracker2.register = register;
11725
11725
  function unregister(handle) {
11726
- registry2.unregister(handle);
11726
+ registry3.unregister(handle);
11727
11727
  for (const ref of openHandles) {
11728
11728
  if (ref.deref() === handle) {
11729
11729
  openHandles.delete(ref);
@@ -64070,8 +64070,8 @@ var init_NodeSession = __esm({
64070
64070
  return session?.type === SessionType.Unicast;
64071
64071
  }
64072
64072
  NodeSession2.is = is;
64073
- function logNew(logger247, operation, session, messenger, fabric, peerNodeId) {
64074
- logger247.info(
64073
+ function logNew(logger251, operation, session, messenger, fabric, peerNodeId) {
64074
+ logger251.info(
64075
64075
  session.via,
64076
64076
  `${operation} session with`,
64077
64077
  Diagnostic.strong(PeerAddress({ fabricIndex: fabric.fabricIndex, nodeId: peerNodeId }).toString()),
@@ -117714,11 +117714,11 @@ var init_Api = __esm({
117714
117714
  }
117715
117715
  Api2.resourceFor = resourceFor;
117716
117716
  function log(level, facility, id, ...message) {
117717
- let logger247 = loggers.get(facility);
117718
- if (!logger247) {
117719
- loggers.set(facility, logger247 = Logger.get(facility));
117717
+ let logger251 = loggers.get(facility);
117718
+ if (!logger251) {
117719
+ loggers.set(facility, logger251 = Logger.get(facility));
117720
117720
  }
117721
- logger247[level](Diagnostic.via(id || "(anon)"), message);
117721
+ logger251[level](Diagnostic.via(id || "(anon)"), message);
117722
117722
  }
117723
117723
  Api2.log = log;
117724
117724
  function logRequest(facility, id, method, target) {
@@ -131221,6 +131221,13 @@ var init_controller_compat = __esm({
131221
131221
  aqara: "yes",
131222
131222
  note: "Aqara lists battery storage; others show battery inside a device."
131223
131223
  },
131224
+ 1292: {
131225
+ apple: "no",
131226
+ google: "no",
131227
+ alexa: "no",
131228
+ aqara: "yes",
131229
+ note: "HA and Aqara render EnergyEvse; SmartThings announced support, unconfirmed. Bridged EVSE can break Alexa device recognition, keep it off Alexa bridges."
131230
+ },
131224
131231
  39: {
131225
131232
  apple: "no",
131226
131233
  google: "no",
@@ -132058,6 +132065,12 @@ var init_bridge_config_schema = __esm({
132058
132065
  description: "Anchor each device to its Home Assistant entity registry id instead of the entity id, so renaming an entity no longer re-adds it in your controller (Alexa, Google Home, Apple Home) and keeps groups and automations. Records are seeded from the start, so enabling this later is safe and never re-adds existing devices. Default off.",
132059
132066
  type: "boolean",
132060
132067
  default: false
132068
+ },
132069
+ wedgeWatchdog: {
132070
+ title: "Wedge Watchdog (Apple 'Updating' workaround)",
132071
+ description: "Rotate the one session that looks wedged, subscriptions still alive but no inbound request from the controller for about 45 minutes, earlier than the blind session rotation. Targets Apple Home tiles stuck on 'Updating' where the controller keeps acking but stops consuming data. A false positive only triggers a transparent reconnect, the same as normal rotation. Default off.",
132072
+ type: "boolean",
132073
+ default: false
132061
132074
  }
132062
132075
  }
132063
132076
  };
@@ -132618,10 +132631,10 @@ var init_home_assistant_actions = __esm({
132618
132631
  circuitBreakerResetMs: 3e4
132619
132632
  };
132620
132633
  HomeAssistantActions = class extends Service {
132621
- constructor(logger247, client, config11) {
132634
+ constructor(logger251, client, config11) {
132622
132635
  super("HomeAssistantActions");
132623
132636
  this.client = client;
132624
- this.log = logger247.get(this);
132637
+ this.log = logger251.get(this);
132625
132638
  this.config = { ...defaultConfig, ...config11 };
132626
132639
  this.circuitBreaker = new CircuitBreaker(
132627
132640
  this.config.circuitBreakerThreshold,
@@ -132967,10 +132980,10 @@ var DiagnosticService = class {
132967
132980
  };
132968
132981
 
132969
132982
  // src/api/access-log.ts
132970
- function accessLogger(logger247) {
132983
+ function accessLogger(logger251) {
132971
132984
  return (req, res, next) => {
132972
132985
  res.on("finish", () => {
132973
- logger247.debug(
132986
+ logger251.debug(
132974
132987
  `${req.method} ${decodeURI(req.originalUrl)} ${res.statusCode} ${res.statusMessage} from ${req.socket.remoteAddress}`
132975
132988
  );
132976
132989
  });
@@ -133155,6 +133168,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133155
133168
  currentEntity: config11.currentEntity,
133156
133169
  batteryPowerEntity: config11.batteryPowerEntity,
133157
133170
  batteryEnergyEntity: config11.batteryEnergyEntity,
133171
+ chargingSwitchEntity: config11.chargingSwitchEntity,
133172
+ currentLimitEntity: config11.currentLimitEntity,
133158
133173
  suctionLevelEntity: config11.suctionLevelEntity,
133159
133174
  mopIntensityEntity: config11.mopIntensityEntity,
133160
133175
  valetudoIdentifier: config11.valetudoIdentifier,
@@ -133316,6 +133331,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133316
133331
  currentEntity: config11.currentEntity,
133317
133332
  batteryPowerEntity: config11.batteryPowerEntity,
133318
133333
  batteryEnergyEntity: config11.batteryEnergyEntity,
133334
+ chargingSwitchEntity: config11.chargingSwitchEntity,
133335
+ currentLimitEntity: config11.currentLimitEntity,
133319
133336
  suctionLevelEntity: config11.suctionLevelEntity,
133320
133337
  mopIntensityEntity: config11.mopIntensityEntity,
133321
133338
  temperatureEntity: config11.temperatureEntity,
@@ -134168,6 +134185,8 @@ function entityMappingApi(mappingStorage, identityStorage) {
134168
134185
  currentEntity: body.currentEntity,
134169
134186
  batteryPowerEntity: body.batteryPowerEntity,
134170
134187
  batteryEnergyEntity: body.batteryEnergyEntity,
134188
+ chargingSwitchEntity: body.chargingSwitchEntity,
134189
+ currentLimitEntity: body.currentLimitEntity,
134171
134190
  suctionLevelEntity: body.suctionLevelEntity,
134172
134191
  mopIntensityEntity: body.mopIntensityEntity,
134173
134192
  customServiceAreas: body.customServiceAreas,
@@ -134669,6 +134688,8 @@ function configToProfileEntry(config11) {
134669
134688
  currentEntity: config11.currentEntity,
134670
134689
  batteryPowerEntity: config11.batteryPowerEntity,
134671
134690
  batteryEnergyEntity: config11.batteryEnergyEntity,
134691
+ chargingSwitchEntity: config11.chargingSwitchEntity,
134692
+ currentLimitEntity: config11.currentLimitEntity,
134672
134693
  suctionLevelEntity: config11.suctionLevelEntity,
134673
134694
  mopIntensityEntity: config11.mopIntensityEntity,
134674
134695
  customServiceAreas: config11.customServiceAreas,
@@ -134808,6 +134829,8 @@ function mappingProfileApi(mappingStorage) {
134808
134829
  currentEntity: entry.currentEntity,
134809
134830
  batteryPowerEntity: entry.batteryPowerEntity,
134810
134831
  batteryEnergyEntity: entry.batteryEnergyEntity,
134832
+ chargingSwitchEntity: entry.chargingSwitchEntity,
134833
+ currentLimitEntity: entry.currentLimitEntity,
134811
134834
  suctionLevelEntity: entry.suctionLevelEntity,
134812
134835
  mopIntensityEntity: entry.mopIntensityEntity,
134813
134836
  customServiceAreas: entry.customServiceAreas,
@@ -135034,6 +135057,300 @@ function resolveLabelValue(value, labels) {
135034
135057
  return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
135035
135058
  }
135036
135059
 
135060
+ // src/matter/endpoints/entity-endpoint.ts
135061
+ init_esm7();
135062
+ var EntityEndpoint = class extends Endpoint {
135063
+ constructor(type, entityId, customName, mappedEntityIds, endpointId) {
135064
+ super(type, { id: endpointId ?? createEndpointId(entityId, customName) });
135065
+ this.entityId = entityId;
135066
+ this.mappedEntityIds = mappedEntityIds ?? [];
135067
+ }
135068
+ entityId;
135069
+ mappedEntityIds;
135070
+ lastMappedStates = {};
135071
+ hasMappedEntityChanged(states) {
135072
+ let changed = false;
135073
+ for (const mappedId of this.mappedEntityIds) {
135074
+ const mappedState = states[mappedId];
135075
+ if (!mappedState) continue;
135076
+ const fp = mappedState.state;
135077
+ if (fp !== this.lastMappedStates[mappedId]) {
135078
+ this.lastMappedStates[mappedId] = fp;
135079
+ changed = true;
135080
+ }
135081
+ }
135082
+ return changed;
135083
+ }
135084
+ };
135085
+ function createEndpointId(entityId, customName) {
135086
+ const baseName = customName || entityId;
135087
+ return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
135088
+ }
135089
+ function getMappedEntityIds(mapping) {
135090
+ if (!mapping) return [];
135091
+ const ids = [];
135092
+ if (mapping.batteryEntity && !mapping.disableBatteryMapping) {
135093
+ ids.push(mapping.batteryEntity);
135094
+ }
135095
+ if (mapping.faultEntity) ids.push(mapping.faultEntity);
135096
+ if (mapping.chargingStateEntity) ids.push(mapping.chargingStateEntity);
135097
+ if (mapping.temperatureEntity) ids.push(mapping.temperatureEntity);
135098
+ if (mapping.humidityEntity) ids.push(mapping.humidityEntity);
135099
+ if (mapping.pressureEntity) ids.push(mapping.pressureEntity);
135100
+ if (mapping.cleaningModeEntity) ids.push(mapping.cleaningModeEntity);
135101
+ if (mapping.suctionLevelEntity) ids.push(mapping.suctionLevelEntity);
135102
+ if (mapping.mopIntensityEntity) ids.push(mapping.mopIntensityEntity);
135103
+ if (mapping.filterLifeEntity) ids.push(mapping.filterLifeEntity);
135104
+ if (mapping.powerEntity) ids.push(mapping.powerEntity);
135105
+ if (mapping.energyEntity) ids.push(mapping.energyEntity);
135106
+ if (mapping.voltageEntity) ids.push(mapping.voltageEntity);
135107
+ if (mapping.currentEntity) ids.push(mapping.currentEntity);
135108
+ if (mapping.batteryPowerEntity) ids.push(mapping.batteryPowerEntity);
135109
+ if (mapping.batteryEnergyEntity) ids.push(mapping.batteryEnergyEntity);
135110
+ if (mapping.chargingSwitchEntity) ids.push(mapping.chargingSwitchEntity);
135111
+ if (mapping.currentLimitEntity) ids.push(mapping.currentLimitEntity);
135112
+ if (mapping.currentRoomEntity) ids.push(mapping.currentRoomEntity);
135113
+ if (mapping.cleanedAreaEntity) ids.push(mapping.cleanedAreaEntity);
135114
+ if (mapping.composedEntities) {
135115
+ for (const sub of mapping.composedEntities) {
135116
+ if (sub.entityId) ids.push(sub.entityId);
135117
+ }
135118
+ }
135119
+ return ids;
135120
+ }
135121
+
135122
+ // src/services/bridges/identity-resolver.ts
135123
+ var SEP = "\0";
135124
+ function identityKey(entity) {
135125
+ const uniqueId = entity.registry?.unique_id;
135126
+ const platform = entity.registry?.platform;
135127
+ if (!uniqueId || !platform) {
135128
+ return null;
135129
+ }
135130
+ const domain = entity.entity_id.split(".")[0];
135131
+ return platform + SEP + domain + SEP + uniqueId;
135132
+ }
135133
+ var IdentityResolver = class {
135134
+ constructor(identityStorage, mappingStorage) {
135135
+ this.identityStorage = identityStorage;
135136
+ this.mappingStorage = mappingStorage;
135137
+ }
135138
+ identityStorage;
135139
+ mappingStorage;
135140
+ async resolveIdentity(bridgeId, entity, mapping, opts) {
135141
+ const entityId = entity.entity_id;
135142
+ const key = identityKey(entity);
135143
+ if (key == null) {
135144
+ return {
135145
+ endpointId: createEndpointId(entityId, mapping?.customName),
135146
+ anchorEntityId: entityId,
135147
+ protected: false
135148
+ };
135149
+ }
135150
+ const existing = this.identityStorage.getIdentity(bridgeId, key);
135151
+ if (existing) {
135152
+ const renamedFrom = existing.lastEntityId !== entityId ? existing.lastEntityId ?? existing.anchorEntityId : void 0;
135153
+ if (opts.stableIdentity) {
135154
+ if (renamedFrom) {
135155
+ this.identityStorage.setIdentity(bridgeId, key, {
135156
+ ...existing,
135157
+ lastEntityId: entityId
135158
+ });
135159
+ await this.rekeyMapping(bridgeId, renamedFrom, entityId);
135160
+ }
135161
+ return {
135162
+ endpointId: existing.endpointId,
135163
+ anchorEntityId: existing.anchorEntityId,
135164
+ protected: true,
135165
+ renamedFrom
135166
+ };
135167
+ }
135168
+ const liveEndpointId = createEndpointId(entityId, mapping?.customName);
135169
+ if (existing.endpointId !== liveEndpointId || existing.anchorEntityId !== entityId || existing.lastEntityId !== entityId) {
135170
+ this.identityStorage.setIdentity(bridgeId, key, {
135171
+ ...existing,
135172
+ endpointId: liveEndpointId,
135173
+ anchorEntityId: entityId,
135174
+ lastEntityId: entityId
135175
+ });
135176
+ }
135177
+ return {
135178
+ endpointId: liveEndpointId,
135179
+ anchorEntityId: entityId,
135180
+ protected: false,
135181
+ renamedFrom
135182
+ };
135183
+ }
135184
+ const desired = createEndpointId(entityId, mapping?.customName);
135185
+ let endpointId = desired;
135186
+ if (opts.stableIdentity) {
135187
+ let n = 2;
135188
+ while (opts.isEndpointIdTaken(endpointId, key)) {
135189
+ endpointId = `${desired}_${n++}`;
135190
+ }
135191
+ }
135192
+ const record = {
135193
+ endpointId,
135194
+ anchorEntityId: entityId,
135195
+ lastEntityId: entityId,
135196
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
135197
+ };
135198
+ this.identityStorage.setIdentity(bridgeId, key, record);
135199
+ if (opts.stableIdentity) {
135200
+ return {
135201
+ endpointId: record.endpointId,
135202
+ anchorEntityId: record.anchorEntityId,
135203
+ protected: true
135204
+ };
135205
+ }
135206
+ return {
135207
+ endpointId: desired,
135208
+ anchorEntityId: entityId,
135209
+ protected: false
135210
+ };
135211
+ }
135212
+ // Carry a mapping from the old entity_id to the new one on rename. anchorEntityId
135213
+ // never changes, only the mapping key. Skips when no old mapping exists or a new
135214
+ // one already does, so a manual mapping under the new id is never clobbered.
135215
+ async rekeyMapping(bridgeId, oldEntityId, newEntityId) {
135216
+ if (oldEntityId === newEntityId) {
135217
+ return;
135218
+ }
135219
+ const old = this.mappingStorage.getMapping(bridgeId, oldEntityId);
135220
+ if (!old) {
135221
+ return;
135222
+ }
135223
+ if (this.mappingStorage.getMapping(bridgeId, newEntityId)) {
135224
+ return;
135225
+ }
135226
+ await this.mappingStorage.setMapping({
135227
+ ...old,
135228
+ bridgeId,
135229
+ entityId: newEntityId
135230
+ });
135231
+ await this.mappingStorage.deleteMapping(bridgeId, oldEntityId);
135232
+ }
135233
+ };
135234
+
135235
+ // src/services/storage/orphan-cleanup.ts
135236
+ var ORPHAN_TOMBSTONE_MS = 7 * 24 * 60 * 60 * 1e3;
135237
+ function buildPresentIdentityKeys(entities) {
135238
+ const keys3 = /* @__PURE__ */ new Set();
135239
+ for (const entity of Object.values(entities ?? {})) {
135240
+ if (!entity?.entity_id) continue;
135241
+ const key = identityKey({ entity_id: entity.entity_id, registry: entity });
135242
+ if (key != null) keys3.add(key);
135243
+ }
135244
+ return keys3;
135245
+ }
135246
+ function buildPresentEntityIds(entities) {
135247
+ const ids = /* @__PURE__ */ new Set();
135248
+ for (const entity of Object.values(entities ?? {})) {
135249
+ if (entity?.entity_id) ids.add(entity.entity_id);
135250
+ }
135251
+ return ids;
135252
+ }
135253
+ function stampIdentityPresence(store, bridgeId, presentKeys, now = Date.now()) {
135254
+ const nowIso = new Date(now).toISOString();
135255
+ for (const key of [...store.getBridgeIdentities(bridgeId).keys()]) {
135256
+ if (presentKeys.has(key)) {
135257
+ store.clearIdentityMissing(bridgeId, key);
135258
+ } else {
135259
+ store.markIdentityMissing(bridgeId, key, nowIso);
135260
+ }
135261
+ }
135262
+ }
135263
+ function stampMappingPresence(store, bridgeId, presentEntityIds, now = Date.now()) {
135264
+ const nowIso = new Date(now).toISOString();
135265
+ const entityIds = store.getMappingsForBridge(bridgeId).map((m) => m.entityId);
135266
+ for (const entityId of entityIds) {
135267
+ if (presentEntityIds.has(entityId)) {
135268
+ store.clearMappingMissing(bridgeId, entityId);
135269
+ } else {
135270
+ store.markMappingMissing(bridgeId, entityId, nowIso);
135271
+ }
135272
+ }
135273
+ }
135274
+ function computeOrphanCandidates(input) {
135275
+ const now = input.now ?? Date.now();
135276
+ const candidates = [];
135277
+ for (const [key, record] of input.identities) {
135278
+ if (!record.missingSince) continue;
135279
+ const missingFor = now - Date.parse(record.missingSince);
135280
+ if (!(missingFor >= ORPHAN_TOMBSTONE_MS)) continue;
135281
+ if (input.presentKeys.has(key)) continue;
135282
+ const lastEntityId = record.lastEntityId ?? record.anchorEntityId;
135283
+ candidates.push({
135284
+ identityKey: key,
135285
+ lastEntityId,
135286
+ missingSince: record.missingSince,
135287
+ // Only a true orphan mapping, not one a live entity holds under a reused
135288
+ // entity_id (a different identity key). Guards the sweep from clobbering
135289
+ // that live entity's custom mapping.
135290
+ hasMapping: input.hasMapping(lastEntityId) && !input.presentEntityIds.has(lastEntityId),
135291
+ kind: "identity"
135292
+ });
135293
+ }
135294
+ if (input.mappings) {
135295
+ const linkedByIdentity = /* @__PURE__ */ new Set();
135296
+ for (const candidate of candidates) {
135297
+ if (candidate.hasMapping) linkedByIdentity.add(candidate.lastEntityId);
135298
+ }
135299
+ for (const mapping of input.mappings) {
135300
+ if (!mapping.missingSince) continue;
135301
+ const missingFor = now - Date.parse(mapping.missingSince);
135302
+ if (!(missingFor >= ORPHAN_TOMBSTONE_MS)) continue;
135303
+ if (input.presentEntityIds.has(mapping.entityId)) continue;
135304
+ if (linkedByIdentity.has(mapping.entityId)) continue;
135305
+ candidates.push({
135306
+ identityKey: mapping.entityId,
135307
+ lastEntityId: mapping.entityId,
135308
+ missingSince: mapping.missingSince,
135309
+ hasMapping: true,
135310
+ kind: "mapping"
135311
+ });
135312
+ }
135313
+ }
135314
+ return candidates;
135315
+ }
135316
+ async function executeOrphanCleanup(input) {
135317
+ const candidates = computeOrphanCandidates({
135318
+ bridgeId: input.bridgeId,
135319
+ identities: input.identities,
135320
+ presentKeys: input.presentKeys,
135321
+ presentEntityIds: input.presentEntityIds,
135322
+ hasMapping: (entityId) => input.getMapping(input.bridgeId, entityId) != null,
135323
+ mappings: input.mappings,
135324
+ now: input.now
135325
+ });
135326
+ const byKey = new Map(candidates.map((c) => [c.identityKey, c]));
135327
+ const results = [];
135328
+ for (const key of input.requestedKeys) {
135329
+ const candidate = byKey.get(key);
135330
+ if (!candidate) {
135331
+ results.push({
135332
+ identityKey: key,
135333
+ deleted: false,
135334
+ reason: "not a current orphan candidate"
135335
+ });
135336
+ continue;
135337
+ }
135338
+ if (candidate.kind === "mapping") {
135339
+ if (!input.presentEntityIds.has(candidate.lastEntityId)) {
135340
+ await input.deleteMapping(input.bridgeId, candidate.lastEntityId);
135341
+ }
135342
+ results.push({ identityKey: key, deleted: true });
135343
+ continue;
135344
+ }
135345
+ if (candidate.hasMapping && !input.presentEntityIds.has(candidate.lastEntityId)) {
135346
+ await input.deleteMapping(input.bridgeId, candidate.lastEntityId);
135347
+ }
135348
+ await input.deleteIdentity(input.bridgeId, key);
135349
+ results.push({ identityKey: key, deleted: true });
135350
+ }
135351
+ return results;
135352
+ }
135353
+
135037
135354
  // src/utils/json/endpoint-to-json.ts
135038
135355
  function safeClone(value, depth = 0) {
135039
135356
  if (depth > 20 || value === null || value === void 0) return value;
@@ -135079,7 +135396,7 @@ function endpointToJson(endpoint, parentId) {
135079
135396
 
135080
135397
  // src/api/matter-api.ts
135081
135398
  var ajv = new Ajv();
135082
- function matterApi(bridgeService, haRegistry, identityStorage) {
135399
+ function matterApi(bridgeService, haRegistry, identityStorage, mappingStorage) {
135083
135400
  const router = express11.Router();
135084
135401
  router.get("/", (_, res) => {
135085
135402
  res.status(200).json({});
@@ -135182,6 +135499,64 @@ function matterApi(bridgeService, haRegistry, identityStorage) {
135182
135499
  });
135183
135500
  }
135184
135501
  });
135502
+ router.get("/bridges/:bridgeId/orphans", async (req, res) => {
135503
+ const bridgeId = req.params.bridgeId;
135504
+ const bridge = bridgeService.bridges.find((b) => b.id === bridgeId);
135505
+ if (!bridge) {
135506
+ res.status(404).send("Not Found");
135507
+ return;
135508
+ }
135509
+ if (!haRegistry || !identityStorage) {
135510
+ res.status(503).json({ error: "Orphan cleanup is not available" });
135511
+ return;
135512
+ }
135513
+ const presentKeys = buildPresentIdentityKeys(haRegistry.entities);
135514
+ const presentEntityIds = buildPresentEntityIds(haRegistry.entities);
135515
+ const candidates = computeOrphanCandidates({
135516
+ bridgeId,
135517
+ identities: identityStorage.getBridgeIdentities(bridgeId),
135518
+ presentKeys,
135519
+ presentEntityIds,
135520
+ hasMapping: (entityId) => mappingStorage?.getMapping(bridgeId, entityId) != null,
135521
+ mappings: mappingStorage?.getMappingsForBridge(bridgeId)
135522
+ });
135523
+ res.status(200).json({ candidates });
135524
+ });
135525
+ router.post(
135526
+ "/bridges/:bridgeId/actions/cleanup-orphans",
135527
+ async (req, res) => {
135528
+ const bridgeId = req.params.bridgeId;
135529
+ const bridge = bridgeService.bridges.find((b) => b.id === bridgeId);
135530
+ if (!bridge) {
135531
+ res.status(404).send("Not Found");
135532
+ return;
135533
+ }
135534
+ if (!haRegistry || !identityStorage || !mappingStorage) {
135535
+ res.status(503).json({ error: "Orphan cleanup is not available" });
135536
+ return;
135537
+ }
135538
+ const body = req.body;
135539
+ const requestedKeys = Array.isArray(body?.identityKeys) ? body.identityKeys.filter((k) => typeof k === "string") : [];
135540
+ try {
135541
+ const results = await executeOrphanCleanup({
135542
+ bridgeId,
135543
+ requestedKeys,
135544
+ identities: identityStorage.getBridgeIdentities(bridgeId),
135545
+ presentKeys: buildPresentIdentityKeys(haRegistry.entities),
135546
+ presentEntityIds: buildPresentEntityIds(haRegistry.entities),
135547
+ getMapping: (b, e) => mappingStorage.getMapping(b, e),
135548
+ deleteMapping: (b, e) => mappingStorage.deleteMapping(b, e),
135549
+ deleteIdentity: (b, k) => identityStorage.deleteIdentity(b, k),
135550
+ mappings: mappingStorage.getMappingsForBridge(bridgeId)
135551
+ });
135552
+ res.status(200).json({ results });
135553
+ } catch (e) {
135554
+ res.status(500).json({
135555
+ error: e instanceof Error ? e.message : "Unknown error"
135556
+ });
135557
+ }
135558
+ }
135559
+ );
135185
135560
  router.get("/bridges/:bridgeId/devices", async (req, res) => {
135186
135561
  const bridgeId = req.params.bridgeId;
135187
135562
  const bridge = bridgeService.bridges.find((b) => b.id === bridgeId);
@@ -136242,7 +136617,7 @@ var BLOCKED_PREFIXES = [
136242
136617
  function pluginApi(bridgeService, storageLocation) {
136243
136618
  const router = express14.Router();
136244
136619
  const installer = new PluginInstaller(storageLocation);
136245
- const registry2 = new PluginRegistry(storageLocation);
136620
+ const registry3 = new PluginRegistry(storageLocation);
136246
136621
  router.get("/", (_req, res) => {
136247
136622
  const result = [];
136248
136623
  for (const bridge of bridgeService.bridges) {
@@ -136327,7 +136702,7 @@ function pluginApi(bridgeService, storageLocation) {
136327
136702
  res.json({ success: true, pluginName, reset: true });
136328
136703
  });
136329
136704
  router.get("/installed", (_req, res) => {
136330
- const registered = registry2.getAll();
136705
+ const registered = registry3.getAll();
136331
136706
  const npmInstalled = installer.listInstalled();
136332
136707
  const result = registered.map((entry) => {
136333
136708
  const npm = npmInstalled.find((p) => p.name === entry.packageName);
@@ -136357,7 +136732,7 @@ function pluginApi(bridgeService, storageLocation) {
136357
136732
  });
136358
136733
  return;
136359
136734
  }
136360
- registry2.add(packageName, config11 ?? {});
136735
+ registry3.add(packageName, config11 ?? {});
136361
136736
  res.json({
136362
136737
  success: true,
136363
136738
  packageName,
@@ -136385,7 +136760,7 @@ function pluginApi(bridgeService, storageLocation) {
136385
136760
  });
136386
136761
  return;
136387
136762
  }
136388
- registry2.remove(packageName);
136763
+ registry3.remove(packageName);
136389
136764
  res.json({
136390
136765
  success: true,
136391
136766
  packageName,
@@ -136425,7 +136800,7 @@ function pluginApi(bridgeService, storageLocation) {
136425
136800
  });
136426
136801
  return;
136427
136802
  }
136428
- registry2.add(result.packageName, {});
136803
+ registry3.add(result.packageName, {});
136429
136804
  res.json({
136430
136805
  success: true,
136431
136806
  packageName: result.packageName,
@@ -136458,7 +136833,7 @@ function pluginApi(bridgeService, storageLocation) {
136458
136833
  });
136459
136834
  return;
136460
136835
  }
136461
- registry2.add(result.packageName, {});
136836
+ registry3.add(result.packageName, {});
136462
136837
  res.json({
136463
136838
  success: true,
136464
136839
  packageName: result.packageName,
@@ -137155,7 +137530,7 @@ var WebSocketApi = class {
137155
137530
 
137156
137531
  // src/api/web-api.ts
137157
137532
  var WebApi = class extends Service {
137158
- constructor(logger247, bridgeService, haClient, haRegistry, bridgeStorage, mappingStorage, identityStorage, lockCredentialStorage, settingsStorage, backupService, props) {
137533
+ constructor(logger251, bridgeService, haClient, haRegistry, bridgeStorage, mappingStorage, identityStorage, lockCredentialStorage, settingsStorage, backupService, props) {
137159
137534
  super("WebApi");
137160
137535
  this.bridgeService = bridgeService;
137161
137536
  this.haClient = haClient;
@@ -137167,8 +137542,8 @@ var WebApi = class extends Service {
137167
137542
  this.settingsStorage = settingsStorage;
137168
137543
  this.backupService = backupService;
137169
137544
  this.props = props;
137170
- this.logger = logger247;
137171
- this.log = logger247.get(this);
137545
+ this.logger = logger251;
137546
+ this.log = logger251.get(this);
137172
137547
  this.accessLogger = accessLogger(this.log.createChild("Access Log"));
137173
137548
  this.startTime = Date.now();
137174
137549
  this.wsApi = new WebSocketApi(
@@ -137201,7 +137576,12 @@ var WebApi = class extends Service {
137201
137576
  const api = express17.Router();
137202
137577
  api.use(express17.json()).use(nocache()).use(
137203
137578
  "/matter",
137204
- matterApi(this.bridgeService, this.haRegistry, this.identityStorage)
137579
+ matterApi(
137580
+ this.bridgeService,
137581
+ this.haRegistry,
137582
+ this.identityStorage,
137583
+ this.mappingStorage
137584
+ )
137205
137585
  ).use(
137206
137586
  "/health",
137207
137587
  healthApi(
@@ -138354,10 +138734,10 @@ function isTransientConnectError(reason) {
138354
138734
  return msg.includes("socket hang up") || msg.includes("tls") || msg.includes("TLS");
138355
138735
  }
138356
138736
  var HomeAssistantClient = class extends Service {
138357
- constructor(logger247, options) {
138737
+ constructor(logger251, options) {
138358
138738
  super("HomeAssistantClient");
138359
138739
  this.options = options;
138360
- this.log = logger247.get(this);
138740
+ this.log = logger251.get(this);
138361
138741
  }
138362
138742
  options;
138363
138743
  static Options = /* @__PURE__ */ Symbol.for("HomeAssistantClientProps");
@@ -139022,11 +139402,37 @@ var EntityIdentityStorage = class extends Service {
139022
139402
  this.identities.delete(bridgeId);
139023
139403
  await this.flush();
139024
139404
  }
139405
+ // Drop a single identity record. Used by the manual orphan cleanup after the
139406
+ // 7-day tombstone; flush immediately since it is a user-initiated deletion.
139407
+ async deleteIdentity(bridgeId, key) {
139408
+ const bridgeMap = this.identities.get(bridgeId);
139409
+ if (bridgeMap?.delete(key)) {
139410
+ await this.flush();
139411
+ }
139412
+ }
139413
+ // Stamp the tombstone the first time a record's entity is absent from HA. Keeps
139414
+ // the first-seen time on later passes and no-ops once stamped, so a steady
139415
+ // absence does not churn the debounced persist.
139416
+ markIdentityMissing(bridgeId, key, nowIso) {
139417
+ const record = this.identities.get(bridgeId)?.get(key);
139418
+ if (!record || record.missingSince != null) return;
139419
+ record.missingSince = nowIso;
139420
+ this.schedulePersist();
139421
+ }
139422
+ // Clear the tombstone when the entity is present again. No-ops when there is
139423
+ // nothing to clear, so an all-present bridge writes nothing on refresh.
139424
+ clearIdentityMissing(bridgeId, key) {
139425
+ const record = this.identities.get(bridgeId)?.get(key);
139426
+ if (!record || record.missingSince == null) return;
139427
+ delete record.missingSince;
139428
+ this.schedulePersist();
139429
+ }
139025
139430
  };
139026
139431
 
139027
139432
  // src/services/storage/entity-mapping-storage.ts
139028
139433
  init_service();
139029
139434
  var CURRENT_VERSION2 = 1;
139435
+ var PERSIST_DEBOUNCE_MS2 = 500;
139030
139436
  var EntityMappingStorage = class extends Service {
139031
139437
  constructor(appStorage) {
139032
139438
  super("EntityMappingStorage");
@@ -139035,10 +139441,14 @@ var EntityMappingStorage = class extends Service {
139035
139441
  appStorage;
139036
139442
  storage;
139037
139443
  mappings = /* @__PURE__ */ new Map();
139444
+ persistTimer = null;
139038
139445
  async initialize() {
139039
139446
  this.storage = this.appStorage.createContext("entity-mappings");
139040
139447
  await this.load();
139041
139448
  }
139449
+ async dispose() {
139450
+ await this.flush();
139451
+ }
139042
139452
  async load() {
139043
139453
  const stored = await this.storage.get("data", {
139044
139454
  version: CURRENT_VERSION2,
@@ -139073,6 +139483,10 @@ var EntityMappingStorage = class extends Service {
139073
139483
  }
139074
139484
  }
139075
139485
  async persist() {
139486
+ if (this.persistTimer) {
139487
+ clearTimeout(this.persistTimer);
139488
+ this.persistTimer = null;
139489
+ }
139076
139490
  const data = {
139077
139491
  version: CURRENT_VERSION2,
139078
139492
  mappings: {}
@@ -139082,6 +139496,17 @@ var EntityMappingStorage = class extends Service {
139082
139496
  }
139083
139497
  await this.storage.set("data", data);
139084
139498
  }
139499
+ schedulePersist() {
139500
+ if (this.persistTimer) return;
139501
+ this.persistTimer = setTimeout(() => {
139502
+ this.persistTimer = null;
139503
+ void this.persist();
139504
+ }, PERSIST_DEBOUNCE_MS2);
139505
+ }
139506
+ // Flush any pending debounced write, used on dispose and by tests.
139507
+ async flush() {
139508
+ await this.persist();
139509
+ }
139085
139510
  getMappingsForBridge(bridgeId) {
139086
139511
  const bridgeMap = this.mappings.get(bridgeId);
139087
139512
  return bridgeMap ? Array.from(bridgeMap.values()) : [];
@@ -139128,6 +139553,8 @@ var EntityMappingStorage = class extends Service {
139128
139553
  currentEntity: request.currentEntity?.trim() || void 0,
139129
139554
  batteryPowerEntity: request.batteryPowerEntity?.trim() || void 0,
139130
139555
  batteryEnergyEntity: request.batteryEnergyEntity?.trim() || void 0,
139556
+ chargingSwitchEntity: request.chargingSwitchEntity?.trim() || void 0,
139557
+ currentLimitEntity: request.currentLimitEntity?.trim() || void 0,
139131
139558
  pressureEntity: request.pressureEntity?.trim() || void 0,
139132
139559
  suctionLevelEntity: request.suctionLevelEntity?.trim() || void 0,
139133
139560
  mopIntensityEntity: request.mopIntensityEntity?.trim() || void 0,
@@ -139156,7 +139583,7 @@ var EntityMappingStorage = class extends Service {
139156
139583
  composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0,
139157
139584
  disableMomentaryFlip: request.disableMomentaryFlip || void 0
139158
139585
  };
139159
- if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.disableBatteryMapping && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.lockUsercodeService && config11.lockUsercodeSlot === void 0 && config11.lockPinMinLength === void 0 && config11.lockPinMaxLength === void 0 && !config11.powerEntity && !config11.energyEntity && !config11.voltageEntity && !config11.currentEntity && !config11.batteryPowerEntity && !config11.batteryEnergyEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && (!config11.fanWindPresets || (config11.fanWindPresets.natural?.length ?? 0) === 0 && (config11.fanWindPresets.sleep?.length ?? 0) === 0) && !config11.fanRestoreSpeedOnPowerOn && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.selectExposeAsSwitch && !config11.selectSwitchOnOption && !config11.selectSwitchOffOption && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0) && !config11.disableMomentaryFlip) {
139586
+ if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.disableBatteryMapping && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.lockUsercodeService && config11.lockUsercodeSlot === void 0 && config11.lockPinMinLength === void 0 && config11.lockPinMaxLength === void 0 && !config11.powerEntity && !config11.energyEntity && !config11.voltageEntity && !config11.currentEntity && !config11.batteryPowerEntity && !config11.batteryEnergyEntity && !config11.chargingSwitchEntity && !config11.currentLimitEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && (!config11.fanWindPresets || (config11.fanWindPresets.natural?.length ?? 0) === 0 && (config11.fanWindPresets.sleep?.length ?? 0) === 0) && !config11.fanRestoreSpeedOnPowerOn && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.selectExposeAsSwitch && !config11.selectSwitchOnOption && !config11.selectSwitchOffOption && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0) && !config11.disableMomentaryFlip) {
139160
139587
  bridgeMap.delete(request.entityId);
139161
139588
  } else {
139162
139589
  bridgeMap.set(request.entityId, config11);
@@ -139175,6 +139602,23 @@ var EntityMappingStorage = class extends Service {
139175
139602
  this.mappings.delete(bridgeId);
139176
139603
  await this.persist();
139177
139604
  }
139605
+ // Stamp the tombstone the first time a mapping's entity is absent from HA.
139606
+ // Keeps the first-seen time on later passes and no-ops once stamped, so a
139607
+ // steady absence does not churn the debounced persist.
139608
+ markMappingMissing(bridgeId, entityId, nowIso) {
139609
+ const record = this.mappings.get(bridgeId)?.get(entityId);
139610
+ if (!record || record.missingSince != null) return;
139611
+ record.missingSince = nowIso;
139612
+ this.schedulePersist();
139613
+ }
139614
+ // Clear the tombstone when the entity is present again. No-ops when there is
139615
+ // nothing to clear, so an all-present bridge writes nothing on refresh.
139616
+ clearMappingMissing(bridgeId, entityId) {
139617
+ const record = this.mappings.get(bridgeId)?.get(entityId);
139618
+ if (!record || record.missingSince == null) return;
139619
+ delete record.missingSince;
139620
+ this.schedulePersist();
139621
+ }
139178
139622
  };
139179
139623
  function sanitizeVendorId(value) {
139180
139624
  if (value === void 0 || value === null || value === "") {
@@ -149100,6 +149544,7 @@ var EnergyEvseDeviceDefinition = MutableEndpoint({
149100
149544
  )
149101
149545
  });
149102
149546
  Object.freeze(EnergyEvseDeviceDefinition);
149547
+ var EnergyEvseDevice = EnergyEvseDeviceDefinition;
149103
149548
 
149104
149549
  // ../../node_modules/.pnpm/@matter+node@0.17.7_patch_hash=34f8224b2964673272e9f22613c0cb1f8ec12758dd46b4fe303d42373451ad4c/node_modules/@matter/node/dist/esm/devices/extended-color-light.js
149105
149550
  init_IdentifyServer();
@@ -156079,8 +156524,8 @@ var PluginManager = class {
156079
156524
  this.storageDir = storageDir;
156080
156525
  this.homeAssistant = homeAssistant;
156081
156526
  }
156082
- setRegistry(registry2) {
156083
- this.registry = registry2;
156527
+ setRegistry(registry3) {
156528
+ this.registry = registry3;
156084
156529
  }
156085
156530
  /**
156086
156531
  * Load and register a built-in plugin instance.
@@ -156805,16 +157250,96 @@ function seedExistingSessionStarts(startedAt, sessions, now = Date.now()) {
156805
157250
  }
156806
157251
  }
156807
157252
 
157253
+ // src/services/bridges/subscription-summary.ts
157254
+ function readPaths(value) {
157255
+ return Array.isArray(value) ? value : null;
157256
+ }
157257
+ function unknownSummary(id) {
157258
+ return {
157259
+ id,
157260
+ attributePaths: 0,
157261
+ eventPaths: 0,
157262
+ scope: "unknown",
157263
+ endpointIds: []
157264
+ };
157265
+ }
157266
+ function summarizeSubscription(sub) {
157267
+ let id = null;
157268
+ let request;
157269
+ try {
157270
+ const s = sub;
157271
+ if (typeof s.subscriptionId === "number") {
157272
+ id = s.subscriptionId;
157273
+ }
157274
+ request = s.request;
157275
+ } catch {
157276
+ return unknownSummary(id);
157277
+ }
157278
+ if (request == null || typeof request !== "object") {
157279
+ return unknownSummary(id);
157280
+ }
157281
+ const req = request;
157282
+ const attrPaths = readPaths(req.attributeRequests);
157283
+ const eventPaths = readPaths(req.eventRequests);
157284
+ if (attrPaths === null && eventPaths === null) {
157285
+ return unknownSummary(id);
157286
+ }
157287
+ const all = [...attrPaths ?? [], ...eventPaths ?? []];
157288
+ let wildcard = false;
157289
+ const endpointIds = /* @__PURE__ */ new Set();
157290
+ for (const path14 of all) {
157291
+ const ep = path14?.endpointId;
157292
+ if (ep == null) {
157293
+ wildcard = true;
157294
+ } else if (typeof ep === "number" || typeof ep === "bigint") {
157295
+ endpointIds.add(Number(ep));
157296
+ }
157297
+ }
157298
+ return {
157299
+ id,
157300
+ attributePaths: attrPaths?.length ?? 0,
157301
+ eventPaths: eventPaths?.length ?? 0,
157302
+ scope: wildcard ? "wildcard" : "endpoint-specific",
157303
+ endpointIds: [...endpointIds].sort((a, b) => a - b)
157304
+ };
157305
+ }
157306
+ function summarizeSubscriptions(subscriptions) {
157307
+ const out = [];
157308
+ try {
157309
+ for (const sub of subscriptions) {
157310
+ out.push(summarizeSubscription(sub));
157311
+ }
157312
+ } catch {
157313
+ }
157314
+ return out;
157315
+ }
157316
+
157317
+ // src/services/bridges/wedge-watchdog.ts
157318
+ var WEDGE_WARMUP_MS = 15 * 60 * 1e3;
157319
+ var WEDGE_IM_SILENCE_MS = 45 * 60 * 1e3;
157320
+ var WEDGE_MIN_ROTATE_INTERVAL_MS = 60 * 60 * 1e3;
157321
+ function decideWedgeRotation(input) {
157322
+ const {
157323
+ subscriptionCount,
157324
+ sessionAgeMs,
157325
+ lastImRequestMsAgo,
157326
+ lastRotatedMsAgo
157327
+ } = input;
157328
+ const imSilenceMs = lastImRequestMsAgo == null ? sessionAgeMs : lastImRequestMsAgo;
157329
+ return subscriptionCount > 0 && sessionAgeMs > WEDGE_WARMUP_MS && imSilenceMs > WEDGE_IM_SILENCE_MS && (lastRotatedMsAgo == null || lastRotatedMsAgo > WEDGE_MIN_ROTATE_INTERVAL_MS);
157330
+ }
157331
+
156808
157332
  // src/services/bridges/bridge.ts
157333
+ var imWrapMarker = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
156809
157334
  var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
156810
157335
  var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS = 2500;
156811
157336
  var MDNS_ADDRESS_CHECK_INTERVAL_MS = 6e4;
156812
157337
  var Bridge = class {
156813
- constructor(env, logger247, dataProvider, endpointManager, serverOptions) {
157338
+ constructor(env, logger251, dataProvider, endpointManager, serverOptions) {
156814
157339
  this.dataProvider = dataProvider;
156815
157340
  this.endpointManager = endpointManager;
156816
157341
  this.serverOptions = serverOptions;
156817
- this.log = logger247.get(`Bridge / ${dataProvider.id}`);
157342
+ this.log = logger251.get(`Bridge / ${dataProvider.id}`);
156818
157343
  this.server = new BridgeServerNode(
156819
157344
  env,
156820
157345
  this.dataProvider,
@@ -156853,6 +157378,12 @@ var Bridge = class {
156853
157378
  sessionStartedAt = /* @__PURE__ */ new Map();
156854
157379
  rotationTimer = null;
156855
157380
  maxSessionAgeMs = 0;
157381
+ // Wedge watchdog: last inbound Interaction Model request time per session and
157382
+ // last time the watchdog rotated it, both keyed by the long-lived session
157383
+ // object so they clear when the session goes away.
157384
+ lastImRequestAt = /* @__PURE__ */ new WeakMap();
157385
+ wedgeLastRotatedAt = /* @__PURE__ */ new WeakMap();
157386
+ wedgeWatchdogTimer = null;
156856
157387
  // Watches the advertised interface addresses so a dynamic ISP IPv6 prefix
156857
157388
  // change forces a fresh operational announcement (#415).
156858
157389
  mdnsAddressTimer = null;
@@ -156899,6 +157430,7 @@ var Bridge = class {
156899
157430
  const sessionList = sessions.map((s) => {
156900
157431
  const subCount = s.subscriptions.size;
156901
157432
  totalSubscriptions += subCount;
157433
+ const subscriptions = summarizeSubscriptions(s.subscriptions);
156902
157434
  const fi = typeof s.fabric?.fabricIndex === "number" ? s.fabric.fabricIndex : null;
156903
157435
  if (fi !== null) {
156904
157436
  const existing = fabricMap.get(fi) ?? {
@@ -156912,14 +157444,18 @@ var Bridge = class {
156912
157444
  const nowMs = Date.now();
156913
157445
  const lastActiveMsAgo = typeof s.activeTimestamp === "number" && s.activeTimestamp > 0 ? nowMs - s.activeTimestamp : null;
156914
157446
  const lastAnyActivityMsAgo = typeof s.timestamp === "number" ? nowMs - s.timestamp : null;
157447
+ const lastImAt = this.lastImRequestAt.get(s);
157448
+ const lastImRequestMsAgo = lastImAt != null ? nowMs - lastImAt : null;
156915
157449
  const startedAt = this.sessionStartedAt.get(s.id);
156916
157450
  return {
156917
157451
  id: s.id,
156918
157452
  peerNodeId: String(s.peerNodeId),
156919
157453
  fabricIndex: fi,
156920
157454
  subscriptionCount: subCount,
157455
+ subscriptions,
156921
157456
  lastActiveMsAgo,
156922
157457
  lastAnyActivityMsAgo,
157458
+ lastImRequestMsAgo,
156923
157459
  isPeerActive: Boolean(s.isPeerActive),
156924
157460
  ageMsFromOpen: startedAt != null ? nowMs - startedAt : null
156925
157461
  };
@@ -157086,6 +157622,7 @@ var Bridge = class {
157086
157622
  this.wireSessionDiagnostics();
157087
157623
  this.wireFabricWarnings();
157088
157624
  this.startSessionRotation();
157625
+ this.startWedgeWatchdog();
157089
157626
  this.startMdnsAddressWatch();
157090
157627
  logMemoryUsage(this.log, "bridge running");
157091
157628
  diagnosticEventBus.emit("bridge_started", `Bridge started`, {
@@ -157285,6 +157822,29 @@ ${e?.toString()}`);
157285
157822
  };
157286
157823
  sessionManager.sessions.added.on(this.sessionAddedHandler);
157287
157824
  sessionManager.sessions.deleted.on(this.sessionDeletedHandler);
157825
+ this.wireImRequestTracking();
157826
+ } catch {
157827
+ }
157828
+ }
157829
+ // Stamp the time of every inbound Interaction Model request per session by
157830
+ // wrapping InteractionServer.onNewExchange. The wedge watchdog reads these to
157831
+ // tell a live-but-consuming controller from one that only keeps acking.
157832
+ wireImRequestTracking() {
157833
+ try {
157834
+ const is = this.server.env.get(InteractionServer);
157835
+ const marked = is;
157836
+ if (marked[imWrapMarker]) {
157837
+ return;
157838
+ }
157839
+ const original = is.onNewExchange.bind(is);
157840
+ is.onNewExchange = (exchange, message) => {
157841
+ const session = exchange.session;
157842
+ if (session) {
157843
+ this.lastImRequestAt.set(session, Date.now());
157844
+ }
157845
+ return original(exchange, message);
157846
+ };
157847
+ marked[imWrapMarker] = true;
157288
157848
  } catch {
157289
157849
  }
157290
157850
  }
@@ -157447,6 +158007,7 @@ ${e?.toString()}`);
157447
158007
  }
157448
158008
  this.staleSessionTimers.clear();
157449
158009
  this.stopSessionRotation();
158010
+ this.stopWedgeWatchdog();
157450
158011
  this.stopMdnsAddressWatch();
157451
158012
  this.sessionStartedAt.clear();
157452
158013
  }
@@ -157512,6 +158073,72 @@ ${e?.toString()}`);
157512
158073
  this.rotationTimer = null;
157513
158074
  }
157514
158075
  }
158076
+ // Opt-in wedge watchdog: reuse the rotation check cadence (5min) to look for
158077
+ // the one session wedged on "Updating" and rotate just that one.
158078
+ startWedgeWatchdog() {
158079
+ this.stopWedgeWatchdog();
158080
+ if (!this.dataProvider.featureFlags?.wedgeWatchdog) {
158081
+ return;
158082
+ }
158083
+ this.wedgeWatchdogTimer = setInterval(
158084
+ () => this.runWedgeWatchdogCheck(),
158085
+ ROTATION_CHECK_INTERVAL_MS
158086
+ );
158087
+ this.log.info(
158088
+ `Wedge watchdog: checking every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
158089
+ );
158090
+ }
158091
+ stopWedgeWatchdog() {
158092
+ if (this.wedgeWatchdogTimer) {
158093
+ clearInterval(this.wedgeWatchdogTimer);
158094
+ this.wedgeWatchdogTimer = null;
158095
+ }
158096
+ }
158097
+ // Rotate exactly the sessions the pure rule flags as wedged. Closing is the
158098
+ // same graceful-then-force path age rotation uses, so a false positive just
158099
+ // re-CASEs the controller.
158100
+ runWedgeWatchdogCheck() {
158101
+ try {
158102
+ const sessionManager = this.server.env.get(SessionManager);
158103
+ const now = Date.now();
158104
+ const closes = [];
158105
+ for (const s of [...sessionManager.sessions]) {
158106
+ if (s.isClosing) continue;
158107
+ const startedAt = this.sessionStartedAt.get(s.id);
158108
+ const sessionAgeMs = startedAt != null ? now - startedAt : 0;
158109
+ const lastImAt = this.lastImRequestAt.get(s);
158110
+ const lastImRequestMsAgo = lastImAt != null ? now - lastImAt : null;
158111
+ const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
158112
+ const lastRotatedMsAgo = lastRotatedAt != null ? now - lastRotatedAt : null;
158113
+ if (!decideWedgeRotation({
158114
+ subscriptionCount: s.subscriptions.size,
158115
+ sessionAgeMs,
158116
+ lastImRequestMsAgo,
158117
+ lastRotatedMsAgo
158118
+ })) {
158119
+ continue;
158120
+ }
158121
+ const silenceMin = Math.round(
158122
+ (lastImRequestMsAgo ?? sessionAgeMs) / 6e4
158123
+ );
158124
+ this.log.info(
158125
+ `Wedge watchdog: rotating session ${s.id}, no inbound interaction for ${silenceMin}min`
158126
+ );
158127
+ this.wedgeLastRotatedAt.set(s, now);
158128
+ closes.push(
158129
+ s.initiateClose().catch(() => {
158130
+ return s.initiateForceClose({
158131
+ cause: new Error("wedge watchdog, forcing")
158132
+ });
158133
+ })
158134
+ );
158135
+ }
158136
+ if (closes.length > 0) {
158137
+ Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
158138
+ }
158139
+ } catch {
158140
+ }
158141
+ }
157515
158142
  // Poll the interface addresses mDNS advertises and re-announce when they
157516
158143
  // change. matter.js caches the operational records at first announcement, so
157517
158144
  // a dynamic ISP IPv6 prefix change keeps advertising the dead global address
@@ -157643,6 +158270,7 @@ ${e?.toString()}`);
157643
158270
  if (this.status.code === BridgeStatus.Running) {
157644
158271
  this.startAutoForceSyncIfEnabled();
157645
158272
  this.startSessionRotation();
158273
+ this.startWedgeWatchdog();
157646
158274
  }
157647
158275
  } catch (e) {
157648
158276
  const reason = "Failed to update bridge due to error:";
@@ -157765,66 +158393,6 @@ var AggregatorEndpoint2 = class extends Endpoint {
157765
158393
  }
157766
158394
  };
157767
158395
 
157768
- // src/matter/endpoints/entity-endpoint.ts
157769
- init_esm7();
157770
- var EntityEndpoint = class extends Endpoint {
157771
- constructor(type, entityId, customName, mappedEntityIds, endpointId) {
157772
- super(type, { id: endpointId ?? createEndpointId(entityId, customName) });
157773
- this.entityId = entityId;
157774
- this.mappedEntityIds = mappedEntityIds ?? [];
157775
- }
157776
- entityId;
157777
- mappedEntityIds;
157778
- lastMappedStates = {};
157779
- hasMappedEntityChanged(states) {
157780
- let changed = false;
157781
- for (const mappedId of this.mappedEntityIds) {
157782
- const mappedState = states[mappedId];
157783
- if (!mappedState) continue;
157784
- const fp = mappedState.state;
157785
- if (fp !== this.lastMappedStates[mappedId]) {
157786
- this.lastMappedStates[mappedId] = fp;
157787
- changed = true;
157788
- }
157789
- }
157790
- return changed;
157791
- }
157792
- };
157793
- function createEndpointId(entityId, customName) {
157794
- const baseName = customName || entityId;
157795
- return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
157796
- }
157797
- function getMappedEntityIds(mapping) {
157798
- if (!mapping) return [];
157799
- const ids = [];
157800
- if (mapping.batteryEntity && !mapping.disableBatteryMapping) {
157801
- ids.push(mapping.batteryEntity);
157802
- }
157803
- if (mapping.faultEntity) ids.push(mapping.faultEntity);
157804
- if (mapping.chargingStateEntity) ids.push(mapping.chargingStateEntity);
157805
- if (mapping.temperatureEntity) ids.push(mapping.temperatureEntity);
157806
- if (mapping.humidityEntity) ids.push(mapping.humidityEntity);
157807
- if (mapping.pressureEntity) ids.push(mapping.pressureEntity);
157808
- if (mapping.cleaningModeEntity) ids.push(mapping.cleaningModeEntity);
157809
- if (mapping.suctionLevelEntity) ids.push(mapping.suctionLevelEntity);
157810
- if (mapping.mopIntensityEntity) ids.push(mapping.mopIntensityEntity);
157811
- if (mapping.filterLifeEntity) ids.push(mapping.filterLifeEntity);
157812
- if (mapping.powerEntity) ids.push(mapping.powerEntity);
157813
- if (mapping.energyEntity) ids.push(mapping.energyEntity);
157814
- if (mapping.voltageEntity) ids.push(mapping.voltageEntity);
157815
- if (mapping.currentEntity) ids.push(mapping.currentEntity);
157816
- if (mapping.batteryPowerEntity) ids.push(mapping.batteryPowerEntity);
157817
- if (mapping.batteryEnergyEntity) ids.push(mapping.batteryEnergyEntity);
157818
- if (mapping.currentRoomEntity) ids.push(mapping.currentRoomEntity);
157819
- if (mapping.cleanedAreaEntity) ids.push(mapping.cleanedAreaEntity);
157820
- if (mapping.composedEntities) {
157821
- for (const sub of mapping.composedEntities) {
157822
- if (sub.entityId) ids.push(sub.entityId);
157823
- }
157824
- }
157825
- return ids;
157826
- }
157827
-
157828
158396
  // src/matter/endpoints/legacy/legacy-endpoint.ts
157829
158397
  init_dist();
157830
158398
  init_esm();
@@ -157903,9 +158471,9 @@ function resolveBatteryPercent(state) {
157903
158471
  return BATTERY_ENUM_PERCENT[state.toLowerCase()] ?? null;
157904
158472
  }
157905
158473
  var EntityStateProvider = class extends Service {
157906
- constructor(registry2) {
158474
+ constructor(registry3) {
157907
158475
  super("EntityStateProvider");
157908
- this.registry = registry2;
158476
+ this.registry = registry3;
157909
158477
  }
157910
158478
  registry;
157911
158479
  /**
@@ -159823,11 +160391,11 @@ function createEndpointId2(entityId, customName) {
159823
160391
  const baseName = customName || entityId;
159824
160392
  return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
159825
160393
  }
159826
- function buildEntityPayload(registry2, entityId) {
159827
- const state = registry2.initialState(entityId);
160394
+ function buildEntityPayload(registry3, entityId) {
160395
+ const state = registry3.initialState(entityId);
159828
160396
  if (!state) return void 0;
159829
- const entity = registry2.entity(entityId);
159830
- const deviceRegistry = registry2.deviceOf(entityId);
160397
+ const entity = registry3.entity(entityId);
160398
+ const deviceRegistry = registry3.deviceOf(entityId);
159831
160399
  return {
159832
160400
  entity_id: entityId,
159833
160401
  state,
@@ -159842,8 +160410,8 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
159842
160410
  lastStates = /* @__PURE__ */ new Map();
159843
160411
  debouncedUpdates = /* @__PURE__ */ new Map();
159844
160412
  static async create(config11) {
159845
- const { registry: registry2, primaryEntityId } = config11;
159846
- const primaryPayload = buildEntityPayload(registry2, primaryEntityId);
160413
+ const { registry: registry3, primaryEntityId } = config11;
160414
+ const primaryPayload = buildEntityPayload(registry3, primaryEntityId);
159847
160415
  if (!primaryPayload) return void 0;
159848
160416
  const airPurifierAttributes = primaryPayload.state.attributes;
159849
160417
  const supportedFeatures = airPurifierAttributes.supported_features ?? 0;
@@ -159931,7 +160499,7 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
159931
160499
  let tempSub;
159932
160500
  if (config11.temperatureEntityId) {
159933
160501
  const tempPayload = buildEntityPayload(
159934
- registry2,
160502
+ registry3,
159935
160503
  config11.temperatureEntityId
159936
160504
  );
159937
160505
  if (tempPayload) {
@@ -159946,7 +160514,7 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
159946
160514
  }
159947
160515
  let humSub;
159948
160516
  if (config11.humidityEntityId) {
159949
- const humPayload = buildEntityPayload(registry2, config11.humidityEntityId);
160517
+ const humPayload = buildEntityPayload(registry3, config11.humidityEntityId);
159950
160518
  if (humPayload) {
159951
160519
  humSub = new Endpoint(
159952
160520
  HumiditySubType.set({
@@ -161400,14 +161968,14 @@ function createEndpointId3(entityId, customName) {
161400
161968
  const baseName = customName || entityId;
161401
161969
  return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
161402
161970
  }
161403
- function buildEntityPayload2(registry2, entityId) {
161404
- const state = registry2.initialState(entityId);
161971
+ function buildEntityPayload2(registry3, entityId) {
161972
+ const state = registry3.initialState(entityId);
161405
161973
  if (!state) return void 0;
161406
161974
  return {
161407
161975
  entity_id: entityId,
161408
161976
  state,
161409
- registry: registry2.entity(entityId),
161410
- deviceRegistry: registry2.deviceOf(entityId)
161977
+ registry: registry3.entity(entityId),
161978
+ deviceRegistry: registry3.deviceOf(entityId)
161411
161979
  };
161412
161980
  }
161413
161981
  var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpoint {
@@ -161417,8 +161985,8 @@ var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpo
161417
161985
  lastStates = /* @__PURE__ */ new Map();
161418
161986
  debouncedUpdates = /* @__PURE__ */ new Map();
161419
161987
  static async create(config11) {
161420
- const { registry: registry2, primaryEntityId } = config11;
161421
- const payload = buildEntityPayload2(registry2, primaryEntityId);
161988
+ const { registry: registry3, primaryEntityId } = config11;
161989
+ const payload = buildEntityPayload2(registry3, primaryEntityId);
161422
161990
  if (!payload) return void 0;
161423
161991
  const endpointId = config11.endpointId ?? createEndpointId3(primaryEntityId, config11.customName);
161424
161992
  const mapping = config11.mapping ?? {
@@ -161676,11 +162244,11 @@ function createEndpointId4(entityId, customName) {
161676
162244
  const baseName = customName || entityId;
161677
162245
  return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
161678
162246
  }
161679
- function buildEntityPayload3(registry2, entityId) {
161680
- const state = registry2.initialStateIncludingUnfiltered(entityId);
162247
+ function buildEntityPayload3(registry3, entityId) {
162248
+ const state = registry3.initialStateIncludingUnfiltered(entityId);
161681
162249
  if (!state) return void 0;
161682
- const entity = registry2.entityIncludingUnfiltered(entityId);
161683
- const deviceRegistry = registry2.deviceOfIncludingUnfiltered(entityId);
162250
+ const entity = registry3.entityIncludingUnfiltered(entityId);
162251
+ const deviceRegistry = registry3.deviceOfIncludingUnfiltered(entityId);
161684
162252
  return {
161685
162253
  entity_id: entityId,
161686
162254
  state,
@@ -161695,8 +162263,8 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
161695
162263
  lastStates = /* @__PURE__ */ new Map();
161696
162264
  debouncedUpdates = /* @__PURE__ */ new Map();
161697
162265
  static async create(config11) {
161698
- const { registry: registry2, primaryEntityId } = config11;
161699
- const primaryPayload = buildEntityPayload3(registry2, primaryEntityId);
162266
+ const { registry: registry3, primaryEntityId } = config11;
162267
+ const primaryPayload = buildEntityPayload3(registry3, primaryEntityId);
161700
162268
  if (!primaryPayload) return void 0;
161701
162269
  let parentType = BridgedNodeEndpoint.with(
161702
162270
  BasicInformationServer2,
@@ -161740,7 +162308,7 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
161740
162308
  parts.push(tempSub);
161741
162309
  let humSub;
161742
162310
  if (config11.humidityEntityId) {
161743
- const humPayload = buildEntityPayload3(registry2, config11.humidityEntityId);
162311
+ const humPayload = buildEntityPayload3(registry3, config11.humidityEntityId);
161744
162312
  if (humPayload) {
161745
162313
  humSub = new Endpoint(
161746
162314
  HumiditySubType2.set({
@@ -161754,7 +162322,7 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
161754
162322
  let pressSub;
161755
162323
  if (config11.pressureEntityId) {
161756
162324
  const pressPayload = buildEntityPayload3(
161757
- registry2,
162325
+ registry3,
161758
162326
  config11.pressureEntityId
161759
162327
  );
161760
162328
  if (pressPayload) {
@@ -162708,6 +163276,15 @@ function clearPendingTilt(st) {
162708
163276
  st.pendingTilt = null;
162709
163277
  }
162710
163278
  var coverOptimistic = /* @__PURE__ */ new WeakMap();
163279
+ function haRestEnd(action) {
163280
+ if (action.action.includes("open_cover")) {
163281
+ return "open";
163282
+ }
163283
+ if (action.action.includes("close_cover")) {
163284
+ return "close";
163285
+ }
163286
+ return null;
163287
+ }
162711
163288
  function getCoverOptimistic(endpoint) {
162712
163289
  let st = coverOptimistic.get(endpoint);
162713
163290
  if (!st) {
@@ -162822,7 +163399,10 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
162822
163399
  if (!entry) {
162823
163400
  return MovementStatus.Stopped;
162824
163401
  }
162825
- const reachedTarget = positionAware && current100ths != null && existingTarget != null && Math.abs(current100ths - existingTarget) < 100;
163402
+ const getExpected = config11.getExpectedRestPosition;
163403
+ const expectedPercent = entry.expectedRestEnd != null && getExpected != null ? getExpected(entry.expectedRestEnd, this.agent) : null;
163404
+ const goal = expectedPercent != null ? expectedPercent * 100 : existingTarget;
163405
+ const reachedTarget = positionAware && current100ths != null && goal != null && Math.abs(current100ths - goal) < 100;
162826
163406
  const expired = Date.now() - entry.startedAt >= optimisticMovementTimeoutMs;
162827
163407
  if (reachedTarget || expired) {
162828
163408
  clearOptimisticAxis(optimistic, axis);
@@ -162927,7 +163507,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
162927
163507
  }
162928
163508
  // Record the optimistic direction and emit it now so a controller gets the
162929
163509
  // moving->stopped edge even when HA never reports a transitional state (#429).
162930
- startOptimisticMovement(axis, status3) {
163510
+ startOptimisticMovement(axis, status3, expectedRestEnd) {
162931
163511
  const optimistic = getCoverOptimistic(this.endpoint);
162932
163512
  clearOptimisticAxis(optimistic, axis);
162933
163513
  const endpoint = this.endpoint;
@@ -162972,7 +163552,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
162972
163552
  })();
162973
163553
  }, optimisticMovementTimeoutMs);
162974
163554
  timer.unref?.();
162975
- optimistic[axis] = { status: status3, startedAt, timer };
163555
+ optimistic[axis] = { status: status3, startedAt, timer, expectedRestEnd };
162976
163556
  this.writeOperationalStatus({ [axis]: status3 });
162977
163557
  }
162978
163558
  async handleMovement(type, _, direction, targetPercent100ths) {
@@ -163031,17 +163611,25 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
163031
163611
  }
163032
163612
  handleLiftOpen() {
163033
163613
  clearPendingLift(getCoverDebounce(this.endpoint));
163034
- this.startOptimisticMovement("lift", MovementStatus.Opening);
163035
163614
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
163036
163615
  const action = this.state.config.openCoverLift(void 0, this.agent);
163616
+ this.startOptimisticMovement(
163617
+ "lift",
163618
+ MovementStatus.Opening,
163619
+ haRestEnd(action)
163620
+ );
163037
163621
  logger218.info(`handleLiftOpen: calling action=${action.action}`);
163038
163622
  homeAssistant.callAction(action);
163039
163623
  }
163040
163624
  handleLiftClose() {
163041
163625
  clearPendingLift(getCoverDebounce(this.endpoint));
163042
- this.startOptimisticMovement("lift", MovementStatus.Closing);
163043
163626
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
163044
163627
  const action = this.state.config.closeCoverLift(void 0, this.agent);
163628
+ this.startOptimisticMovement(
163629
+ "lift",
163630
+ MovementStatus.Closing,
163631
+ haRestEnd(action)
163632
+ );
163045
163633
  logger218.info(`handleLiftClose: calling action=${action.action}`);
163046
163634
  homeAssistant.callAction(action);
163047
163635
  }
@@ -163052,14 +163640,15 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
163052
163640
  return;
163053
163641
  }
163054
163642
  this.state.targetPositionLiftPercent100ths = targetPercent100ths;
163055
- this.startOptimisticMovement(
163056
- "lift",
163057
- currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing
163058
- );
163059
163643
  const targetPosition = targetPercent100ths / 100;
163060
163644
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
163061
163645
  const action = config11.setLiftPosition(targetPosition, this.agent);
163062
163646
  const entityId = homeAssistant.entityId;
163647
+ this.startOptimisticMovement(
163648
+ "lift",
163649
+ currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing,
163650
+ haRestEnd(action)
163651
+ );
163063
163652
  const actions = this.env.get(HomeAssistantActions);
163064
163653
  const st = getCoverDebounce(this.endpoint);
163065
163654
  st.pendingLift = { action, entityId, actions };
@@ -163092,21 +163681,27 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
163092
163681
  const st = getCoverDebounce(this.endpoint);
163093
163682
  clearPendingTilt(st);
163094
163683
  if (st.pendingLift?.action.action.includes("tilt")) clearPendingLift(st);
163095
- this.startOptimisticMovement("tilt", MovementStatus.Opening);
163096
- const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
163097
- homeAssistant.callAction(
163098
- this.state.config.openCoverTilt(void 0, this.agent)
163684
+ const action = this.state.config.openCoverTilt(void 0, this.agent);
163685
+ this.startOptimisticMovement(
163686
+ "tilt",
163687
+ MovementStatus.Opening,
163688
+ haRestEnd(action)
163099
163689
  );
163690
+ const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
163691
+ homeAssistant.callAction(action);
163100
163692
  }
163101
163693
  handleTiltClose() {
163102
163694
  const st = getCoverDebounce(this.endpoint);
163103
163695
  clearPendingTilt(st);
163104
163696
  if (st.pendingLift?.action.action.includes("tilt")) clearPendingLift(st);
163105
- this.startOptimisticMovement("tilt", MovementStatus.Closing);
163106
- const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
163107
- homeAssistant.callAction(
163108
- this.state.config.closeCoverTilt(void 0, this.agent)
163697
+ const action = this.state.config.closeCoverTilt(void 0, this.agent);
163698
+ this.startOptimisticMovement(
163699
+ "tilt",
163700
+ MovementStatus.Closing,
163701
+ haRestEnd(action)
163109
163702
  );
163703
+ const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
163704
+ homeAssistant.callAction(action);
163110
163705
  }
163111
163706
  handleGoToTiltPosition(targetPercent100ths) {
163112
163707
  const config11 = this.state.config;
@@ -163115,14 +163710,15 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
163115
163710
  return;
163116
163711
  }
163117
163712
  this.state.targetPositionTiltPercent100ths = targetPercent100ths;
163118
- this.startOptimisticMovement(
163119
- "tilt",
163120
- currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing
163121
- );
163122
163713
  const targetPosition = targetPercent100ths / 100;
163123
163714
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
163124
163715
  const action = config11.setTiltPosition(targetPosition, this.agent);
163125
163716
  const entityId = homeAssistant.entityId;
163717
+ this.startOptimisticMovement(
163718
+ "tilt",
163719
+ currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing,
163720
+ haRestEnd(action)
163721
+ );
163126
163722
  const actions = this.env.get(HomeAssistantActions);
163127
163723
  const st = getCoverDebounce(this.endpoint);
163128
163724
  st.pendingTilt = { action, entityId, actions };
@@ -163264,6 +163860,7 @@ var adjustPositionForWriting2 = (position, agent) => {
163264
163860
  const matterSem = usesMatterSemantics(agent);
163265
163861
  return adjustPositionForWriting(position, featureFlags, matterSem);
163266
163862
  };
163863
+ var expectedRestPosition = (end, agent) => adjustPositionForReading2(end === "open" ? 100 : 0, agent);
163267
163864
  var shouldSwapOpenClose = (agent) => {
163268
163865
  const homeAssistant = agent.get(HomeAssistantEntityBehavior);
163269
163866
  const entitySwap = homeAssistant.state.mapping?.coverSwapOpenClose;
@@ -163351,11 +163948,11 @@ var config6 = {
163351
163948
  }
163352
163949
  return position == null ? null : adjustPositionForReading2(position, agent);
163353
163950
  },
163951
+ getExpectedRestPosition: expectedRestPosition,
163354
163952
  getCoverType: (entity) => deviceClassMapping(entity)?.type,
163355
163953
  getEndProductType: (entity) => deviceClassMapping(entity)?.endProductType,
163356
163954
  getMovementStatus: (entity, agent) => {
163357
- const { featureFlags } = agent.env.get(BridgeDataProvider);
163358
- const swapped = featureFlags?.coverSwapOpenClose === true;
163955
+ const swapped = shouldSwapOpenClose(agent);
163359
163956
  const coverState = entity.state;
163360
163957
  if (coverState === CoverDeviceState.opening) {
163361
163958
  return swapped ? WindowCovering3.MovementStatus.Closing : WindowCovering3.MovementStatus.Opening;
@@ -167397,6 +167994,284 @@ var ElectricalMeterType = ElectricalMeterDevice.with(
167397
167994
  EnergyServer
167398
167995
  );
167399
167996
 
167997
+ // src/matter/endpoints/legacy/sensor/devices/energy-evse.ts
167998
+ init_esm();
167999
+ init_home_assistant_actions();
168000
+ init_home_assistant_entity_behavior();
168001
+
168002
+ // src/matter/endpoints/legacy/sensor/devices/evse-status.ts
168003
+ function mapEvseStatus(rawState, chargingSwitchOn) {
168004
+ const raw = rawState.toLowerCase().trim();
168005
+ const switchSupply = chargingSwitchOn ? EnergyEvse3.SupplyState.ChargingEnabled : EnergyEvse3.SupplyState.Disabled;
168006
+ if (raw === "" || raw === "unknown" || raw === "unavailable") {
168007
+ return {
168008
+ state: null,
168009
+ supplyState: EnergyEvse3.SupplyState.Disabled,
168010
+ faultState: EnergyEvse3.FaultState.NoError
168011
+ };
168012
+ }
168013
+ if (raw.includes("error") || raw.includes("fault")) {
168014
+ return {
168015
+ state: EnergyEvse3.State.Fault,
168016
+ supplyState: EnergyEvse3.SupplyState.DisabledError,
168017
+ faultState: EnergyEvse3.FaultState.Other
168018
+ };
168019
+ }
168020
+ if (raw.includes("not connected") || raw.includes("not_connected") || raw.includes("disconnected") || raw.includes("no vehicle")) {
168021
+ return {
168022
+ state: EnergyEvse3.State.NotPluggedIn,
168023
+ supplyState: switchSupply,
168024
+ faultState: EnergyEvse3.FaultState.NoError
168025
+ };
168026
+ }
168027
+ if (raw.includes("not charging") || raw.includes("not_charging") || raw.includes("not-charging")) {
168028
+ return {
168029
+ state: EnergyEvse3.State.PluggedInNoDemand,
168030
+ supplyState: switchSupply,
168031
+ faultState: EnergyEvse3.FaultState.NoError
168032
+ };
168033
+ }
168034
+ if (raw.includes("charg") && !raw.includes("discharg")) {
168035
+ return {
168036
+ state: EnergyEvse3.State.PluggedInCharging,
168037
+ supplyState: EnergyEvse3.SupplyState.ChargingEnabled,
168038
+ faultState: EnergyEvse3.FaultState.NoError
168039
+ };
168040
+ }
168041
+ if (raw.includes("connected") || raw.includes("ready") || raw.includes("awaiting") || raw.includes("completed") || raw.includes("sleep") || raw.includes("paused")) {
168042
+ return {
168043
+ state: EnergyEvse3.State.PluggedInNoDemand,
168044
+ supplyState: switchSupply,
168045
+ faultState: EnergyEvse3.FaultState.NoError
168046
+ };
168047
+ }
168048
+ return {
168049
+ state: null,
168050
+ supplyState: EnergyEvse3.SupplyState.Disabled,
168051
+ faultState: EnergyEvse3.FaultState.NoError
168052
+ };
168053
+ }
168054
+
168055
+ // src/matter/endpoints/legacy/sensor/devices/energy-evse.ts
168056
+ var logger229 = Logger.get("EnergyEvse");
168057
+ var MIN_CHARGE_CURRENT_MA = 6e3;
168058
+ var MAX_CHARGE_CURRENT_MA = 32e3;
168059
+ var CIRCUIT_CAPACITY_MA = 32e3;
168060
+ var MAX_TIMER_MS = 2147483647;
168061
+ function clampMa(ma) {
168062
+ return Math.min(CIRCUIT_CAPACITY_MA, Math.max(MIN_CHARGE_CURRENT_MA, ma));
168063
+ }
168064
+ var evseChargingTimers = /* @__PURE__ */ new WeakMap();
168065
+ function clearEvseChargingTimer(endpoint) {
168066
+ const timer = evseChargingTimers.get(endpoint);
168067
+ if (timer) {
168068
+ clearTimeout(timer);
168069
+ evseChargingTimers.delete(endpoint);
168070
+ }
168071
+ }
168072
+ var EvseStatusServer = class _EvseStatusServer extends EnergyEvseServer {
168073
+ async initialize() {
168074
+ await super.initialize();
168075
+ const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
168076
+ this.update();
168077
+ this.reactTo(homeAssistant.onChange, this.update, { offline: true });
168078
+ }
168079
+ async [Symbol.asyncDispose]() {
168080
+ clearEvseChargingTimer(this.endpoint);
168081
+ await super[Symbol.asyncDispose]();
168082
+ }
168083
+ chargingSwitchIsOn() {
168084
+ const mapping = this.agent.get(HomeAssistantEntityBehavior).state.mapping;
168085
+ const switchEntity = mapping?.chargingSwitchEntity;
168086
+ if (!switchEntity) return false;
168087
+ const stateProvider = this.agent.env.get(EntityStateProvider);
168088
+ return stateProvider.getState(switchEntity)?.state === "on";
168089
+ }
168090
+ update() {
168091
+ const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
168092
+ const raw = homeAssistant.entity.state?.state ?? "";
168093
+ const status3 = mapEvseStatus(raw, this.chargingSwitchIsOn());
168094
+ const patch = {
168095
+ state: status3.state,
168096
+ supplyState: status3.supplyState,
168097
+ faultState: status3.faultState
168098
+ };
168099
+ const mapping = homeAssistant.state.mapping;
168100
+ if (mapping?.currentLimitEntity) {
168101
+ const stateProvider = this.agent.env.get(EntityStateProvider);
168102
+ const amps = stateProvider.getNumericState(mapping.currentLimitEntity);
168103
+ if (amps != null) {
168104
+ patch.maximumChargeCurrent = clampMa(Math.round(amps * 1e3));
168105
+ }
168106
+ }
168107
+ applyPatchState(this.state, patch);
168108
+ }
168109
+ enableCharging(request) {
168110
+ const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
168111
+ const mapping = homeAssistant.state.mapping;
168112
+ const until = request.chargingEnabledUntil;
168113
+ if (until != null && until * 1e3 <= Date.now()) {
168114
+ this.disable();
168115
+ return;
168116
+ }
168117
+ clearEvseChargingTimer(this.endpoint);
168118
+ let dispatched = false;
168119
+ if (mapping?.chargingSwitchEntity) {
168120
+ homeAssistant.callAction({
168121
+ action: "switch.turn_on",
168122
+ target: mapping.chargingSwitchEntity
168123
+ });
168124
+ dispatched = true;
168125
+ }
168126
+ const maxCurrent = request.maximumChargeCurrent;
168127
+ if (mapping?.currentLimitEntity && maxCurrent != null) {
168128
+ const amps = Math.max(
168129
+ MIN_CHARGE_CURRENT_MA / 1e3,
168130
+ Math.floor(clampMa(Number(maxCurrent)) / 1e3)
168131
+ );
168132
+ homeAssistant.callAction({
168133
+ action: "number.set_value",
168134
+ target: mapping.currentLimitEntity,
168135
+ data: { value: amps }
168136
+ });
168137
+ dispatched = true;
168138
+ }
168139
+ if (!dispatched) {
168140
+ logger229.debug(
168141
+ `enableCharging ignored for ${homeAssistant.entityId}: no charging switch or current limit mapped`
168142
+ );
168143
+ return;
168144
+ }
168145
+ const patch = {
168146
+ supplyState: EnergyEvse3.SupplyState.ChargingEnabled,
168147
+ chargingEnabledUntil: until ?? null
168148
+ };
168149
+ if (request.minimumChargeCurrent != null) {
168150
+ patch.minimumChargeCurrent = clampMa(
168151
+ Number(request.minimumChargeCurrent)
168152
+ );
168153
+ }
168154
+ if (maxCurrent != null) {
168155
+ patch.maximumChargeCurrent = clampMa(Number(maxCurrent));
168156
+ }
168157
+ applyPatchState(this.state, patch);
168158
+ if (until != null) {
168159
+ const delay = until * 1e3 - Date.now();
168160
+ if (delay > 0 && delay <= MAX_TIMER_MS) {
168161
+ const endpoint = this.endpoint;
168162
+ const actions = this.env.get(HomeAssistantActions);
168163
+ const entityId = homeAssistant.entityId;
168164
+ const switchEntity = mapping?.chargingSwitchEntity;
168165
+ const timer = setTimeout(() => {
168166
+ void (async () => {
168167
+ try {
168168
+ if (evseChargingTimers.get(endpoint) !== timer) return;
168169
+ if (switchEntity) {
168170
+ actions.call(
168171
+ { action: "switch.turn_off", target: switchEntity },
168172
+ entityId
168173
+ );
168174
+ }
168175
+ await endpoint.setStateOf(_EvseStatusServer, {
168176
+ supplyState: EnergyEvse3.SupplyState.Disabled,
168177
+ chargingEnabledUntil: null
168178
+ });
168179
+ } catch (error) {
168180
+ logger229.debug(
168181
+ `EVSE charge-window expiry write failed (endpoint may be closing): ${error}`
168182
+ );
168183
+ } finally {
168184
+ if (evseChargingTimers.get(endpoint) === timer) {
168185
+ evseChargingTimers.delete(endpoint);
168186
+ }
168187
+ }
168188
+ })();
168189
+ }, delay);
168190
+ timer.unref?.();
168191
+ evseChargingTimers.set(endpoint, timer);
168192
+ }
168193
+ }
168194
+ }
168195
+ disable() {
168196
+ clearEvseChargingTimer(this.endpoint);
168197
+ const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
168198
+ const mapping = homeAssistant.state.mapping;
168199
+ if (mapping?.chargingSwitchEntity) {
168200
+ homeAssistant.callAction({
168201
+ action: "switch.turn_off",
168202
+ target: mapping.chargingSwitchEntity
168203
+ });
168204
+ }
168205
+ if (mapping?.chargingSwitchEntity || mapping?.currentLimitEntity) {
168206
+ applyPatchState(this.state, {
168207
+ supplyState: EnergyEvse3.SupplyState.Disabled,
168208
+ chargingEnabledUntil: null
168209
+ });
168210
+ } else {
168211
+ logger229.debug(
168212
+ `disable ignored for ${homeAssistant.entityId}: no charging switch mapped`
168213
+ );
168214
+ }
168215
+ }
168216
+ };
168217
+ var EvseStatusSeeded = EvseStatusServer.set({
168218
+ supplyState: EnergyEvse3.SupplyState.Disabled,
168219
+ faultState: EnergyEvse3.FaultState.NoError,
168220
+ circuitCapacity: CIRCUIT_CAPACITY_MA,
168221
+ minimumChargeCurrent: MIN_CHARGE_CURRENT_MA,
168222
+ maximumChargeCurrent: MAX_CHARGE_CURRENT_MA
168223
+ });
168224
+ var EvseModeSeeded = EnergyEvseModeServer.set({
168225
+ supportedModes: [
168226
+ {
168227
+ label: "Manual",
168228
+ mode: 1,
168229
+ modeTags: [{ value: EnergyEvseMode3.ModeTag.Manual }]
168230
+ }
168231
+ ],
168232
+ currentMode: 1
168233
+ });
168234
+ var WiredPowerSourceBase = PowerSourceServer.with("Wired");
168235
+ var EvsePowerSourceServer = class extends WiredPowerSourceBase {
168236
+ async initialize() {
168237
+ await super.initialize();
168238
+ const endpointNumber = this.endpoint.number;
168239
+ if (endpointNumber != null) {
168240
+ applyPatchState(this.state, { endpointList: [endpointNumber] });
168241
+ }
168242
+ }
168243
+ };
168244
+ var EvsePowerSource = EvsePowerSourceServer.set({
168245
+ status: PowerSource3.PowerSourceStatus.Active,
168246
+ order: 0,
168247
+ description: "Mains",
168248
+ wiredCurrentType: PowerSource3.WiredCurrentType.Ac
168249
+ });
168250
+ var EnergyEvseBase = EnergyEvseDevice.with(
168251
+ BasicInformationServer2,
168252
+ IdentifyServer2,
168253
+ HomeAssistantEntityBehavior,
168254
+ EvseStatusSeeded,
168255
+ EvseModeSeeded,
168256
+ EvsePowerSource,
168257
+ // Always mount the measurement clusters; both seed safe defaults when no
168258
+ // power/energy entity is mapped, so the endpoint honestly carries the
168259
+ // ElectricalSensor device type.
168260
+ PowerServer,
168261
+ EnergyServer,
168262
+ DescriptorServer
168263
+ ).set({
168264
+ descriptor: {
168265
+ deviceTypeList: [
168266
+ { deviceType: DeviceTypeId(1292), revision: 2 },
168267
+ { deviceType: DeviceTypeId(1296), revision: 1 }
168268
+ ]
168269
+ }
168270
+ });
168271
+ function energyEvseType() {
168272
+ return EnergyEvseBase;
168273
+ }
168274
+
167400
168275
  // src/matter/behaviors/flow-measurement-server.ts
167401
168276
  init_home_assistant_entity_behavior();
167402
168277
  var FlowMeasurementServerBase = class extends FlowMeasurementServer {
@@ -168136,7 +169011,7 @@ var TvocConcentrationMeasurementServer = class extends TvocConcentrationMeasurem
168136
169011
  };
168137
169012
 
168138
169013
  // src/matter/endpoints/legacy/sensor/devices/tvoc-sensor.ts
168139
- var logger229 = Logger.get("TvocSensor");
169014
+ var logger230 = Logger.get("TvocSensor");
168140
169015
  function airQualityFromUgm3(value) {
168141
169016
  if (value <= 300) return AirQuality3.AirQualityEnum.Good;
168142
169017
  if (value <= 1e3) return AirQuality3.AirQualityEnum.Fair;
@@ -168177,17 +169052,17 @@ var TvocAirQualityServer = class extends TvocAirQualityServerBase {
168177
169052
  const attributes9 = entity.state.attributes;
168178
169053
  const deviceClass = attributes9.device_class;
168179
169054
  let airQuality = AirQuality3.AirQualityEnum.Unknown;
168180
- logger229.debug(
169055
+ logger230.debug(
168181
169056
  `[${entity.entity_id}] TVOC update: state="${state}", device_class="${deviceClass}"`
168182
169057
  );
168183
169058
  if (state != null && !Number.isNaN(+state)) {
168184
169059
  const value = +state;
168185
169060
  airQuality = deviceClass === SensorDeviceClass.volatile_organic_compounds ? airQualityFromUgm3(value) : airQualityFromPpb(value);
168186
- logger229.debug(
169061
+ logger230.debug(
168187
169062
  `[${entity.entity_id}] TVOC value=${value} (${deviceClass}) -> airQuality=${AirQuality3.AirQualityEnum[airQuality]}`
168188
169063
  );
168189
169064
  } else {
168190
- logger229.warn(
169065
+ logger230.warn(
168191
169066
  `[${entity.entity_id}] TVOC state not a valid number: "${state}"`
168192
169067
  );
168193
169068
  }
@@ -168401,7 +169276,7 @@ init_home_assistant_entity_behavior();
168401
169276
  // src/matter/behaviors/pm25-concentration-measurement-server.ts
168402
169277
  init_esm();
168403
169278
  init_home_assistant_entity_behavior();
168404
- var logger230 = Logger.get("Pm25ConcentrationMeasurementServer");
169279
+ var logger231 = Logger.get("Pm25ConcentrationMeasurementServer");
168405
169280
  var Pm25ConcentrationMeasurementServerBase = Pm25ConcentrationMeasurementServer.with(
168406
169281
  ConcentrationMeasurement3.Feature.NumericMeasurement
168407
169282
  );
@@ -168425,11 +169300,11 @@ var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasure
168425
169300
  if (this.state.measurementMedium === void 0) {
168426
169301
  this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
168427
169302
  }
168428
- logger230.debug(
169303
+ logger231.debug(
168429
169304
  "Pm25ConcentrationMeasurementServer: before super.initialize()"
168430
169305
  );
168431
169306
  await super.initialize();
168432
- logger230.debug(
169307
+ logger231.debug(
168433
169308
  "Pm25ConcentrationMeasurementServer: after super.initialize()"
168434
169309
  );
168435
169310
  const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
@@ -168452,7 +169327,7 @@ var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasure
168452
169327
  };
168453
169328
 
168454
169329
  // src/matter/endpoints/legacy/sensor/devices/pm25-sensor.ts
168455
- var logger231 = Logger.get("Pm25AirQualityServer");
169330
+ var logger232 = Logger.get("Pm25AirQualityServer");
168456
169331
  var Pm25AirQualityServerBase = AirQualityServer.with(
168457
169332
  AirQuality3.Feature.Fair,
168458
169333
  AirQuality3.Feature.Moderate,
@@ -168464,9 +169339,9 @@ var Pm25AirQualityServer = class extends Pm25AirQualityServerBase {
168464
169339
  if (this.state.airQuality === void 0) {
168465
169340
  this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
168466
169341
  }
168467
- logger231.debug("Pm25AirQualityServer: before super.initialize()");
169342
+ logger232.debug("Pm25AirQualityServer: before super.initialize()");
168468
169343
  await super.initialize();
168469
- logger231.debug("Pm25AirQualityServer: after super.initialize()");
169344
+ logger232.debug("Pm25AirQualityServer: after super.initialize()");
168470
169345
  const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
168471
169346
  this.update(homeAssistant.entity);
168472
169347
  this.reactTo(homeAssistant.onChange, this.update, { offline: true });
@@ -168891,7 +169766,7 @@ init_home_assistant_entity_behavior();
168891
169766
  init_dist();
168892
169767
  init_esm();
168893
169768
  init_home_assistant_entity_behavior();
168894
- var logger232 = Logger.get("VacuumIdentifyServer");
169769
+ var logger233 = Logger.get("VacuumIdentifyServer");
168895
169770
  var IDENTIFY_BUTTON_SUFFIXES = ["_identify", "_locate", "_find_me"];
168896
169771
  var VacuumIdentifyServer = class extends IdentifyServer2 {
168897
169772
  triggerEffect(effect) {
@@ -168908,19 +169783,19 @@ var VacuumIdentifyServer = class extends IdentifyServer2 {
168908
169783
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
168909
169784
  const features3 = homeAssistant.entity.state.attributes.supported_features ?? 0;
168910
169785
  if (testBit(features3, VacuumDeviceFeature.LOCATE)) {
168911
- logger232.info(`${source} \u2192 vacuum.locate for ${homeAssistant.entityId}`);
169786
+ logger233.info(`${source} \u2192 vacuum.locate for ${homeAssistant.entityId}`);
168912
169787
  homeAssistant.callAction({ action: "vacuum.locate" });
168913
169788
  return;
168914
169789
  }
168915
169790
  const sibling = this.#findIdentifyButton(homeAssistant);
168916
169791
  if (sibling) {
168917
- logger232.info(
169792
+ logger233.info(
168918
169793
  `${source} \u2192 button.press ${sibling} for ${homeAssistant.entityId}`
168919
169794
  );
168920
169795
  homeAssistant.callAction({ action: "button.press", target: sibling });
168921
169796
  return;
168922
169797
  }
168923
- logger232.warn(
169798
+ logger233.warn(
168924
169799
  `${source} for ${homeAssistant.entityId}, LOCATE not in supported_features (${features3}), trying vacuum.locate anyway`
168925
169800
  );
168926
169801
  homeAssistant.callAction({ action: "vacuum.locate" });
@@ -168928,8 +169803,8 @@ var VacuumIdentifyServer = class extends IdentifyServer2 {
168928
169803
  #findIdentifyButton(homeAssistant) {
168929
169804
  const deviceId = homeAssistant.entity.registry?.device_id;
168930
169805
  if (!deviceId) return void 0;
168931
- const registry2 = this.env.get(HomeAssistantRegistry);
168932
- for (const entity of Object.values(registry2.entities)) {
169806
+ const registry3 = this.env.get(HomeAssistantRegistry);
169807
+ for (const entity of Object.values(registry3.entities)) {
168933
169808
  if (entity.device_id !== deviceId) continue;
168934
169809
  if (!entity.entity_id.startsWith("button.")) continue;
168935
169810
  const uniqueId = entity.unique_id ?? "";
@@ -169119,14 +169994,14 @@ init_esm();
169119
169994
 
169120
169995
  // src/matter/behaviors/service-area-server.ts
169121
169996
  init_esm();
169122
- var logger233 = Logger.get("ServiceAreaServer");
169997
+ var logger234 = Logger.get("ServiceAreaServer");
169123
169998
  var ServiceAreaWithProgress = ServiceAreaBehavior.with(
169124
169999
  ServiceArea3.Feature.ProgressReporting
169125
170000
  );
169126
170001
  var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
169127
170002
  selectAreas(request) {
169128
170003
  const { newAreas } = request;
169129
- logger233.info(
170004
+ logger234.info(
169130
170005
  `ServiceArea selectAreas called with: ${JSON.stringify(newAreas)}`
169131
170006
  );
169132
170007
  const uniqueAreas = [...new Set(newAreas)];
@@ -169135,7 +170010,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
169135
170010
  (id) => !supportedAreaIds.includes(id)
169136
170011
  );
169137
170012
  if (invalidAreas.length > 0) {
169138
- logger233.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
170013
+ logger234.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
169139
170014
  return {
169140
170015
  status: ServiceArea3.SelectAreasStatus.UnsupportedArea,
169141
170016
  statusText: `Invalid area IDs: ${invalidAreas.join(", ")}`
@@ -169147,7 +170022,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
169147
170022
  status: ServiceArea3.OperationalStatus.Pending
169148
170023
  }));
169149
170024
  this.state.currentArea = null;
169150
- logger233.info(
170025
+ logger234.info(
169151
170026
  `ServiceArea: Stored ${uniqueAreas.length} areas for cleaning: ${uniqueAreas.join(", ")}`
169152
170027
  );
169153
170028
  return {
@@ -169168,7 +170043,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
169168
170043
  ServiceAreaServerBase2.State = State;
169169
170044
  })(ServiceAreaServerBase || (ServiceAreaServerBase = {}));
169170
170045
  function ServiceAreaServer2(initialState) {
169171
- logger233.info(
170046
+ logger234.info(
169172
170047
  `Creating ServiceAreaServer with ${initialState.supportedAreas.length} areas`
169173
170048
  );
169174
170049
  return ServiceAreaServerBase.set({
@@ -169185,7 +170060,7 @@ var ServiceAreaWithMapsAndProgress = ServiceAreaBehavior.with(
169185
170060
  var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress {
169186
170061
  selectAreas(request) {
169187
170062
  const { newAreas } = request;
169188
- logger233.info(
170063
+ logger234.info(
169189
170064
  `ServiceArea selectAreas called with: ${JSON.stringify(newAreas)}`
169190
170065
  );
169191
170066
  const uniqueAreas = [...new Set(newAreas)];
@@ -169194,7 +170069,7 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
169194
170069
  (id) => !supportedAreaIds.includes(id)
169195
170070
  );
169196
170071
  if (invalidAreas.length > 0) {
169197
- logger233.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
170072
+ logger234.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
169198
170073
  return {
169199
170074
  status: ServiceArea3.SelectAreasStatus.UnsupportedArea,
169200
170075
  statusText: `Invalid area IDs: ${invalidAreas.join(", ")}`
@@ -169206,7 +170081,7 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
169206
170081
  status: ServiceArea3.OperationalStatus.Pending
169207
170082
  }));
169208
170083
  this.state.currentArea = null;
169209
- logger233.info(
170084
+ logger234.info(
169210
170085
  `ServiceArea: Stored ${uniqueAreas.length} areas for cleaning: ${uniqueAreas.join(", ")}`
169211
170086
  );
169212
170087
  return {
@@ -169227,14 +170102,14 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
169227
170102
  ServiceAreaServerWithMapsBase2.State = State;
169228
170103
  })(ServiceAreaServerWithMapsBase || (ServiceAreaServerWithMapsBase = {}));
169229
170104
  function ServiceAreaServerWithMaps(initialState) {
169230
- logger233.info(
170105
+ logger234.info(
169231
170106
  `Creating ServiceAreaServer with Maps: ${initialState.supportedAreas.length} areas, ${initialState.supportedMaps.length} maps`
169232
170107
  );
169233
170108
  for (const map of initialState.supportedMaps) {
169234
170109
  const areaCount = initialState.supportedAreas.filter(
169235
170110
  (a) => a.mapId === map.mapId
169236
170111
  ).length;
169237
- logger233.info(` Map ${map.mapId}: "${map.name}" (${areaCount} areas)`);
170112
+ logger234.info(` Map ${map.mapId}: "${map.name}" (${areaCount} areas)`);
169238
170113
  }
169239
170114
  return ServiceAreaServerWithMapsBase.set({
169240
170115
  supportedAreas: initialState.supportedAreas,
@@ -169246,7 +170121,7 @@ function ServiceAreaServerWithMaps(initialState) {
169246
170121
  }
169247
170122
 
169248
170123
  // src/matter/endpoints/legacy/vacuum/behaviors/vacuum-service-area-server.ts
169249
- var logger234 = Logger.get("VacuumServiceAreaServer");
170124
+ var logger235 = Logger.get("VacuumServiceAreaServer");
169250
170125
  function toAreaId(roomId) {
169251
170126
  if (typeof roomId === "number") {
169252
170127
  return roomId;
@@ -169325,13 +170200,13 @@ function createVacuumServiceAreaServer(attributes9, roomEntities, includeUnnamed
169325
170200
  let rooms;
169326
170201
  if (roomEntities && roomEntities.length > 0) {
169327
170202
  rooms = buttonEntitiesToRooms(roomEntities, attributes9);
169328
- logger234.info(
170203
+ logger235.info(
169329
170204
  `Using ${rooms.length} button entities as rooms: ${rooms.map((r) => r.name).join(", ")}`
169330
170205
  );
169331
170206
  } else {
169332
170207
  rooms = parseVacuumRooms(attributes9, includeUnnamedRooms);
169333
170208
  if (rooms.length > 0) {
169334
- logger234.info(
170209
+ logger235.info(
169335
170210
  `Using ${rooms.length} rooms from attributes: ${rooms.map((r) => r.name).join(", ")}`
169336
170211
  );
169337
170212
  }
@@ -169385,7 +170260,7 @@ function createCustomServiceAreaServer(customAreas) {
169385
170260
  landmarkInfo: null
169386
170261
  }
169387
170262
  }));
169388
- logger234.info(
170263
+ logger235.info(
169389
170264
  `Using ${customAreas.length} custom service areas: ${customAreas.map((a) => a.name).join(", ")}`
169390
170265
  );
169391
170266
  return ServiceAreaServer2({
@@ -169407,7 +170282,7 @@ function createCleanAreaServiceAreaServer(cleanAreaRooms) {
169407
170282
  landmarkInfo: null
169408
170283
  }
169409
170284
  }));
169410
- logger234.info(
170285
+ logger235.info(
169411
170286
  `Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA: ${cleanAreaRooms.map((r) => r.name).join(", ")}`
169412
170287
  );
169413
170288
  return ServiceAreaServer2({
@@ -169418,11 +170293,11 @@ function createCleanAreaServiceAreaServer(cleanAreaRooms) {
169418
170293
  }
169419
170294
 
169420
170295
  // src/matter/endpoints/legacy/vacuum/behaviors/vacuum-rvc-run-mode-server.ts
169421
- var logger235 = Logger.get("VacuumRvcRunModeServer");
170296
+ var logger236 = Logger.get("VacuumRvcRunModeServer");
169422
170297
  function buildValetudoSegmentAction(vacuumEntityId, segmentIds, valetudoIdentifier) {
169423
170298
  const identifier = valetudoIdentifier || vacuumEntityId.replace(/^vacuum\.valetudo_/, "");
169424
170299
  const topic = `valetudo/${identifier}/MapSegmentationCapability/clean/set`;
169425
- logger235.info(
170300
+ logger236.info(
169426
170301
  `Valetudo: mqtt.publish to ${topic}, segments: ${segmentIds.join(", ")}`
169427
170302
  );
169428
170303
  return {
@@ -169527,14 +170402,14 @@ function mergeBatchData(areas) {
169527
170402
  function handleCustomServiceAreas(selectedAreas, customAreas, session) {
169528
170403
  const matched = selectedAreas.map((areaId) => ({ areaId, area: customAreas[areaId - 1] })).filter((m) => !!m.area);
169529
170404
  if (matched.length === 0) {
169530
- logger235.warn(
170405
+ logger236.warn(
169531
170406
  `Custom service areas: no match for selected IDs ${selectedAreas.join(", ")}`
169532
170407
  );
169533
170408
  return { action: "vacuum.start" };
169534
170409
  }
169535
170410
  const batchArea = matched.find(({ area }) => area.batchDispatch === true);
169536
170411
  if (batchArea) {
169537
- logger235.info(
170412
+ logger236.info(
169538
170413
  `Custom service areas (batch): single call for ${matched.length} room(s): ${matched.map(({ area }) => area.name).join(", ")}`
169539
170414
  );
169540
170415
  session.pendingDispatches = [];
@@ -169555,7 +170430,7 @@ function handleCustomServiceAreas(selectedAreas, customAreas, session) {
169555
170430
  }
169556
170431
  };
169557
170432
  }
169558
- logger235.info(
170433
+ logger236.info(
169559
170434
  `Custom service areas: ${matched.length} room(s) queued: ${matched.map(({ area }) => `${area.service} (${area.name})`).join(", ")}`
169560
170435
  );
169561
170436
  session.pendingDispatches = matched.slice(1).map(({ areaId, area }) => ({
@@ -169579,20 +170454,22 @@ function resolveCleanAreaIds(selectedAreas, cleanAreaRooms) {
169579
170454
  }
169580
170455
  return haAreaIds;
169581
170456
  }
170457
+ var cleaningStates = [
170458
+ VacuumState.cleaning,
170459
+ VacuumState.segment_cleaning,
170460
+ VacuumState.zone_cleaning,
170461
+ VacuumState.spot_cleaning,
170462
+ VacuumState.mop_cleaning,
170463
+ VacuumState.paused
170464
+ ];
170465
+ function vacuumIsCleaning(state) {
170466
+ return state != null && cleaningStates.includes(state);
170467
+ }
169582
170468
  var vacuumRvcRunModeConfig = {
169583
170469
  getCurrentMode: (entity) => {
169584
- const state = entity.state;
169585
- const cleaningStates = [
169586
- VacuumState.cleaning,
169587
- VacuumState.segment_cleaning,
169588
- VacuumState.zone_cleaning,
169589
- VacuumState.spot_cleaning,
169590
- VacuumState.mop_cleaning,
169591
- VacuumState.paused
169592
- ];
169593
- const isCleaning = cleaningStates.includes(state);
169594
- logger235.debug(
169595
- `Vacuum state: "${state}", isCleaning: ${isCleaning}, currentMode: ${isCleaning ? "Cleaning" : "Idle"}`
170470
+ const isCleaning = vacuumIsCleaning(entity.state);
170471
+ logger236.debug(
170472
+ `Vacuum state: "${entity.state}", isCleaning: ${isCleaning}, currentMode: ${isCleaning ? "Cleaning" : "Idle"}`
169596
170473
  );
169597
170474
  return isCleaning ? 1 /* Cleaning */ : 0 /* Idle */;
169598
170475
  },
@@ -169625,7 +170502,7 @@ var vacuumRvcRunModeConfig = {
169625
170502
  if (cleanAreaRooms && cleanAreaRooms.length > 0) {
169626
170503
  const haAreaIds = resolveCleanAreaIds(selectedAreas, cleanAreaRooms);
169627
170504
  if (haAreaIds.length > 0) {
169628
- logger235.info(
170505
+ logger236.info(
169629
170506
  `CLEAN_AREA: cleaning HA areas: ${haAreaIds.join(", ")}`
169630
170507
  );
169631
170508
  return {
@@ -169644,7 +170521,7 @@ var vacuumRvcRunModeConfig = {
169644
170521
  }
169645
170522
  }
169646
170523
  if (matched.length > 0) {
169647
- logger235.info(
170524
+ logger236.info(
169648
170525
  `Roborock: ${matched.length} room button(s) queued: ${matched.map((m) => m.entityId).join(", ")}`
169649
170526
  );
169650
170527
  session.pendingDispatches = matched.slice(1).map(({ areaId, entityId }) => ({
@@ -169678,14 +170555,14 @@ var vacuumRvcRunModeConfig = {
169678
170555
  }
169679
170556
  }
169680
170557
  if (roomIds.length > 0) {
169681
- logger235.info(
170558
+ logger236.info(
169682
170559
  `Starting cleaning with selected areas: ${roomIds.join(", ")}`
169683
170560
  );
169684
170561
  if (isDreameVacuum(attributes9)) {
169685
170562
  if (targetMapName) {
169686
170563
  const vacName = vacuumEntityId.replace("vacuum.", "");
169687
170564
  const selectedMapEntity = `select.${vacName}_selected_map`;
169688
- logger235.info(
170565
+ logger236.info(
169689
170566
  `Dreame multi-floor: switching to map "${targetMapName}" via ${selectedMapEntity}`
169690
170567
  );
169691
170568
  homeAssistant.callAction({
@@ -169712,7 +170589,7 @@ var vacuumRvcRunModeConfig = {
169712
170589
  }
169713
170590
  if (isEcovacsVacuum(attributes9)) {
169714
170591
  const roomIdStr = roomIds.join(",");
169715
- logger235.info(
170592
+ logger236.info(
169716
170593
  `Ecovacs vacuum: Using spot_area for rooms: ${roomIdStr}`
169717
170594
  );
169718
170595
  return {
@@ -169727,14 +170604,14 @@ var vacuumRvcRunModeConfig = {
169727
170604
  }
169728
170605
  };
169729
170606
  }
169730
- logger235.warn(
170607
+ logger236.warn(
169731
170608
  `Room cleaning via send_command not supported for this vacuum type. Rooms: ${roomIds.join(", ")}. Falling back to vacuum.start`
169732
170609
  );
169733
170610
  }
169734
170611
  }
169735
170612
  } catch {
169736
170613
  }
169737
- logger235.info("Starting regular cleaning (no areas selected)");
170614
+ logger236.info("Starting regular cleaning (no areas selected)");
169738
170615
  return { action: "vacuum.start" };
169739
170616
  },
169740
170617
  returnToBase: () => ({ action: "vacuum.return_to_base" }),
@@ -169749,7 +170626,7 @@ var vacuumRvcRunModeConfig = {
169749
170626
  const homeAssistant = agent.get(HomeAssistantEntityBehavior);
169750
170627
  const entity = homeAssistant.entity;
169751
170628
  const attributes9 = entity.state.attributes;
169752
- logger235.info(`cleanRoom called: roomMode=${roomMode}`);
170629
+ logger236.info(`cleanRoom called: roomMode=${roomMode}`);
169753
170630
  const cleanAreaRooms = homeAssistant.state.mapping?.cleanAreaRooms;
169754
170631
  if (cleanAreaRooms && cleanAreaRooms.length > 0) {
169755
170632
  const sorted = [...cleanAreaRooms].sort(
@@ -169758,7 +170635,7 @@ var vacuumRvcRunModeConfig = {
169758
170635
  const areaIndex = roomMode - ROOM_MODE_BASE2 - 1;
169759
170636
  if (areaIndex >= 0 && areaIndex < sorted.length) {
169760
170637
  const area = sorted[areaIndex];
169761
- logger235.info(
170638
+ logger236.info(
169762
170639
  `cleanRoom: CLEAN_AREA "${area.name}" \u2192 vacuum.clean_area(${area.haAreaId})`
169763
170640
  );
169764
170641
  return {
@@ -169775,7 +170652,7 @@ var vacuumRvcRunModeConfig = {
169775
170652
  const areaIndex = roomMode - ROOM_MODE_BASE2 - 1;
169776
170653
  if (areaIndex >= 0 && areaIndex < sorted.length) {
169777
170654
  const area = sorted[areaIndex];
169778
- logger235.info(
170655
+ logger236.info(
169779
170656
  `cleanRoom: custom service area "${area.name}" \u2192 ${area.service}`
169780
170657
  );
169781
170658
  return {
@@ -169796,7 +170673,7 @@ var vacuumRvcRunModeConfig = {
169796
170673
  }
169797
170674
  const rooms = parseVacuumRooms(attributes9);
169798
170675
  const numericIdFromMode = getRoomIdFromMode(roomMode);
169799
- logger235.info(
170676
+ logger236.info(
169800
170677
  `cleanRoom: numericIdFromMode=${numericIdFromMode}, available rooms: ${JSON.stringify(rooms.map((r) => ({ id: r.id, name: r.name, modeValue: getRoomModeValue(r) })))}`
169801
170678
  );
169802
170679
  const room = rooms.find((r) => getRoomModeValue(r) === roomMode);
@@ -169806,7 +170683,7 @@ var vacuumRvcRunModeConfig = {
169806
170683
  if (room.mapName) {
169807
170684
  const vacuumName = vacuumEntityId.replace("vacuum.", "");
169808
170685
  const selectedMapEntity = `select.${vacuumName}_selected_map`;
169809
- logger235.info(
170686
+ logger236.info(
169810
170687
  `Dreame multi-floor: switching to map "${room.mapName}" via ${selectedMapEntity}`
169811
170688
  );
169812
170689
  homeAssistant.callAction({
@@ -169815,7 +170692,7 @@ var vacuumRvcRunModeConfig = {
169815
170692
  data: { option: room.mapName }
169816
170693
  });
169817
170694
  }
169818
- logger235.debug(
170695
+ logger236.debug(
169819
170696
  `Dreame vacuum detected, using dreame_vacuum.vacuum_clean_segment for room ${room.name} (commandId: ${commandId3}, id: ${room.id})`
169820
170697
  );
169821
170698
  return {
@@ -169826,7 +170703,7 @@ var vacuumRvcRunModeConfig = {
169826
170703
  };
169827
170704
  }
169828
170705
  if (isRoborockVacuum(attributes9) || isXiaomiMiotVacuum(attributes9)) {
169829
- logger235.debug(
170706
+ logger236.debug(
169830
170707
  `Using vacuum.send_command with app_segment_clean for room ${room.name} (commandId: ${commandId3}, id: ${room.id})`
169831
170708
  );
169832
170709
  return {
@@ -169839,7 +170716,7 @@ var vacuumRvcRunModeConfig = {
169839
170716
  }
169840
170717
  if (isEcovacsVacuum(attributes9)) {
169841
170718
  const roomIdStr = String(commandId3);
169842
- logger235.info(
170719
+ logger236.info(
169843
170720
  `Ecovacs vacuum: Using spot_area for room ${room.name} (id: ${roomIdStr})`
169844
170721
  );
169845
170722
  return {
@@ -169854,7 +170731,7 @@ var vacuumRvcRunModeConfig = {
169854
170731
  }
169855
170732
  };
169856
170733
  }
169857
- logger235.warn(
170734
+ logger236.warn(
169858
170735
  `Room cleaning via send_command not supported for this vacuum type. Room: ${room.name} (id=${commandId3}). Falling back to vacuum.start`
169859
170736
  );
169860
170737
  }
@@ -169871,20 +170748,20 @@ function createVacuumRvcRunModeServer(attributes9, includeUnnamedRooms = false,
169871
170748
  customAreas,
169872
170749
  disableRoomModes
169873
170750
  );
169874
- logger235.info(
170751
+ logger236.info(
169875
170752
  `Creating VacuumRvcRunModeServer with ${rooms.length} rooms, ${supportedModes2.length} total modes`
169876
170753
  );
169877
170754
  if (rooms.length > 0) {
169878
- logger235.info(`Rooms found: ${rooms.map((r) => r.name).join(", ")}`);
170755
+ logger236.info(`Rooms found: ${rooms.map((r) => r.name).join(", ")}`);
169879
170756
  }
169880
170757
  if (filteredCount > 0) {
169881
170758
  const filtered = allRooms.filter((r) => !rooms.some((x) => x.id === r.id));
169882
- logger235.info(
170759
+ logger236.info(
169883
170760
  `Filtered out ${filteredCount} unnamed room(s): ${filtered.map((r) => r.name).join(", ")}`
169884
170761
  );
169885
170762
  }
169886
170763
  if (allRooms.length === 0) {
169887
- logger235.debug(
170764
+ logger236.debug(
169888
170765
  `No rooms found. Attributes: rooms=${JSON.stringify(attributes9.rooms)}, segments=${JSON.stringify(attributes9.segments)}, room_list=${attributes9.room_list}`
169889
170766
  );
169890
170767
  }
@@ -169918,7 +170795,7 @@ function createCleanAreaRvcRunModeServer(cleanAreaRooms) {
169918
170795
  modeTags: [{ value: RvcRunMode3.ModeTag.Cleaning }]
169919
170796
  });
169920
170797
  }
169921
- logger235.info(
170798
+ logger236.info(
169922
170799
  `Creating CLEAN_AREA RvcRunModeServer with ${cleanAreaRooms.length} HA areas, ${modes.length} total modes`
169923
170800
  );
169924
170801
  return RvcRunModeServer2(vacuumRvcRunModeConfig, {
@@ -169926,11 +170803,12 @@ function createCleanAreaRvcRunModeServer(cleanAreaRooms) {
169926
170803
  currentMode: 0 /* Idle */
169927
170804
  });
169928
170805
  }
169929
- var VacuumRvcRunModeServer = RvcRunModeServer2(vacuumRvcRunModeConfig);
169930
170806
 
169931
170807
  // src/matter/endpoints/legacy/vacuum/behaviors/vacuum-on-off-server.ts
169932
170808
  var VacuumOnOffServer = OnOffServer2({
169933
- isOn: (_, agent) => agent.get(VacuumRvcRunModeServer).state.currentMode === 1 /* Cleaning */,
170809
+ // Derive from the entity directly. Reading the sibling rvcRunMode behavior
170810
+ // crashed with a class mismatch and would lag one tick behind anyway (#428).
170811
+ isOn: (entity) => vacuumIsCleaning(entity.state),
169934
170812
  turnOn: () => ({ action: "vacuum.start" }),
169935
170813
  turnOff: () => ({ action: "vacuum.return_to_base" })
169936
170814
  }).with();
@@ -170007,7 +170885,7 @@ init_nodejs();
170007
170885
 
170008
170886
  // src/matter/behaviors/rvc-clean-mode-server.ts
170009
170887
  init_home_assistant_entity_behavior();
170010
- var logger236 = Logger.get("RvcCleanModeServerBase");
170888
+ var logger237 = Logger.get("RvcCleanModeServerBase");
170011
170889
  var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeServer {
170012
170890
  // Pending mode from a recent changeToMode command.
170013
170891
  // Prevents stale HA state (from a different entity like select.xxx)
@@ -170054,14 +170932,14 @@ var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeS
170054
170932
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
170055
170933
  const { newMode } = request;
170056
170934
  if (newMode !== this.state.currentMode && !this.state.supportedModes.some((m) => m.mode === newMode)) {
170057
- logger236.warn(`changeToMode(${newMode}) rejected: unsupported mode`);
170935
+ logger237.warn(`changeToMode(${newMode}) rejected: unsupported mode`);
170058
170936
  return {
170059
170937
  status: ModeBase3.ModeChangeStatus.UnsupportedMode,
170060
170938
  statusText: `Unsupported mode: ${newMode}`
170061
170939
  };
170062
170940
  }
170063
170941
  const modeLabel = this.state.supportedModes.find((m) => m.mode === newMode);
170064
- logger236.info(
170942
+ logger237.info(
170065
170943
  `changeToMode(${newMode}) "${modeLabel?.label ?? "unknown"}" for ${homeAssistant.entityId}`
170066
170944
  );
170067
170945
  this.pendingMode = newMode;
@@ -170069,7 +170947,7 @@ var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeS
170069
170947
  this.state.currentMode = newMode;
170070
170948
  const action = this.state.config.setCleanMode(newMode, this.agent);
170071
170949
  if (action) {
170072
- logger236.info(
170950
+ logger237.info(
170073
170951
  `changeToMode: dispatching action ${action.action} \u2192 ${action.target ?? homeAssistant.entityId}`
170074
170952
  );
170075
170953
  homeAssistant.callAction(action);
@@ -170102,7 +170980,7 @@ function RvcCleanModeServer2(config11, initialState) {
170102
170980
  }
170103
170981
 
170104
170982
  // src/matter/endpoints/legacy/vacuum/behaviors/vacuum-rvc-clean-mode-server.ts
170105
- var logger237 = Logger.get("VacuumRvcCleanModeServer");
170983
+ var logger238 = Logger.get("VacuumRvcCleanModeServer");
170106
170984
  var MODE_VACUUM = 0;
170107
170985
  var MODE_VACUUM_AND_MOP = 1;
170108
170986
  var MODE_MOP = 2;
@@ -170372,7 +171250,7 @@ function findMatchingCleanOption(ct, availableOptions) {
170372
171250
  const match = availableOptions.find((o) => classifyCleanOption(o) === type);
170373
171251
  if (match) return match;
170374
171252
  }
170375
- logger237.warn(
171253
+ logger238.warn(
170376
171254
  `No match for ${CLEAN_TYPE_LABELS[ct]} in [${availableOptions.join(", ")}]`
170377
171255
  );
170378
171256
  return availableOptions[0];
@@ -170381,7 +171259,7 @@ function buildCleaningModeAction(targetCleanType, agent) {
170381
171259
  const selectEntityId = getCleaningModeSelectEntity(agent);
170382
171260
  const { options } = readSelectEntity(selectEntityId, agent);
170383
171261
  const optionToUse = findMatchingCleanOption(targetCleanType, options);
170384
- logger237.info(
171262
+ logger238.info(
170385
171263
  `Switching cleaning mode to: ${optionToUse} via ${selectEntityId}`
170386
171264
  );
170387
171265
  return {
@@ -170470,7 +171348,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170470
171348
  }
170471
171349
  }
170472
171350
  if (speedMode !== void 0) {
170473
- logger237.debug(
171351
+ logger238.debug(
170474
171352
  `Current mode: Vacuum + fan_speed="${speedState}" -> mode ${speedMode}`
170475
171353
  );
170476
171354
  return speedMode;
@@ -170491,7 +171369,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170491
171369
  }
170492
171370
  }
170493
171371
  if (mopMode !== void 0) {
170494
- logger237.debug(
171372
+ logger238.debug(
170495
171373
  `Current mode: Mop + intensity="${state}" -> mode ${mopMode}`
170496
171374
  );
170497
171375
  return mopMode;
@@ -170509,14 +171387,14 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170509
171387
  const homeAssistant = agent.get(HomeAssistantEntityBehavior);
170510
171388
  const vacuumEntityId = homeAssistant.entityId;
170511
171389
  const mapping = homeAssistant.state.mapping;
170512
- logger237.info(
171390
+ logger238.info(
170513
171391
  `setCleanMode(${mode}) for ${vacuumEntityId}, suctionEntity=${mapping?.suctionLevelEntity ?? "none"}, mopEntity=${mapping?.mopIntensityEntity ?? "none"}, fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])}, customTags=${JSON.stringify(customFanSpeedTags ?? {})}`
170514
171392
  );
170515
171393
  if (mopIntensityList && mopIntensityList.length > 0 && isMopIntensityMode(mode)) {
170516
171394
  const mopIndex = mode - MOP_INTENSITY_MODE_BASE;
170517
171395
  const mopName = mopIntensityList[mopIndex];
170518
171396
  if (!mopName) {
170519
- logger237.warn(`Invalid mop intensity mode index: ${mopIndex}`);
171397
+ logger238.warn(`Invalid mop intensity mode index: ${mopIndex}`);
170520
171398
  return void 0;
170521
171399
  }
170522
171400
  if (hasCleanTypes) {
@@ -170529,18 +171407,18 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170529
171407
  mapping.mopIntensityEntity,
170530
171408
  agent
170531
171409
  );
170532
- logger237.info(
171410
+ logger238.info(
170533
171411
  `Mop intensity entity ${mapping.mopIntensityEntity}: current="${state}", options=${JSON.stringify(options ?? [])}`
170534
171412
  );
170535
171413
  let option = matchMopIntensityOption(mopName, options);
170536
171414
  if (!option && options && mopIndex < options.length) {
170537
171415
  option = options[mopIndex];
170538
- logger237.info(
171416
+ logger238.info(
170539
171417
  `Positional match for mop "${mopName}" -> "${option}" (index ${mopIndex})`
170540
171418
  );
170541
171419
  }
170542
171420
  if (option) {
170543
- logger237.info(
171421
+ logger238.info(
170544
171422
  `Setting mop intensity to: ${option} via ${mapping.mopIntensityEntity}`
170545
171423
  );
170546
171424
  return {
@@ -170549,11 +171427,11 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170549
171427
  target: mapping.mopIntensityEntity
170550
171428
  };
170551
171429
  }
170552
- logger237.warn(
171430
+ logger238.warn(
170553
171431
  `No match for mop intensity "${mopName}" in options: [${(options ?? []).join(", ")}]`
170554
171432
  );
170555
171433
  } else {
170556
- logger237.warn(
171434
+ logger238.warn(
170557
171435
  `Mop intensity mode ${mode} requested but no mopIntensityEntity configured`
170558
171436
  );
170559
171437
  }
@@ -170563,7 +171441,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170563
171441
  const fanSpeedIndex = mode - FAN_SPEED_MODE_BASE;
170564
171442
  const fanSpeedName = fanSpeedList[fanSpeedIndex];
170565
171443
  if (!fanSpeedName) {
170566
- logger237.warn(`Invalid fan speed mode index: ${fanSpeedIndex}`);
171444
+ logger238.warn(`Invalid fan speed mode index: ${fanSpeedIndex}`);
170567
171445
  return void 0;
170568
171446
  }
170569
171447
  if (mapping?.suctionLevelEntity) {
@@ -170576,7 +171454,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170576
171454
  mapping.suctionLevelEntity,
170577
171455
  agent
170578
171456
  );
170579
- logger237.info(
171457
+ logger238.info(
170580
171458
  `Suction entity ${mapping.suctionLevelEntity}: current="${state}", options=${JSON.stringify(options ?? [])}`
170581
171459
  );
170582
171460
  let option = matchFanSpeedOption(
@@ -170586,12 +171464,12 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170586
171464
  );
170587
171465
  if (!option && options && fanSpeedIndex < options.length) {
170588
171466
  option = options[fanSpeedIndex];
170589
- logger237.info(
171467
+ logger238.info(
170590
171468
  `Positional match for fan "${fanSpeedName}" -> "${option}" (index ${fanSpeedIndex})`
170591
171469
  );
170592
171470
  }
170593
171471
  if (option) {
170594
- logger237.info(
171472
+ logger238.info(
170595
171473
  `Setting suction to: ${option} via ${mapping.suctionLevelEntity}`
170596
171474
  );
170597
171475
  return {
@@ -170600,7 +171478,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170600
171478
  target: mapping.suctionLevelEntity
170601
171479
  };
170602
171480
  }
170603
- logger237.warn(
171481
+ logger238.warn(
170604
171482
  `No match for fan speed "${fanSpeedName}" in suction options: [${(options ?? []).join(", ")}]`
170605
171483
  );
170606
171484
  return void 0;
@@ -170610,7 +171488,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170610
171488
  buildCleaningModeAction(0 /* Sweeping */, agent)
170611
171489
  );
170612
171490
  }
170613
- logger237.info(
171491
+ logger238.info(
170614
171492
  `Setting fan speed to: ${fanSpeedName} via vacuum.set_fan_speed`
170615
171493
  );
170616
171494
  return {
@@ -170620,7 +171498,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170620
171498
  };
170621
171499
  }
170622
171500
  if (!hasCleanTypes) {
170623
- logger237.debug(
171501
+ logger238.debug(
170624
171502
  `Ignoring cleaning type change (mode=${mode}): no cleaning mode entity`
170625
171503
  );
170626
171504
  return void 0;
@@ -170632,7 +171510,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
170632
171510
  agent
170633
171511
  );
170634
171512
  const optionToUse = findMatchingCleanOption(cleanType, availableOptions);
170635
- logger237.info(
171513
+ logger238.info(
170636
171514
  `Setting cleaning mode to: ${optionToUse} (mode=${mode}) via ${selectEntityId}`
170637
171515
  );
170638
171516
  return {
@@ -170650,10 +171528,10 @@ function createVacuumRvcCleanModeServer(_attributes, fanSpeedList, mopIntensityL
170650
171528
  cleaningModeOptions,
170651
171529
  customFanSpeedTags
170652
171530
  );
170653
- logger237.info(
171531
+ logger238.info(
170654
171532
  `Creating VacuumRvcCleanModeServer with ${supportedModes2.length} modes (fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])}, cleaningModeOptions=${JSON.stringify(cleaningModeOptions ?? [])}, customTags=${JSON.stringify(customFanSpeedTags ?? {})})`
170655
171533
  );
170656
- logger237.info(
171534
+ logger238.info(
170657
171535
  `Modes: ${supportedModes2.map((m) => `${m.mode}:${m.label}[${m.modeTags.map((t) => t.value).join(",")}]`).join(", ")}`
170658
171536
  );
170659
171537
  const initialState = {
@@ -170716,7 +171594,7 @@ function resolveMopIntensityList(mopIntensityEntity) {
170716
171594
  init_dist();
170717
171595
  init_esm();
170718
171596
  init_home_assistant_entity_behavior();
170719
- var logger238 = Logger.get("VacuumRvcOperationalStateServer");
171597
+ var logger239 = Logger.get("VacuumRvcOperationalStateServer");
170720
171598
  function batteryFromAttributes(attrs) {
170721
171599
  const raw = attrs.battery_level ?? attrs.battery;
170722
171600
  if (raw == null) return null;
@@ -170743,7 +171621,7 @@ function isDockedCharging(entity, batteryPercent) {
170743
171621
  }
170744
171622
  function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttributes(entity.attributes), chargingState = null) {
170745
171623
  const state = entity.state;
170746
- const cleaningStates = [
171624
+ const cleaningStates2 = [
170747
171625
  VacuumState.cleaning,
170748
171626
  VacuumState.segment_cleaning,
170749
171627
  VacuumState.zone_cleaning,
@@ -170756,7 +171634,7 @@ function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttribute
170756
171634
  operationalState = charging ? RvcOperationalState4.OperationalState.Charging : RvcOperationalState4.OperationalState.Docked;
170757
171635
  } else if (state === VacuumState.returning) {
170758
171636
  operationalState = RvcOperationalState4.OperationalState.SeekingCharger;
170759
- } else if (cleaningStates.includes(state)) {
171637
+ } else if (cleaningStates2.includes(state)) {
170760
171638
  operationalState = RvcOperationalState4.OperationalState.Running;
170761
171639
  } else if (state === VacuumState.paused) {
170762
171640
  operationalState = RvcOperationalState4.OperationalState.Paused;
@@ -170767,16 +171645,16 @@ function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttribute
170767
171645
  operationalState = RvcOperationalState4.OperationalState.Error;
170768
171646
  } else {
170769
171647
  if (state.toLowerCase().includes("clean")) {
170770
- logger238.info(
171648
+ logger239.info(
170771
171649
  `Unknown vacuum state "${state}" contains 'clean', treating as Running`
170772
171650
  );
170773
171651
  operationalState = RvcOperationalState4.OperationalState.Running;
170774
171652
  } else {
170775
- logger238.info(`Unknown vacuum state "${state}", treating as Stopped`);
171653
+ logger239.info(`Unknown vacuum state "${state}", treating as Stopped`);
170776
171654
  operationalState = RvcOperationalState4.OperationalState.Stopped;
170777
171655
  }
170778
171656
  }
170779
- logger238.debug(
171657
+ logger239.debug(
170780
171658
  `Vacuum operationalState: "${state}" -> ${RvcOperationalState4.OperationalState[operationalState]}`
170781
171659
  );
170782
171660
  return operationalState;
@@ -170805,7 +171683,7 @@ var VacuumRvcOperationalStateServer = RvcOperationalStateServer2({
170805
171683
  });
170806
171684
 
170807
171685
  // src/matter/endpoints/legacy/vacuum/index.ts
170808
- var logger239 = Logger.get("VacuumDevice");
171686
+ var logger240 = Logger.get("VacuumDevice");
170809
171687
  var VacuumEndpointType = RoboticVacuumCleanerDevice.with(
170810
171688
  BasicInformationServer2,
170811
171689
  VacuumIdentifyServer,
@@ -170819,7 +171697,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
170819
171697
  const entityId = homeAssistantEntity.entity.entity_id;
170820
171698
  const attributes9 = homeAssistantEntity.entity.state.attributes;
170821
171699
  const customAreas = homeAssistantEntity.mapping?.customServiceAreas;
170822
- logger239.info(
171700
+ logger240.info(
170823
171701
  `Creating vacuum endpoint for ${entityId}, mapping: ${JSON.stringify(homeAssistantEntity.mapping ?? "none")}`
170824
171702
  );
170825
171703
  const cleanAreaRooms = homeAssistantEntity.mapping?.cleanAreaRooms;
@@ -170832,32 +171710,32 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
170832
171710
  )
170833
171711
  ).set({ homeAssistantEntity });
170834
171712
  if (includeOnOff) {
170835
- logger239.info(`${entityId}: Adding OnOff cluster (vacuumOnOff flag enabled)`);
171713
+ logger240.info(`${entityId}: Adding OnOff cluster (vacuumOnOff flag enabled)`);
170836
171714
  device = device.with(VacuumOnOffServer);
170837
171715
  }
170838
171716
  device = device.with(VacuumPowerSourceServer);
170839
171717
  const roomEntities = homeAssistantEntity.mapping?.roomEntities;
170840
171718
  const rooms = parseVacuumRooms(attributes9);
170841
- logger239.info(
171719
+ logger240.info(
170842
171720
  `${entityId}: customAreas=${customAreas?.length ?? 0}, roomEntities=${JSON.stringify(roomEntities ?? [])}, parsedRooms=${rooms.length}, cleanAreaRooms=${cleanAreaRooms?.length ?? 0}`
170843
171721
  );
170844
171722
  if (cleanAreaRooms && cleanAreaRooms.length > 0) {
170845
- logger239.info(
171723
+ logger240.info(
170846
171724
  `${entityId}: Adding ServiceArea (${cleanAreaRooms.length} HA areas via CLEAN_AREA)`
170847
171725
  );
170848
171726
  device = device.with(createCleanAreaServiceAreaServer(cleanAreaRooms));
170849
171727
  } else if (customAreas && customAreas.length > 0) {
170850
- logger239.info(
171728
+ logger240.info(
170851
171729
  `${entityId}: Adding ServiceArea (${customAreas.length} custom areas)`
170852
171730
  );
170853
171731
  device = device.with(createCustomServiceAreaServer(customAreas));
170854
171732
  } else if (rooms.length > 0 || roomEntities && roomEntities.length > 0) {
170855
- logger239.info(`${entityId}: Adding ServiceArea (${rooms.length} rooms)`);
171733
+ logger240.info(`${entityId}: Adding ServiceArea (${rooms.length} rooms)`);
170856
171734
  device = device.with(
170857
171735
  createVacuumServiceAreaServer(attributes9, roomEntities)
170858
171736
  );
170859
171737
  } else {
170860
- logger239.info(`${entityId}: Adding ServiceArea (default single-area)`);
171738
+ logger240.info(`${entityId}: Adding ServiceArea (default single-area)`);
170861
171739
  device = device.with(createDefaultServiceAreaServer());
170862
171740
  }
170863
171741
  const fanSpeedList = resolveFanSpeedList(
@@ -170868,7 +171746,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
170868
171746
  homeAssistantEntity.mapping?.mopIntensityEntity
170869
171747
  );
170870
171748
  if (cleaningModeOptions || fanSpeedList || mopIntensityList) {
170871
- logger239.info(
171749
+ logger240.info(
170872
171750
  `${entityId}: Adding RvcCleanMode (multi-mode, cleaningModeOptions=${JSON.stringify(cleaningModeOptions ?? [])}, fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])})`
170873
171751
  );
170874
171752
  device = device.with(
@@ -170881,7 +171759,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
170881
171759
  )
170882
171760
  );
170883
171761
  } else {
170884
- logger239.info(`${entityId}: Adding RvcCleanMode (default single-mode)`);
171762
+ logger240.info(`${entityId}: Adding RvcCleanMode (default single-mode)`);
170885
171763
  device = device.with(createDefaultRvcCleanModeServer());
170886
171764
  }
170887
171765
  return device;
@@ -171047,7 +171925,7 @@ var WaterHeaterThermostatServer = ThermostatServer2(
171047
171925
  );
171048
171926
 
171049
171927
  // src/matter/endpoints/legacy/water-heater/index.ts
171050
- var logger240 = Logger.get("WaterHeaterDevice");
171928
+ var logger241 = Logger.get("WaterHeaterDevice");
171051
171929
  var WaterHeaterDeviceType = ThermostatDevice.with(
171052
171930
  BasicInformationServer2,
171053
171931
  IdentifyServer2,
@@ -171063,7 +171941,7 @@ function toMatterTemp2(value) {
171063
171941
  }
171064
171942
  function WaterHeaterDevice(homeAssistantEntity) {
171065
171943
  const attributes9 = homeAssistantEntity.entity.state.attributes;
171066
- logger240.debug(
171944
+ logger241.debug(
171067
171945
  `Creating device for ${homeAssistantEntity.entity.entity_id}, min_temp=${attributes9.min_temp}, max_temp=${attributes9.max_temp}`
171068
171946
  );
171069
171947
  const minLimit = toMatterTemp2(attributes9.min_temp) ?? 0;
@@ -171303,6 +172181,7 @@ var matterDeviceTypeFactories = {
171303
172181
  // Legacy SolarPower alias, kept for existing mappings.
171304
172182
  electrical_sensor: (ha) => ElectricalSensorType.set({ homeAssistantEntity: ha }),
171305
172183
  solar_power: (ha) => ElectricalSensorType.set({ homeAssistantEntity: ha }),
172184
+ evse: (ha) => energyEvseType().set({ homeAssistantEntity: ha }),
171306
172185
  contact_sensor: (ha) => ContactSensorType.set({ homeAssistantEntity: ha }),
171307
172186
  motion_sensor: (ha) => MotionSensorType.set({ homeAssistantEntity: ha }),
171308
172187
  occupancy_sensor: (ha) => OccupancySensorType.set({ homeAssistantEntity: ha }),
@@ -171318,7 +172197,7 @@ var matterDeviceTypeFactories = {
171318
172197
  };
171319
172198
 
171320
172199
  // src/matter/endpoints/composed/user-composed-endpoint.ts
171321
- var logger241 = Logger.get("UserComposedEndpoint");
172200
+ var logger242 = Logger.get("UserComposedEndpoint");
171322
172201
  function stripBasicInformation(type) {
171323
172202
  const behaviors = { ...type.behaviors };
171324
172203
  delete behaviors.bridgedDeviceBasicInformation;
@@ -171328,11 +172207,11 @@ function createEndpointId5(entityId, customName) {
171328
172207
  const baseName = customName || entityId;
171329
172208
  return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
171330
172209
  }
171331
- function buildEntityPayload4(registry2, entityId) {
171332
- const state = registry2.initialStateIncludingUnfiltered(entityId);
172210
+ function buildEntityPayload4(registry3, entityId) {
172211
+ const state = registry3.initialStateIncludingUnfiltered(entityId);
171333
172212
  if (!state) return void 0;
171334
- const entity = registry2.entityIncludingUnfiltered(entityId);
171335
- const deviceRegistry = registry2.deviceOfIncludingUnfiltered(entityId);
172213
+ const entity = registry3.entityIncludingUnfiltered(entityId);
172214
+ const deviceRegistry = registry3.deviceOfIncludingUnfiltered(entityId);
171336
172215
  return {
171337
172216
  entity_id: entityId,
171338
172217
  state,
@@ -171348,8 +172227,8 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
171348
172227
  lastMappedStates = /* @__PURE__ */ new Map();
171349
172228
  debouncedUpdates = /* @__PURE__ */ new Map();
171350
172229
  static async create(config11) {
171351
- const { registry: registry2, primaryEntityId, composedEntities } = config11;
171352
- const primaryPayload = buildEntityPayload4(registry2, primaryEntityId);
172230
+ const { registry: registry3, primaryEntityId, composedEntities } = config11;
172231
+ const primaryPayload = buildEntityPayload4(registry3, primaryEntityId);
171353
172232
  if (!primaryPayload) return void 0;
171354
172233
  let parentType = BridgedNodeEndpoint.with(
171355
172234
  BasicInformationServer2,
@@ -171391,10 +172270,10 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
171391
172270
  primarySubPayload,
171392
172271
  primaryMapping,
171393
172272
  config11.areaName,
171394
- { vacuumOnOff: registry2.isVacuumOnOffEnabled() }
172273
+ { vacuumOnOff: registry3.isVacuumOnOffEnabled() }
171395
172274
  );
171396
172275
  if (!primaryType) {
171397
- logger241.warn(
172276
+ logger242.warn(
171398
172277
  `Cannot create endpoint type for primary entity ${primaryEntityId}`
171399
172278
  );
171400
172279
  return void 0;
@@ -171407,9 +172286,9 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
171407
172286
  for (let i = 0; i < composedEntities.length; i++) {
171408
172287
  const sub = composedEntities[i];
171409
172288
  if (!sub.entityId) continue;
171410
- const subPayload = buildEntityPayload4(registry2, sub.entityId);
172289
+ const subPayload = buildEntityPayload4(registry3, sub.entityId);
171411
172290
  if (!subPayload) {
171412
- logger241.warn(
172291
+ logger242.warn(
171413
172292
  `Cannot find state for composed sub-entity ${sub.entityId}, it does not exist in Home Assistant (removed or renamed?)`
171414
172293
  );
171415
172294
  continue;
@@ -171424,7 +172303,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
171424
172303
  config11.areaName
171425
172304
  );
171426
172305
  if (!subType) {
171427
- logger241.warn(
172306
+ logger242.warn(
171428
172307
  `Cannot create endpoint type for composed sub-entity ${sub.entityId}`
171429
172308
  );
171430
172309
  continue;
@@ -171437,7 +172316,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
171437
172316
  mappedIds.push(sub.entityId);
171438
172317
  }
171439
172318
  if (parts.length < 2) {
171440
- logger241.warn(
172319
+ logger242.warn(
171441
172320
  `User composed device ${primaryEntityId}: only ${parts.length} sub-endpoint(s), need at least 2 (primary + one sub-entity). Falling back to standalone.`
171442
172321
  );
171443
172322
  return void 0;
@@ -171461,7 +172340,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
171461
172340
  const labels = parts.map(
171462
172341
  (_, i) => i === 0 ? primaryEntityId.split(".")[0] : composedEntities[i - 1]?.entityId?.split(".")[0] ?? "?"
171463
172342
  ).join("+");
171464
- logger241.info(
172343
+ logger242.info(
171465
172344
  `Created user composed device ${primaryEntityId}: ${parts.length} sub-endpoint(s) [${labels}]`
171466
172345
  );
171467
172346
  return endpoint;
@@ -171567,45 +172446,45 @@ function asStandaloneEndpointType(type) {
171567
172446
  }
171568
172447
 
171569
172448
  // src/matter/endpoints/legacy/legacy-endpoint.ts
171570
- var logger242 = Logger.get("LegacyEndpoint");
172449
+ var logger243 = Logger.get("LegacyEndpoint");
171571
172450
  var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171572
- static async create(registry2, entityId, mapping, pluginDomainMappings, standalone = false, endpointId, identityAnchor) {
171573
- const deviceRegistry = registry2.deviceOf(entityId);
171574
- let state = registry2.initialState(entityId);
171575
- const entity = registry2.entity(entityId);
172451
+ static async create(registry3, entityId, mapping, pluginDomainMappings, standalone = false, endpointId, identityAnchor) {
172452
+ const deviceRegistry = registry3.deviceOf(entityId);
172453
+ let state = registry3.initialState(entityId);
172454
+ const entity = registry3.entity(entityId);
171576
172455
  if (!state) {
171577
172456
  return;
171578
172457
  }
171579
- if (registry2.isAutoBatteryMappingEnabled() && registry2.isBatteryEntityUsed(entityId)) {
171580
- logger242.debug(
172458
+ if (registry3.isAutoBatteryMappingEnabled() && registry3.isBatteryEntityUsed(entityId)) {
172459
+ logger243.debug(
171581
172460
  `Skipping ${entityId} - already auto-assigned as battery to another device`
171582
172461
  );
171583
172462
  return;
171584
172463
  }
171585
- if (registry2.isAutoHumidityMappingEnabled() && registry2.isHumidityEntityUsed(entityId)) {
171586
- logger242.debug(
172464
+ if (registry3.isAutoHumidityMappingEnabled() && registry3.isHumidityEntityUsed(entityId)) {
172465
+ logger243.debug(
171587
172466
  `Skipping ${entityId} - already auto-assigned as humidity to a temperature sensor`
171588
172467
  );
171589
172468
  return;
171590
172469
  }
171591
- if (registry2.isAutoPressureMappingEnabled() && registry2.isPressureEntityUsed(entityId)) {
171592
- logger242.debug(
172470
+ if (registry3.isAutoPressureMappingEnabled() && registry3.isPressureEntityUsed(entityId)) {
172471
+ logger243.debug(
171593
172472
  `Skipping ${entityId} - already auto-assigned as pressure to a temperature sensor`
171594
172473
  );
171595
172474
  return;
171596
172475
  }
171597
- if (registry2.isAutoComposedDevicesEnabled() && registry2.isComposedSubEntityUsed(entityId)) {
171598
- logger242.debug(
172476
+ if (registry3.isAutoComposedDevicesEnabled() && registry3.isComposedSubEntityUsed(entityId)) {
172477
+ logger243.debug(
171599
172478
  `Skipping ${entityId} - already consumed by a composed device`
171600
172479
  );
171601
172480
  return;
171602
172481
  }
171603
172482
  let effectiveMapping = mapping;
171604
172483
  if (entity.device_id) {
171605
- if (registry2.isAutoHumidityMappingEnabled()) {
172484
+ if (registry3.isAutoHumidityMappingEnabled()) {
171606
172485
  const attrs = state.attributes;
171607
172486
  if (!mapping?.humidityEntity && entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature) {
171608
- const humidityEntityId = registry2.findHumidityEntityForDevice(
172487
+ const humidityEntityId = registry3.findHumidityEntityForDevice(
171609
172488
  entity.device_id
171610
172489
  );
171611
172490
  if (humidityEntityId && humidityEntityId !== entityId) {
@@ -171614,17 +172493,17 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171614
172493
  entityId: effectiveMapping?.entityId ?? entityId,
171615
172494
  humidityEntity: humidityEntityId
171616
172495
  };
171617
- registry2.markHumidityEntityUsed(humidityEntityId);
171618
- logger242.debug(
172496
+ registry3.markHumidityEntityUsed(humidityEntityId);
172497
+ logger243.debug(
171619
172498
  `Auto-assigned humidity ${humidityEntityId} to ${entityId}`
171620
172499
  );
171621
172500
  }
171622
172501
  }
171623
172502
  }
171624
- if (registry2.isAutoPressureMappingEnabled()) {
172503
+ if (registry3.isAutoPressureMappingEnabled()) {
171625
172504
  const attrs = state.attributes;
171626
172505
  if (!mapping?.pressureEntity && entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature) {
171627
- const pressureEntityId = registry2.findPressureEntityForDevice(
172506
+ const pressureEntityId = registry3.findPressureEntityForDevice(
171628
172507
  entity.device_id
171629
172508
  );
171630
172509
  if (pressureEntityId && pressureEntityId !== entityId) {
@@ -171633,16 +172512,16 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171633
172512
  entityId: effectiveMapping?.entityId ?? entityId,
171634
172513
  pressureEntity: pressureEntityId
171635
172514
  };
171636
- registry2.markPressureEntityUsed(pressureEntityId);
171637
- logger242.debug(
172515
+ registry3.markPressureEntityUsed(pressureEntityId);
172516
+ logger243.debug(
171638
172517
  `Auto-assigned pressure ${pressureEntityId} to ${entityId}`
171639
172518
  );
171640
172519
  }
171641
172520
  }
171642
172521
  }
171643
172522
  const isVacuum = entityId.startsWith("vacuum.");
171644
- if ((registry2.isAutoBatteryMappingEnabled() || isVacuum) && !mapping?.batteryEntity && !mapping?.disableBatteryMapping) {
171645
- const batteryEntityId = registry2.findBatteryEntityForDevice(
172523
+ if ((registry3.isAutoBatteryMappingEnabled() || isVacuum) && !mapping?.batteryEntity && !mapping?.disableBatteryMapping) {
172524
+ const batteryEntityId = registry3.findBatteryEntityForDevice(
171646
172525
  entity.device_id
171647
172526
  );
171648
172527
  if (batteryEntityId && batteryEntityId !== entityId) {
@@ -171651,16 +172530,16 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171651
172530
  entityId: effectiveMapping?.entityId ?? entityId,
171652
172531
  batteryEntity: batteryEntityId
171653
172532
  };
171654
- registry2.markBatteryEntityUsed(batteryEntityId);
171655
- logger242.debug(
172533
+ registry3.markBatteryEntityUsed(batteryEntityId);
172534
+ logger243.debug(
171656
172535
  `Auto-assigned battery ${batteryEntityId} to ${entityId}`
171657
172536
  );
171658
172537
  }
171659
172538
  }
171660
172539
  const alarmDeviceClass = state.attributes.device_class;
171661
172540
  const isSmokeCoAlarm = mapping?.matterDeviceType === "smoke_co_alarm" || entityId.startsWith("binary_sensor.") && (alarmDeviceClass === "smoke" || alarmDeviceClass === "carbon_monoxide" || alarmDeviceClass === "gas");
171662
- if (registry2.isAutoBatteryMappingEnabled() && !mapping?.faultEntity && isSmokeCoAlarm) {
171663
- const faultEntityId = registry2.findProblemEntityForDevice(
172541
+ if (registry3.isAutoBatteryMappingEnabled() && !mapping?.faultEntity && isSmokeCoAlarm) {
172542
+ const faultEntityId = registry3.findProblemEntityForDevice(
171664
172543
  entity.device_id
171665
172544
  );
171666
172545
  if (faultEntityId && faultEntityId !== entityId) {
@@ -171669,13 +172548,13 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171669
172548
  entityId: effectiveMapping?.entityId ?? entityId,
171670
172549
  faultEntity: faultEntityId
171671
172550
  };
171672
- logger242.debug(`Auto-assigned fault ${faultEntityId} to ${entityId}`);
172551
+ logger243.debug(`Auto-assigned fault ${faultEntityId} to ${entityId}`);
171673
172552
  }
171674
172553
  }
171675
172554
  if (!mapping?.powerEntity) {
171676
172555
  const domain = entityId.split(".")[0];
171677
172556
  if (domain === "switch") {
171678
- const powerEntityId = registry2.findPowerEntityForDevice(
172557
+ const powerEntityId = registry3.findPowerEntityForDevice(
171679
172558
  entity.device_id
171680
172559
  );
171681
172560
  if (powerEntityId && powerEntityId !== entityId) {
@@ -171684,15 +172563,15 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171684
172563
  entityId: effectiveMapping?.entityId ?? entityId,
171685
172564
  powerEntity: powerEntityId
171686
172565
  };
171687
- registry2.markPowerEntityUsed(powerEntityId);
171688
- logger242.debug(`Auto-assigned power ${powerEntityId} to ${entityId}`);
172566
+ registry3.markPowerEntityUsed(powerEntityId);
172567
+ logger243.debug(`Auto-assigned power ${powerEntityId} to ${entityId}`);
171689
172568
  }
171690
172569
  }
171691
172570
  }
171692
172571
  if (!mapping?.energyEntity) {
171693
172572
  const domain = entityId.split(".")[0];
171694
172573
  if (domain === "switch") {
171695
- const energyEntityId = registry2.findEnergyEntityForDevice(
172574
+ const energyEntityId = registry3.findEnergyEntityForDevice(
171696
172575
  entity.device_id
171697
172576
  );
171698
172577
  if (energyEntityId && energyEntityId !== entityId) {
@@ -171701,15 +172580,15 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171701
172580
  entityId: effectiveMapping?.entityId ?? entityId,
171702
172581
  energyEntity: energyEntityId
171703
172582
  };
171704
- registry2.markEnergyEntityUsed(energyEntityId);
171705
- logger242.debug(
172583
+ registry3.markEnergyEntityUsed(energyEntityId);
172584
+ logger243.debug(
171706
172585
  `Auto-assigned energy ${energyEntityId} to ${entityId}`
171707
172586
  );
171708
172587
  }
171709
172588
  }
171710
172589
  }
171711
172590
  if (entityId.startsWith("vacuum.")) {
171712
- const vacuumEntities = registry2.findVacuumSelectEntities(
172591
+ const vacuumEntities = registry3.findVacuumSelectEntities(
171713
172592
  entity.device_id
171714
172593
  );
171715
172594
  if (!effectiveMapping?.cleaningModeEntity && vacuumEntities.cleaningModeEntity) {
@@ -171718,7 +172597,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171718
172597
  entityId: effectiveMapping?.entityId ?? entityId,
171719
172598
  cleaningModeEntity: vacuumEntities.cleaningModeEntity
171720
172599
  };
171721
- logger242.info(
172600
+ logger243.info(
171722
172601
  `Auto-assigned cleaningMode ${vacuumEntities.cleaningModeEntity} to ${entityId}`
171723
172602
  );
171724
172603
  }
@@ -171728,7 +172607,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171728
172607
  entityId: effectiveMapping?.entityId ?? entityId,
171729
172608
  suctionLevelEntity: vacuumEntities.suctionLevelEntity
171730
172609
  };
171731
- logger242.info(
172610
+ logger243.info(
171732
172611
  `Auto-assigned suctionLevel ${vacuumEntities.suctionLevelEntity} to ${entityId}`
171733
172612
  );
171734
172613
  }
@@ -171738,7 +172617,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171738
172617
  entityId: effectiveMapping?.entityId ?? entityId,
171739
172618
  mopIntensityEntity: vacuumEntities.mopIntensityEntity
171740
172619
  };
171741
- logger242.info(
172620
+ logger243.info(
171742
172621
  `Auto-assigned mopIntensity ${vacuumEntities.mopIntensityEntity} to ${entityId}`
171743
172622
  );
171744
172623
  }
@@ -171748,12 +172627,12 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171748
172627
  entityId: effectiveMapping?.entityId ?? entityId,
171749
172628
  currentRoomEntity: vacuumEntities.currentRoomEntity
171750
172629
  };
171751
- logger242.info(
172630
+ logger243.info(
171752
172631
  `Auto-assigned currentRoom ${vacuumEntities.currentRoomEntity} to ${entityId}`
171753
172632
  );
171754
172633
  }
171755
172634
  const supportedFeatures = state.attributes.supported_features ?? 0;
171756
- const cleanAreaRooms = await registry2.resolveCleanAreaRooms(
172635
+ const cleanAreaRooms = await registry3.resolveCleanAreaRooms(
171757
172636
  entityId,
171758
172637
  supportedFeatures
171759
172638
  );
@@ -171763,13 +172642,13 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171763
172642
  entityId: effectiveMapping?.entityId ?? entityId,
171764
172643
  cleanAreaRooms
171765
172644
  };
171766
- logger242.info(
172645
+ logger243.info(
171767
172646
  `Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA for ${entityId}`
171768
172647
  );
171769
172648
  }
171770
172649
  const vacAttrs = state.attributes;
171771
172650
  if (cleanAreaRooms.length === 0 && !vacAttrs.rooms && !vacAttrs.segments && !vacAttrs.room_mapping) {
171772
- const valetudoRooms = registry2.findValetudoMapSegments(
172651
+ const valetudoRooms = registry3.findValetudoMapSegments(
171773
172652
  entity.device_id
171774
172653
  );
171775
172654
  if (valetudoRooms.length > 0) {
@@ -171784,11 +172663,11 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171784
172663
  rooms: roomsObj
171785
172664
  }
171786
172665
  };
171787
- logger242.debug(
172666
+ logger243.debug(
171788
172667
  `Auto-detected ${valetudoRooms.length} Valetudo segments for ${entityId}`
171789
172668
  );
171790
172669
  } else {
171791
- const roborockRooms = await registry2.resolveRoborockRooms(entityId);
172670
+ const roborockRooms = await registry3.resolveRoborockRooms(entityId);
171792
172671
  if (roborockRooms.length > 0) {
171793
172672
  const roomsObj = {};
171794
172673
  for (const r of roborockRooms) {
@@ -171801,7 +172680,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171801
172680
  rooms: roomsObj
171802
172681
  }
171803
172682
  };
171804
- logger242.debug(
172683
+ logger243.debug(
171805
172684
  `Auto-detected ${roborockRooms.length} Roborock rooms for ${entityId}`
171806
172685
  );
171807
172686
  }
@@ -171810,14 +172689,14 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171810
172689
  }
171811
172690
  }
171812
172691
  if (standalone && ((effectiveMapping?.composedEntities?.length ?? 0) > 0 || effectiveMapping?.climateExposeFan === true)) {
171813
- logger242.warn(
172692
+ logger243.warn(
171814
172693
  `Composed mappings are not supported in server mode, exposing ${entityId} as a flat standalone endpoint`
171815
172694
  );
171816
172695
  }
171817
- if (!standalone && registry2.isAutoComposedDevicesEnabled() && effectiveMapping?.composedEntities && effectiveMapping.composedEntities.length > 0) {
171818
- const composedAreaName = registry2.getAreaName(entityId);
172696
+ if (!standalone && registry3.isAutoComposedDevicesEnabled() && effectiveMapping?.composedEntities && effectiveMapping.composedEntities.length > 0) {
172697
+ const composedAreaName = registry3.getAreaName(entityId);
171819
172698
  const composed = await UserComposedEndpoint.create({
171820
- registry: registry2,
172699
+ registry: registry3,
171821
172700
  primaryEntityId: entityId,
171822
172701
  mapping: effectiveMapping,
171823
172702
  composedEntities: effectiveMapping.composedEntities,
@@ -171829,16 +172708,16 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171829
172708
  if (composed) {
171830
172709
  return composed;
171831
172710
  }
171832
- logger242.warn(
172711
+ logger243.warn(
171833
172712
  `User composed device creation failed for ${entityId}, falling back to standalone`
171834
172713
  );
171835
172714
  }
171836
- if (!standalone && registry2.isAutoComposedDevicesEnabled()) {
172715
+ if (!standalone && registry3.isAutoComposedDevicesEnabled()) {
171837
172716
  const attrs = state.attributes;
171838
172717
  if (entityId.startsWith("sensor.") && attrs.device_class === SensorDeviceClass.temperature && (effectiveMapping?.humidityEntity || effectiveMapping?.pressureEntity)) {
171839
- const composedAreaName = registry2.getAreaName(entityId);
172718
+ const composedAreaName = registry3.getAreaName(entityId);
171840
172719
  const composed = await ComposedSensorEndpoint.create({
171841
- registry: registry2,
172720
+ registry: registry3,
171842
172721
  primaryEntityId: entityId,
171843
172722
  humidityEntityId: effectiveMapping?.humidityEntity,
171844
172723
  pressureEntityId: effectiveMapping?.pressureEntity,
@@ -171854,12 +172733,12 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171854
172733
  }
171855
172734
  const resolvedMatterType = mapping?.matterDeviceType ?? (entityId.startsWith("fan.") ? "fan" : void 0);
171856
172735
  if (resolvedMatterType === "air_purifier") {
171857
- const temperatureEntityId = effectiveMapping?.temperatureEntity || (entity.device_id ? registry2.findTemperatureEntityForDevice(entity.device_id) : void 0);
171858
- const humidityEntityId = effectiveMapping?.humidityEntity || (entity.device_id ? registry2.findHumidityEntityForDevice(entity.device_id) : void 0);
172736
+ const temperatureEntityId = effectiveMapping?.temperatureEntity || (entity.device_id ? registry3.findTemperatureEntityForDevice(entity.device_id) : void 0);
172737
+ const humidityEntityId = effectiveMapping?.humidityEntity || (entity.device_id ? registry3.findHumidityEntityForDevice(entity.device_id) : void 0);
171859
172738
  if (temperatureEntityId || humidityEntityId) {
171860
- const composedAreaName = registry2.getAreaName(entityId);
172739
+ const composedAreaName = registry3.getAreaName(entityId);
171861
172740
  const composed = await ComposedAirPurifierEndpoint.create({
171862
- registry: registry2,
172741
+ registry: registry3,
171863
172742
  primaryEntityId: entityId,
171864
172743
  temperatureEntityId,
171865
172744
  humidityEntityId,
@@ -171881,9 +172760,9 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171881
172760
  if (!standalone && entityId.startsWith("climate.") && effectiveMapping?.climateExposeFan === true) {
171882
172761
  const climateFeatures = state.attributes.supported_features ?? 0;
171883
172762
  if ((climateFeatures & ClimateDeviceFeature.FAN_MODE) !== 0) {
171884
- const composedAreaName = registry2.getAreaName(entityId);
172763
+ const composedAreaName = registry3.getAreaName(entityId);
171885
172764
  const composed = await ComposedClimateFanEndpoint.create({
171886
- registry: registry2,
172765
+ registry: registry3,
171887
172766
  primaryEntityId: entityId,
171888
172767
  mapping: effectiveMapping,
171889
172768
  customName: effectiveMapping?.customName,
@@ -171894,7 +172773,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171894
172773
  if (composed) {
171895
172774
  return composed;
171896
172775
  }
171897
- logger242.warn(
172776
+ logger243.warn(
171898
172777
  `Companion fan creation failed for ${entityId}, falling back to standalone`
171899
172778
  );
171900
172779
  }
@@ -171908,7 +172787,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171908
172787
  let cleaningModeOptions;
171909
172788
  if (entityId.startsWith("vacuum.")) {
171910
172789
  if (effectiveMapping?.cleaningModeEntity) {
171911
- const cmState = registry2.initialState(
172790
+ const cmState = registry3.initialState(
171912
172791
  effectiveMapping.cleaningModeEntity
171913
172792
  );
171914
172793
  cleaningModeOptions = cmState?.attributes?.options;
@@ -171922,13 +172801,13 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171922
172801
  ];
171923
172802
  }
171924
172803
  }
171925
- const areaName = registry2.getAreaName(entityId);
172804
+ const areaName = registry3.getAreaName(entityId);
171926
172805
  let type = createLegacyEndpointType(
171927
172806
  payload,
171928
172807
  effectiveMapping,
171929
172808
  areaName,
171930
172809
  {
171931
- vacuumOnOff: registry2.isVacuumOnOffEnabled(),
172810
+ vacuumOnOff: registry3.isVacuumOnOffEnabled(),
171932
172811
  cleaningModeOptions,
171933
172812
  pluginDomainMappings
171934
172813
  },
@@ -171974,11 +172853,11 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
171974
172853
  }
171975
172854
  if (mappedChanged) {
171976
172855
  this.pendingMappedChange = true;
171977
- logger242.debug(
172856
+ logger243.debug(
171978
172857
  `Mapped entity change detected for ${this.entityId}, forcing update`
171979
172858
  );
171980
172859
  }
171981
- logger242.debug(
172860
+ logger243.debug(
171982
172861
  `State update received for ${this.entityId}: state=${state.state}`
171983
172862
  );
171984
172863
  this.lastState = state;
@@ -172107,18 +172986,186 @@ var CameraAvStreamServer = class extends Base4 {
172107
172986
  })(CameraAvStreamServer || (CameraAvStreamServer = {}));
172108
172987
 
172109
172988
  // src/plugins/builtin/camera/webrtc-provider-server.ts
172989
+ init_esm();
172110
172990
  init_esm7();
172111
172991
  init_esm3();
172992
+
172993
+ // src/plugins/builtin/camera/requestor-client.ts
172994
+ init_esm();
172995
+ init_esm4();
172996
+ var logger244 = Logger.get("CameraWebRtcRequestor");
172997
+ var registry2 = /* @__PURE__ */ new Map();
172998
+ var pendingDeliveries = /* @__PURE__ */ new Map();
172999
+ var invokeTransport = defaultInvoke;
173000
+ function registerRequestor(sessionId, registration) {
173001
+ registry2.set(sessionId, registration);
173002
+ }
173003
+ function unregisterRequestor(sessionId) {
173004
+ registry2.delete(sessionId);
173005
+ const timer = pendingDeliveries.get(sessionId);
173006
+ if (timer) {
173007
+ clearTimeout(timer);
173008
+ pendingDeliveries.delete(sessionId);
173009
+ }
173010
+ }
173011
+ function unregisterAllRequestors() {
173012
+ for (const id of [...registry2.keys()]) unregisterRequestor(id);
173013
+ for (const timer of pendingDeliveries.values()) clearTimeout(timer);
173014
+ pendingDeliveries.clear();
173015
+ }
173016
+ function deliverAnswerDeferred(sessionId, sdp, onGiveUp) {
173017
+ const prior = pendingDeliveries.get(sessionId);
173018
+ if (prior) clearTimeout(prior);
173019
+ const owner = registry2.get(sessionId);
173020
+ const timer = setTimeout(() => {
173021
+ void (async () => {
173022
+ try {
173023
+ for (const delayMs of [0, 250, 500]) {
173024
+ if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
173025
+ if (registry2.get(sessionId) !== owner || pendingDeliveries.get(sessionId) !== timer) {
173026
+ return;
173027
+ }
173028
+ if (await sendAnswer(sessionId, sdp)) {
173029
+ logger244.info(`answer delivered for session ${sessionId}`);
173030
+ return;
173031
+ }
173032
+ }
173033
+ if (registry2.get(sessionId) !== owner || pendingDeliveries.get(sessionId) !== timer) {
173034
+ return;
173035
+ }
173036
+ logger244.info(
173037
+ `answer delivery failed for session ${sessionId}, giving up`
173038
+ );
173039
+ await onGiveUp();
173040
+ } catch (err) {
173041
+ logger244.info(
173042
+ `answer delivery for session ${sessionId} threw: ${errText(err)}`
173043
+ );
173044
+ } finally {
173045
+ if (pendingDeliveries.get(sessionId) === timer) {
173046
+ pendingDeliveries.delete(sessionId);
173047
+ }
173048
+ }
173049
+ })();
173050
+ }, 0);
173051
+ timer.unref?.();
173052
+ pendingDeliveries.set(sessionId, timer);
173053
+ }
173054
+ function hasRequestor(sessionId) {
173055
+ return registry2.has(sessionId);
173056
+ }
173057
+ function sendAnswer(sessionId, sdp) {
173058
+ return invokeRequestor(sessionId, "answer", {
173059
+ webRtcSessionId: sessionId,
173060
+ sdp
173061
+ });
173062
+ }
173063
+ async function invokeRequestor(sessionId, command, fields) {
173064
+ const registration = registry2.get(sessionId);
173065
+ if (!registration) {
173066
+ logger244.info(`requestor ${command}: no session ${sessionId} registered`);
173067
+ return false;
173068
+ }
173069
+ if (registration.session.isClosed) {
173070
+ logger244.info(`requestor ${command}: session ${sessionId} already closed`);
173071
+ return false;
173072
+ }
173073
+ const request = {
173074
+ endpoint: registration.requestorEndpoint,
173075
+ cluster: WebRtcTransportRequestor3.Cluster,
173076
+ command,
173077
+ fields
173078
+ };
173079
+ try {
173080
+ return await invokeTransport({ registration, request });
173081
+ } catch (err) {
173082
+ logger244.info(
173083
+ `requestor ${command} failed for session ${sessionId}: ${errText(err)}`
173084
+ );
173085
+ return false;
173086
+ }
173087
+ }
173088
+ async function defaultInvoke({
173089
+ registration,
173090
+ request
173091
+ }) {
173092
+ const { session, env } = registration;
173093
+ const exchangeManager = env.get(ExchangeManager);
173094
+ const exchangeProvider = new DedicatedChannelExchangeProvider(
173095
+ exchangeManager,
173096
+ session
173097
+ );
173098
+ const client = new ClientInteraction({ environment: env, exchangeProvider });
173099
+ try {
173100
+ const result = client.invoke(
173101
+ Invoke({
173102
+ commands: [
173103
+ {
173104
+ endpoint: request.endpoint,
173105
+ cluster: request.cluster,
173106
+ command: request.command,
173107
+ // biome-ignore lint/suspicious/noExplicitAny: fields typed per command by caller
173108
+ fields: request.fields
173109
+ }
173110
+ ]
173111
+ })
173112
+ );
173113
+ for await (const chunk of result) {
173114
+ for (const entry of chunk) {
173115
+ if (entry.kind === "cmd-status") {
173116
+ if (entry.status !== Status2.Success) {
173117
+ logger244.info(
173118
+ `requestor ${request.command} status ${entry.status} for session ${String(request.fields.webRtcSessionId)}`
173119
+ );
173120
+ return false;
173121
+ }
173122
+ return true;
173123
+ }
173124
+ }
173125
+ }
173126
+ return true;
173127
+ } finally {
173128
+ await client.close().catch(() => {
173129
+ });
173130
+ }
173131
+ }
173132
+ function errText(err) {
173133
+ return err instanceof Error ? err.message : String(err);
173134
+ }
173135
+
173136
+ // src/plugins/builtin/camera/webrtc-provider-server.ts
173137
+ var logger245 = Logger.get("CameraWebRtc");
173138
+ var nextGlobalSessionId = 0;
173139
+ function mintSessionId() {
173140
+ for (let i = 0; i <= 65534; i++) {
173141
+ const id = nextGlobalSessionId;
173142
+ nextGlobalSessionId = nextGlobalSessionId >= 65534 ? 0 : nextGlobalSessionId + 1;
173143
+ if (!hasRequestor(id)) return id;
173144
+ }
173145
+ return nextGlobalSessionId;
173146
+ }
172112
173147
  var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
172113
173148
  solicitOffer(request) {
172114
- const id = this.state.nextSessionId++;
173149
+ const id = mintSessionId();
172115
173150
  this.trackSession(id, request.streamUsage, request.originatingEndpointId);
172116
- void this.state.bridge.startSession(id, this.state.entityId).catch(() => {
172117
- });
173151
+ logger245.info(
173152
+ `solicitOffer session=${id} (${this.state.entityId}), deferred offer`
173153
+ );
173154
+ void this.state.bridge.startSession(id, this.state.entityId, {
173155
+ iceServers: request.iceServers,
173156
+ iceTransportPolicy: request.iceTransportPolicy
173157
+ }).catch(
173158
+ (err) => logger245.info(
173159
+ `solicitOffer startSession failed for ${this.state.entityId}: ${errText2(err)}`
173160
+ )
173161
+ );
172118
173162
  return { webRtcSessionId: id, deferredOffer: true };
172119
173163
  }
172120
173164
  async provideOffer(request) {
172121
- const id = request.webRtcSessionId ?? this.state.nextSessionId++;
173165
+ const id = request.webRtcSessionId ?? mintSessionId();
173166
+ logger245.info(
173167
+ `provideOffer entry: entityId=${this.state.entityId} session=${id} (sdp ${request.sdp.length} chars)`
173168
+ );
172122
173169
  if (request.webRtcSessionId == null) {
172123
173170
  this.trackSession(
172124
173171
  id,
@@ -172126,37 +173173,96 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
172126
173173
  request.originatingEndpointId ?? EndpointNumber(0)
172127
173174
  );
172128
173175
  }
172129
- const answerSdp = await this.state.bridge.acceptControllerOffer(
172130
- id,
172131
- this.state.entityId,
172132
- request.sdp
173176
+ const requestorEndpoint = request.originatingEndpointId ?? EndpointNumber(0);
173177
+ const session = this.context.session;
173178
+ if (session) {
173179
+ registerRequestor(id, {
173180
+ session,
173181
+ requestorEndpoint,
173182
+ env: this.env
173183
+ });
173184
+ }
173185
+ let answerSdp;
173186
+ try {
173187
+ answerSdp = await this.state.bridge.acceptControllerOffer(
173188
+ id,
173189
+ this.state.entityId,
173190
+ request.sdp,
173191
+ {
173192
+ iceServers: request.iceServers,
173193
+ iceTransportPolicy: request.iceTransportPolicy
173194
+ }
173195
+ );
173196
+ } catch (err) {
173197
+ const message = errText2(err);
173198
+ logger245.info(
173199
+ `provideOffer failed for ${this.state.entityId} session=${id}: ${message}`
173200
+ );
173201
+ unregisterRequestor(id);
173202
+ this.state.currentSessions = this.state.currentSessions.filter(
173203
+ (s) => s.id !== id
173204
+ );
173205
+ throw new StatusResponseError(
173206
+ `WebRTC offer failed: ${message}`,
173207
+ StatusCode.Failure
173208
+ );
173209
+ }
173210
+ logger245.info(
173211
+ `provideOffer answer computed for ${this.state.entityId} session=${id} (${answerSdp.length} chars); delivering via requestor`
172133
173212
  );
172134
- void answerSdp;
173213
+ const bridge = this.state.bridge;
173214
+ const state = this.state;
173215
+ deliverAnswerDeferred(id, answerSdp, async () => {
173216
+ await bridge.endSession(id).catch(() => {
173217
+ });
173218
+ unregisterRequestor(id);
173219
+ try {
173220
+ state.currentSessions = state.currentSessions.filter(
173221
+ (s) => s.id !== id
173222
+ );
173223
+ } catch {
173224
+ }
173225
+ });
172135
173226
  return { webRtcSessionId: id };
172136
173227
  }
172137
173228
  provideAnswer(request) {
173229
+ logger245.info(
173230
+ `provideAnswer session=${request.webRtcSessionId} (sdp ${request.sdp.length} chars, ${this.state.entityId})`
173231
+ );
172138
173232
  return this.state.bridge.acceptControllerAnswer(
172139
173233
  request.webRtcSessionId,
172140
173234
  request.sdp
172141
173235
  );
172142
173236
  }
172143
173237
  async provideIceCandidates(request) {
173238
+ logger245.info(
173239
+ `provideIceCandidates session=${request.webRtcSessionId}: ${request.iceCandidates.length} candidate(s) (${this.state.entityId})`
173240
+ );
172144
173241
  for (const c of request.iceCandidates) {
172145
173242
  await this.state.bridge.addControllerIceCandidate(
172146
173243
  request.webRtcSessionId,
172147
173244
  c.candidate,
172148
- c.sdpMid
173245
+ c.sdpMid,
173246
+ c.sdpmLineIndex
172149
173247
  );
172150
173248
  }
172151
173249
  }
172152
173250
  async endSession(request) {
173251
+ logger245.info(
173252
+ `endSession session=${request.webRtcSessionId} (${this.state.entityId})`
173253
+ );
172153
173254
  await this.state.bridge.endSession(request.webRtcSessionId);
173255
+ unregisterRequestor(request.webRtcSessionId);
172154
173256
  this.state.currentSessions = this.state.currentSessions.filter(
172155
173257
  (s) => s.id !== request.webRtcSessionId
172156
173258
  );
172157
173259
  }
172158
173260
  trackSession(id, streamUsage, peerEndpointId) {
172159
173261
  const session = this.context.session;
173262
+ const fabricIndex = session?.associatedFabric?.fabricIndex;
173263
+ if (fabricIndex == null) {
173264
+ return;
173265
+ }
172160
173266
  this.state.currentSessions = [
172161
173267
  ...this.state.currentSessions,
172162
173268
  {
@@ -172165,16 +173271,18 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
172165
173271
  peerEndpointId,
172166
173272
  streamUsage,
172167
173273
  metadataEnabled: false,
172168
- fabricIndex: session?.associatedFabric?.fabricIndex ?? FabricIndex3(0)
173274
+ fabricIndex
172169
173275
  }
172170
173276
  ];
172171
173277
  }
172172
173278
  };
173279
+ function errText2(err) {
173280
+ return err instanceof Error ? err.message : String(err);
173281
+ }
172173
173282
  ((CameraWebRtcProviderServer2) => {
172174
173283
  class State extends WebRtcTransportProviderServer.State {
172175
173284
  bridge;
172176
173285
  entityId;
172177
- nextSessionId = 1;
172178
173286
  }
172179
173287
  CameraWebRtcProviderServer2.State = State;
172180
173288
  })(CameraWebRtcProviderServer || (CameraWebRtcProviderServer = {}));
@@ -172193,8 +173301,7 @@ function createCameraEndpointType(bridge, entityId, sensor = defaultSensorParams
172193
173301
  webRtcTransportProvider: {
172194
173302
  bridge,
172195
173303
  entityId,
172196
- currentSessions: [],
172197
- nextSessionId: 1
173304
+ currentSessions: []
172198
173305
  },
172199
173306
  cameraAvStreamManagement: {
172200
173307
  bridge,
@@ -172256,128 +173363,232 @@ function createCameraEndpointType(bridge, entityId, sensor = defaultSensorParams
172256
173363
  }
172257
173364
 
172258
173365
  // src/plugins/builtin/camera/webrtc-bridge.ts
173366
+ init_esm();
172259
173367
  import {
172260
173368
  createConnection as createConnection3,
172261
173369
  createLongLivedTokenAuth as createLongLivedTokenAuth2
172262
173370
  } from "home-assistant-js-websocket";
172263
173371
  import { RTCPeerConnection } from "werift";
173372
+ var logger246 = Logger.get("CameraWebRtc");
173373
+ var DEFAULT_HA_WEBRTC_TIMEOUT_MS = 15e3;
173374
+ var haWebRtcTimeoutMs = DEFAULT_HA_WEBRTC_TIMEOUT_MS;
172264
173375
  var WebRtcBridge = class {
172265
- constructor(config11) {
173376
+ constructor(config11, deps = {}) {
172266
173377
  this.config = config11;
173378
+ this.deps = deps;
172267
173379
  }
172268
173380
  config;
173381
+ deps;
172269
173382
  connection;
172270
173383
  sessions = /* @__PURE__ */ new Map();
172271
173384
  async ha() {
172272
173385
  if (this.connection) return this.connection;
172273
- const auth = createLongLivedTokenAuth2(
172274
- this.config.haUrl,
172275
- this.config.haToken
172276
- );
172277
- this.connection = await createConnection3({ auth });
173386
+ if (this.deps.connect) {
173387
+ this.connection = await this.deps.connect(this.config);
173388
+ } else {
173389
+ const auth = createLongLivedTokenAuth2(
173390
+ this.config.haUrl,
173391
+ this.config.haToken
173392
+ );
173393
+ this.connection = await createConnection3({ auth });
173394
+ }
172278
173395
  return this.connection;
172279
173396
  }
172280
173397
  // We offer to the controller; it answers. Pulls HA media first, then forwards.
172281
- async startSession(matterSessionId, entityId) {
173398
+ async startSession(matterSessionId, entityId, ice) {
172282
173399
  const haPeer = new RTCPeerConnection();
172283
- const controllerPeer = new RTCPeerConnection();
172284
- haPeer.onTrack.subscribe((track) => {
172285
- const transceiver = controllerPeer.addTransceiver(
172286
- track.kind,
172287
- { direction: "sendonly" }
173400
+ const controllerPeer = new RTCPeerConnection(
173401
+ this.controllerConfig(entityId, ice)
173402
+ );
173403
+ this.wireStateLogging(haPeer, "haPeer", entityId);
173404
+ this.wireStateLogging(controllerPeer, "controllerPeer", entityId);
173405
+ const senders = /* @__PURE__ */ new Map();
173406
+ senders.set(
173407
+ "video",
173408
+ controllerPeer.addTransceiver("video", { direction: "sendonly" })
173409
+ );
173410
+ this.forwardHaTracks(haPeer, senders, entityId);
173411
+ await this.closeExistingPeers(matterSessionId);
173412
+ this.sessions.set(matterSessionId, { entityId, haPeer, controllerPeer });
173413
+ try {
173414
+ haPeer.addTransceiver("video", { direction: "recvonly" });
173415
+ haPeer.addTransceiver("audio", { direction: "recvonly" });
173416
+ const haOffer = await haPeer.createOffer();
173417
+ await haPeer.setLocalDescription(haOffer);
173418
+ const haOfferSdp = this.localSdp(haPeer);
173419
+ logger246.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
173420
+ const { answer, sessionId, unsubscribe } = await this.requestHaWebRtc(
173421
+ entityId,
173422
+ haOfferSdp,
173423
+ haPeer
172288
173424
  );
172289
- track.onReceiveRtp.subscribe((rtp) => transceiver.sender.sendRtp(rtp));
172290
- });
172291
- haPeer.addTransceiver("video", { direction: "recvonly" });
172292
- haPeer.addTransceiver("audio", { direction: "recvonly" });
172293
- const haOffer = await haPeer.createOffer();
172294
- await haPeer.setLocalDescription(haOffer);
172295
- const haAnswer = await this.requestHaWebRtc(entityId, haOffer.sdp);
172296
- await haPeer.setRemoteDescription({ type: "answer", sdp: haAnswer });
172297
- const controllerOffer = await controllerPeer.createOffer();
172298
- await controllerPeer.setLocalDescription(controllerOffer);
172299
- const iceCandidates = [];
172300
- controllerPeer.onIceCandidate.subscribe((c) => {
172301
- if (c?.candidate)
172302
- iceCandidates.push({
172303
- candidate: c.candidate,
172304
- sdpMid: c.sdpMid ?? null
172305
- });
172306
- });
172307
- this.sessions.set(matterSessionId, {
172308
- entityId,
172309
- haPeer,
172310
- controllerPeer
172311
- });
172312
- return { sdp: controllerOffer.sdp, iceCandidates };
173425
+ const session = this.sessions.get(matterSessionId);
173426
+ if (session) {
173427
+ session.haSessionId = sessionId;
173428
+ session.haUnsubscribe = unsubscribe;
173429
+ }
173430
+ await haPeer.setRemoteDescription({ type: "answer", sdp: answer });
173431
+ const iceCandidates = [];
173432
+ controllerPeer.onIceCandidate.subscribe((c) => {
173433
+ if (c?.candidate)
173434
+ iceCandidates.push({
173435
+ candidate: c.candidate,
173436
+ sdpMid: c.sdpMid ?? null
173437
+ });
173438
+ });
173439
+ const controllerOffer = await controllerPeer.createOffer();
173440
+ await controllerPeer.setLocalDescription(controllerOffer);
173441
+ const sdp = this.localSdp(controllerPeer);
173442
+ logger246.info(
173443
+ `controller offer created for ${entityId} (${sdp.length} chars)`
173444
+ );
173445
+ return { sdp, iceCandidates };
173446
+ } catch (err) {
173447
+ logger246.info(`startSession failed for ${entityId}: ${errText3(err)}`);
173448
+ await this.endSession(matterSessionId);
173449
+ throw err;
173450
+ }
172313
173451
  }
172314
173452
  // ProvideOffer flow: controller sent its offer. Pull HA media, answer it.
172315
173453
  // Returning the answer to the controller is the unverified Requestor path.
172316
- async acceptControllerOffer(matterSessionId, entityId, controllerOfferSdp) {
173454
+ async acceptControllerOffer(matterSessionId, entityId, controllerOfferSdp, ice) {
172317
173455
  const haPeer = new RTCPeerConnection();
172318
- const controllerPeer = new RTCPeerConnection();
172319
- haPeer.onTrack.subscribe((track) => {
172320
- const transceiver = controllerPeer.addTransceiver(track.kind, {
172321
- direction: "sendonly"
172322
- });
172323
- track.onReceiveRtp.subscribe((rtp) => transceiver.sender.sendRtp(rtp));
172324
- });
173456
+ const controllerPeer = new RTCPeerConnection(
173457
+ this.controllerConfig(entityId, ice)
173458
+ );
173459
+ this.wireStateLogging(haPeer, "haPeer", entityId);
173460
+ this.wireStateLogging(controllerPeer, "controllerPeer", entityId);
173461
+ const senders = /* @__PURE__ */ new Map();
173462
+ for (const kind of offerKinds(controllerOfferSdp)) {
173463
+ senders.set(
173464
+ kind,
173465
+ controllerPeer.addTransceiver(kind, { direction: "sendonly" })
173466
+ );
173467
+ }
173468
+ this.forwardHaTracks(haPeer, senders, entityId);
173469
+ await this.closeExistingPeers(matterSessionId);
172325
173470
  this.sessions.set(matterSessionId, { entityId, haPeer, controllerPeer });
172326
- haPeer.addTransceiver("video", { direction: "recvonly" });
172327
- haPeer.addTransceiver("audio", { direction: "recvonly" });
172328
- const haOffer = await haPeer.createOffer();
172329
- await haPeer.setLocalDescription(haOffer);
172330
- const haAnswer = await this.requestHaWebRtc(entityId, haOffer.sdp);
172331
- await haPeer.setRemoteDescription({ type: "answer", sdp: haAnswer });
172332
- await controllerPeer.setRemoteDescription({
172333
- type: "offer",
172334
- sdp: controllerOfferSdp
172335
- });
172336
- const answer = await controllerPeer.createAnswer();
172337
- await controllerPeer.setLocalDescription(answer);
172338
- return answer.sdp;
173471
+ try {
173472
+ haPeer.addTransceiver("video", { direction: "recvonly" });
173473
+ haPeer.addTransceiver("audio", { direction: "recvonly" });
173474
+ const haOffer = await haPeer.createOffer();
173475
+ await haPeer.setLocalDescription(haOffer);
173476
+ const haOfferSdp = this.localSdp(haPeer);
173477
+ logger246.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
173478
+ const { answer, sessionId, unsubscribe } = await this.requestHaWebRtc(
173479
+ entityId,
173480
+ haOfferSdp,
173481
+ haPeer
173482
+ );
173483
+ const session = this.sessions.get(matterSessionId);
173484
+ if (session) {
173485
+ session.haSessionId = sessionId;
173486
+ session.haUnsubscribe = unsubscribe;
173487
+ }
173488
+ await haPeer.setRemoteDescription({ type: "answer", sdp: answer });
173489
+ await controllerPeer.setRemoteDescription({
173490
+ type: "offer",
173491
+ sdp: controllerOfferSdp
173492
+ });
173493
+ const controllerAnswer = await controllerPeer.createAnswer();
173494
+ await controllerPeer.setLocalDescription(controllerAnswer);
173495
+ const answerSdp = this.localSdp(controllerPeer);
173496
+ logger246.info(
173497
+ `controller answer created for ${entityId} (${answerSdp.length} chars)`
173498
+ );
173499
+ return answerSdp;
173500
+ } catch (err) {
173501
+ logger246.info(
173502
+ `acceptControllerOffer failed for ${entityId}: ${errText3(err)}`
173503
+ );
173504
+ await this.endSession(matterSessionId);
173505
+ throw err;
173506
+ }
172339
173507
  }
172340
173508
  /** Apply the Matter controller's SDP answer to the controller peer. */
172341
173509
  async acceptControllerAnswer(matterSessionId, sdp) {
172342
173510
  const session = this.sessions.get(matterSessionId);
172343
173511
  if (!session) return;
173512
+ logger246.debug(`controller answer applied for ${session.entityId}`);
172344
173513
  await session.controllerPeer.setRemoteDescription({ type: "answer", sdp });
172345
173514
  }
172346
173515
  /** Add a remote ICE candidate from the Matter controller. */
172347
- async addControllerIceCandidate(matterSessionId, candidate, sdpMid) {
173516
+ async addControllerIceCandidate(matterSessionId, candidate, sdpMid, sdpMLineIndex) {
172348
173517
  const session = this.sessions.get(matterSessionId);
172349
173518
  if (!session) return;
173519
+ logger246.debug(
173520
+ `controller ICE candidate for ${session.entityId}: ${candidate}`
173521
+ );
172350
173522
  await session.controllerPeer.addIceCandidate({
172351
173523
  candidate,
172352
- sdpMid: sdpMid ?? void 0
173524
+ sdpMid: sdpMid ?? void 0,
173525
+ sdpMLineIndex: sdpMLineIndex ?? void 0
173526
+ });
173527
+ }
173528
+ // Close the peers of a session we are about to overwrite. Leaves no map
173529
+ // entry; the caller replaces it with fresh peers under the same id.
173530
+ async closeExistingPeers(matterSessionId) {
173531
+ const prior = this.sessions.get(matterSessionId);
173532
+ if (!prior) return;
173533
+ logger246.info(
173534
+ `replacing session ${matterSessionId} (${prior.entityId}), closing prior peers`
173535
+ );
173536
+ if (prior.haUnsubscribe) {
173537
+ await Promise.resolve(prior.haUnsubscribe()).catch(
173538
+ (err) => logger246.debug(`HA unsubscribe failed: ${errText3(err)}`)
173539
+ );
173540
+ }
173541
+ await prior.haPeer.close().catch(() => {
173542
+ });
173543
+ await prior.controllerPeer.close().catch(() => {
173544
+ });
173545
+ await this.stopHaStream(prior).catch(() => {
172353
173546
  });
172354
173547
  }
172355
173548
  async endSession(matterSessionId) {
172356
173549
  const session = this.sessions.get(matterSessionId);
172357
173550
  if (!session) return;
173551
+ logger246.info(`ending session ${matterSessionId} (${session.entityId})`);
172358
173552
  this.sessions.delete(matterSessionId);
172359
- await session.haPeer.close();
172360
- await session.controllerPeer.close();
172361
- if (session.haSessionId) {
172362
- try {
172363
- const conn = await this.ha();
172364
- await conn.sendMessagePromise({
172365
- type: "camera/webrtc/candidate",
172366
- entity_id: session.entityId,
172367
- session_id: session.haSessionId,
172368
- candidate: { candidate: "" }
172369
- });
172370
- } catch {
172371
- }
173553
+ if (session.haUnsubscribe) {
173554
+ await Promise.resolve(session.haUnsubscribe()).catch(
173555
+ (err) => logger246.debug(`HA unsubscribe failed: ${errText3(err)}`)
173556
+ );
172372
173557
  }
173558
+ await session.haPeer.close().catch(() => {
173559
+ });
173560
+ await session.controllerPeer.close().catch(() => {
173561
+ });
173562
+ await this.stopHaStream(session).catch(() => {
173563
+ });
173564
+ }
173565
+ // Tell HA to stop the stream if we tracked an HA session id. Best effort.
173566
+ async stopHaStream(session) {
173567
+ if (!session.haSessionId) return;
173568
+ const conn = await this.ha();
173569
+ await conn.sendMessagePromise({
173570
+ type: "camera/webrtc/candidate",
173571
+ entity_id: session.entityId,
173572
+ session_id: session.haSessionId,
173573
+ candidate: { candidate: "" }
173574
+ });
172373
173575
  }
172374
173576
  // Grab a still JPEG via HA's camera proxy (for CaptureSnapshot).
172375
173577
  async snapshot(entityId) {
172376
173578
  const url2 = `${this.config.haUrl}/api/camera_proxy/${entityId}`;
172377
- const res = await fetch(url2, {
172378
- headers: { Authorization: `Bearer ${this.config.haToken}` }
172379
- });
173579
+ let res;
173580
+ try {
173581
+ res = await fetch(url2, {
173582
+ headers: { Authorization: `Bearer ${this.config.haToken}` }
173583
+ });
173584
+ } catch (err) {
173585
+ logger246.info(`snapshot fetch failed for ${entityId}: ${errText3(err)}`);
173586
+ throw err;
173587
+ }
172380
173588
  if (!res.ok) {
173589
+ logger246.info(
173590
+ `snapshot fetch failed for ${entityId}: camera_proxy ${res.status}`
173591
+ );
172381
173592
  throw new Error(`HA camera_proxy ${entityId}: ${res.status}`);
172382
173593
  }
172383
173594
  return new Uint8Array(await res.arrayBuffer());
@@ -172389,29 +173600,157 @@ var WebRtcBridge = class {
172389
173600
  this.connection?.close();
172390
173601
  this.connection = void 0;
172391
173602
  }
172392
- // Offer to HA's WebRTC, resolve on the first answer event off the same sub.
172393
- async requestHaWebRtc(entityId, offerSdp) {
173603
+ // Forward every track HA sends into the matching pre-added controller sender.
173604
+ forwardHaTracks(haPeer, senders, entityId) {
173605
+ haPeer.onTrack.subscribe((track) => {
173606
+ logger246.info(`onTrack ${track.kind} from HA (${entityId})`);
173607
+ const transceiver = senders.get(track.kind);
173608
+ if (!transceiver) {
173609
+ logger246.info(
173610
+ `no controller transceiver for ${track.kind} (${entityId})`
173611
+ );
173612
+ return;
173613
+ }
173614
+ let firstLogged = false;
173615
+ track.onReceiveRtp.subscribe((rtp) => {
173616
+ if (!firstLogged) {
173617
+ firstLogged = true;
173618
+ logger246.info(`first RTP forwarded for ${track.kind} (${entityId})`);
173619
+ }
173620
+ transceiver.sender.sendRtp(rtp);
173621
+ });
173622
+ });
173623
+ }
173624
+ // Log connection/ice state transitions (these events fire on change only).
173625
+ wireStateLogging(peer, label, entityId) {
173626
+ peer.connectionStateChange.subscribe(
173627
+ (s) => logger246.info(`${label} connectionState=${s} (${entityId})`)
173628
+ );
173629
+ peer.iceConnectionStateChange.subscribe(
173630
+ (s) => logger246.info(`${label} iceConnectionState=${s} (${entityId})`)
173631
+ );
173632
+ }
173633
+ // Map the Matter ICE struct onto werift's config. Only the controller peer
173634
+ // uses controller-supplied servers; the HA peer keeps werift defaults.
173635
+ controllerConfig(entityId, ice) {
173636
+ const iceServers = (ice?.iceServers ?? []).flatMap(
173637
+ (s) => (s.urLs ?? []).map((url2) => ({
173638
+ urls: url2,
173639
+ username: s.username,
173640
+ credential: s.credential
173641
+ }))
173642
+ );
173643
+ if (iceServers.length === 0) {
173644
+ logger246.info(`controller peer using default ICE servers (${entityId})`);
173645
+ return void 0;
173646
+ }
173647
+ const policy = ice?.iceTransportPolicy === "relay" || ice?.iceTransportPolicy === "all" ? ice.iceTransportPolicy : void 0;
173648
+ logger246.info(
173649
+ `controller peer using ${iceServers.length} controller ICE server(s)${policy ? ` policy=${policy}` : ""} (${entityId})`
173650
+ );
173651
+ return policy ? { iceServers, iceTransportPolicy: policy } : { iceServers };
173652
+ }
173653
+ localSdp(peer) {
173654
+ const local = peer.localDescription;
173655
+ if (!local) throw new Error("no local description");
173656
+ return local.sdp;
173657
+ }
173658
+ // Feed an HA trickle-ICE candidate into the HA peer.
173659
+ feedHaCandidate(entityId, haPeer, candidate) {
173660
+ const value = candidate?.candidate;
173661
+ if (!value) return;
173662
+ logger246.debug(`HA ICE candidate for ${entityId}: ${value}`);
173663
+ void haPeer.addIceCandidate({
173664
+ candidate: value,
173665
+ sdpMid: candidate?.sdpMid ?? void 0,
173666
+ sdpMLineIndex: candidate?.sdpMLineIndex ?? void 0
173667
+ }).catch(
173668
+ (e) => logger246.debug(
173669
+ `HA addIceCandidate failed for ${entityId}: ${errText3(e)}`
173670
+ )
173671
+ );
173672
+ }
173673
+ // Offer to HA's WebRTC and resolve on the first answer. Rejects on an error
173674
+ // reply or after a timeout; keeps the subscription open past the answer so HA
173675
+ // trickle candidates keep flowing into the HA peer until the session ends.
173676
+ async requestHaWebRtc(entityId, offerSdp, haPeer) {
172394
173677
  const conn = await this.ha();
172395
- return new Promise((resolve11, reject) => {
173678
+ let unsubscribe = () => {
173679
+ };
173680
+ let sessionId;
173681
+ const answer = await new Promise((resolve11, reject) => {
172396
173682
  let settled = false;
173683
+ let rejected = false;
173684
+ let timer;
173685
+ const settle = (fn) => {
173686
+ if (settled) return;
173687
+ settled = true;
173688
+ clearTimeout(timer);
173689
+ fn();
173690
+ };
173691
+ const fail = (err) => settle(() => {
173692
+ rejected = true;
173693
+ unsubscribe();
173694
+ reject(err);
173695
+ });
173696
+ timer = setTimeout(() => {
173697
+ logger246.info(
173698
+ `HA WebRTC timed out after ${haWebRtcTimeoutMs}ms for ${entityId}`
173699
+ );
173700
+ fail(
173701
+ new Error(`HA WebRTC request timed out after ${haWebRtcTimeoutMs}ms`)
173702
+ );
173703
+ }, haWebRtcTimeoutMs);
172397
173704
  conn.subscribeMessage(
172398
173705
  (msg) => {
172399
- if (msg.type === "answer" && msg.answer && !settled) {
172400
- settled = true;
172401
- const session = [...this.sessions.values()].find(
172402
- (s) => s.entityId === entityId
172403
- );
172404
- if (session && msg.session_id) {
172405
- session.haSessionId = msg.session_id;
173706
+ switch (msg.type) {
173707
+ case "session":
173708
+ if (msg.session_id) sessionId = msg.session_id;
173709
+ break;
173710
+ case "answer":
173711
+ if (msg.answer) {
173712
+ if (msg.session_id) sessionId = msg.session_id;
173713
+ const ans = msg.answer;
173714
+ settle(() => {
173715
+ logger246.info(
173716
+ `HA answer received for ${entityId} (${ans.length} chars)`
173717
+ );
173718
+ resolve11(ans);
173719
+ });
173720
+ }
173721
+ break;
173722
+ case "error": {
173723
+ const detail = msg.message ?? msg.code ?? "unknown error";
173724
+ logger246.info(`HA WebRTC error for ${entityId}: ${detail}`);
173725
+ fail(new Error(`HA WebRTC error: ${detail}`));
173726
+ break;
172406
173727
  }
172407
- resolve11(msg.answer);
173728
+ case "candidate":
173729
+ this.feedHaCandidate(entityId, haPeer, msg.candidate);
173730
+ break;
172408
173731
  }
172409
173732
  },
172410
173733
  { type: "camera/webrtc/offer", entity_id: entityId, offer: offerSdp }
172411
- ).catch(reject);
173734
+ ).then((unsub) => {
173735
+ unsubscribe = unsub;
173736
+ if (rejected) unsub();
173737
+ }).catch((err) => {
173738
+ fail(err instanceof Error ? err : new Error(String(err)));
173739
+ });
172412
173740
  });
173741
+ return { answer, sessionId, unsubscribe };
172413
173742
  }
172414
173743
  };
173744
+ function offerKinds(sdp) {
173745
+ const kinds = [];
173746
+ for (const m of sdp.matchAll(/^m=(video|audio)/gm)) {
173747
+ kinds.push(m[1]);
173748
+ }
173749
+ return kinds;
173750
+ }
173751
+ function errText3(err) {
173752
+ return err instanceof Error ? err.message : String(err);
173753
+ }
172415
173754
 
172416
173755
  // src/plugins/builtin/camera/camera-plugin.ts
172417
173756
  var CONFIG_KEY = "config";
@@ -172544,6 +173883,7 @@ var CameraPlugin = class {
172544
173883
  await this.bridge?.close().catch(() => {
172545
173884
  });
172546
173885
  this.bridge = void 0;
173886
+ unregisterAllRequestors();
172547
173887
  }
172548
173888
  };
172549
173889
 
@@ -172557,7 +173897,7 @@ import {
172557
173897
  getCollection
172558
173898
  } from "home-assistant-js-websocket";
172559
173899
  import { atLeastHaVersion } from "home-assistant-js-websocket/dist/util.js";
172560
- var logger243 = Logger.get("SubscribeEntities");
173900
+ var logger247 = Logger.get("SubscribeEntities");
172561
173901
  function processEvent(store, updates) {
172562
173902
  const state = { ...store.state };
172563
173903
  if (updates.a) {
@@ -172583,7 +173923,7 @@ function processEvent(store, updates) {
172583
173923
  for (const entityId in updates.c) {
172584
173924
  let entityState = state[entityId];
172585
173925
  if (!entityState) {
172586
- logger243.warn("Received state update for unknown entity", entityId);
173926
+ logger247.warn("Received state update for unknown entity", entityId);
172587
173927
  continue;
172588
173928
  }
172589
173929
  entityState = { ...entityState };
@@ -172672,7 +174012,7 @@ var subscribeEntities = (conn, onChange, entityIds) => {
172672
174012
  // src/services/bridges/entity-isolation-service.ts
172673
174013
  init_esm();
172674
174014
  init_diagnostic_event_bus();
172675
- var logger244 = Logger.get("EntityIsolation");
174015
+ var logger248 = Logger.get("EntityIsolation");
172676
174016
  var EntityIsolationServiceImpl = class {
172677
174017
  isolatedEntities = /* @__PURE__ */ new Map();
172678
174018
  isolationCallbacks = /* @__PURE__ */ new Map();
@@ -172737,13 +174077,13 @@ var EntityIsolationServiceImpl = class {
172737
174077
  }
172738
174078
  const parsed = this.parseEndpointPath(msg);
172739
174079
  if (!parsed) {
172740
- logger244.warn("Could not parse entity from error:", msg);
174080
+ logger248.warn("Could not parse entity from error:", msg);
172741
174081
  return false;
172742
174082
  }
172743
174083
  const { bridgeId, entityName } = parsed;
172744
174084
  const callback = this.isolationCallbacks.get(bridgeId);
172745
174085
  if (!callback) {
172746
- logger244.warn(
174086
+ logger248.warn(
172747
174087
  `No isolation callback registered for bridge ${bridgeId}, entity: ${entityName}`
172748
174088
  );
172749
174089
  return false;
@@ -172758,7 +174098,7 @@ var EntityIsolationServiceImpl = class {
172758
174098
  reason,
172759
174099
  failedAt: (/* @__PURE__ */ new Date()).toISOString()
172760
174100
  });
172761
- logger244.warn(
174101
+ logger248.warn(
172762
174102
  `Isolating entity "${entityName}" from bridge ${bridgeId} due to: ${reason}`
172763
174103
  );
172764
174104
  diagnosticEventBus.emit("entity_error", `Entity isolated: ${entityName}`, {
@@ -172770,7 +174110,7 @@ var EntityIsolationServiceImpl = class {
172770
174110
  await callback(entityName);
172771
174111
  return true;
172772
174112
  } catch (e) {
172773
- logger244.error(`Failed to isolate entity ${entityName}:`, e);
174113
+ logger248.error(`Failed to isolate entity ${entityName}:`, e);
172774
174114
  return false;
172775
174115
  }
172776
174116
  }
@@ -172799,128 +174139,16 @@ var EntityIsolationServiceImpl = class {
172799
174139
  };
172800
174140
  var EntityIsolationService = new EntityIsolationServiceImpl();
172801
174141
 
172802
- // src/services/bridges/identity-resolver.ts
172803
- var SEP = "\0";
172804
- function identityKey(entity) {
172805
- const uniqueId = entity.registry?.unique_id;
172806
- const platform = entity.registry?.platform;
172807
- if (!uniqueId || !platform) {
172808
- return null;
172809
- }
172810
- const domain = entity.entity_id.split(".")[0];
172811
- return platform + SEP + domain + SEP + uniqueId;
172812
- }
172813
- var IdentityResolver = class {
172814
- constructor(identityStorage, mappingStorage) {
172815
- this.identityStorage = identityStorage;
172816
- this.mappingStorage = mappingStorage;
172817
- }
172818
- identityStorage;
172819
- mappingStorage;
172820
- async resolveIdentity(bridgeId, entity, mapping, opts) {
172821
- const entityId = entity.entity_id;
172822
- const key = identityKey(entity);
172823
- if (key == null) {
172824
- return {
172825
- endpointId: createEndpointId(entityId, mapping?.customName),
172826
- anchorEntityId: entityId,
172827
- protected: false
172828
- };
172829
- }
172830
- const existing = this.identityStorage.getIdentity(bridgeId, key);
172831
- if (existing) {
172832
- const renamedFrom = existing.lastEntityId !== entityId ? existing.lastEntityId ?? existing.anchorEntityId : void 0;
172833
- if (opts.stableIdentity) {
172834
- if (renamedFrom) {
172835
- this.identityStorage.setIdentity(bridgeId, key, {
172836
- ...existing,
172837
- lastEntityId: entityId
172838
- });
172839
- await this.rekeyMapping(bridgeId, renamedFrom, entityId);
172840
- }
172841
- return {
172842
- endpointId: existing.endpointId,
172843
- anchorEntityId: existing.anchorEntityId,
172844
- protected: true,
172845
- renamedFrom
172846
- };
172847
- }
172848
- const liveEndpointId = createEndpointId(entityId, mapping?.customName);
172849
- if (existing.endpointId !== liveEndpointId || existing.anchorEntityId !== entityId || existing.lastEntityId !== entityId) {
172850
- this.identityStorage.setIdentity(bridgeId, key, {
172851
- ...existing,
172852
- endpointId: liveEndpointId,
172853
- anchorEntityId: entityId,
172854
- lastEntityId: entityId
172855
- });
172856
- }
172857
- return {
172858
- endpointId: liveEndpointId,
172859
- anchorEntityId: entityId,
172860
- protected: false,
172861
- renamedFrom
172862
- };
172863
- }
172864
- const desired = createEndpointId(entityId, mapping?.customName);
172865
- let endpointId = desired;
172866
- if (opts.stableIdentity) {
172867
- let n = 2;
172868
- while (opts.isEndpointIdTaken(endpointId, key)) {
172869
- endpointId = `${desired}_${n++}`;
172870
- }
172871
- }
172872
- const record = {
172873
- endpointId,
172874
- anchorEntityId: entityId,
172875
- lastEntityId: entityId,
172876
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
172877
- };
172878
- this.identityStorage.setIdentity(bridgeId, key, record);
172879
- if (opts.stableIdentity) {
172880
- return {
172881
- endpointId: record.endpointId,
172882
- anchorEntityId: record.anchorEntityId,
172883
- protected: true
172884
- };
172885
- }
172886
- return {
172887
- endpointId: desired,
172888
- anchorEntityId: entityId,
172889
- protected: false
172890
- };
172891
- }
172892
- // Carry a mapping from the old entity_id to the new one on rename. anchorEntityId
172893
- // never changes, only the mapping key. Skips when no old mapping exists or a new
172894
- // one already does, so a manual mapping under the new id is never clobbered.
172895
- async rekeyMapping(bridgeId, oldEntityId, newEntityId) {
172896
- if (oldEntityId === newEntityId) {
172897
- return;
172898
- }
172899
- const old = this.mappingStorage.getMapping(bridgeId, oldEntityId);
172900
- if (!old) {
172901
- return;
172902
- }
172903
- if (this.mappingStorage.getMapping(bridgeId, newEntityId)) {
172904
- return;
172905
- }
172906
- await this.mappingStorage.setMapping({
172907
- ...old,
172908
- bridgeId,
172909
- entityId: newEntityId
172910
- });
172911
- await this.mappingStorage.deleteMapping(bridgeId, oldEntityId);
172912
- }
172913
- };
172914
-
172915
174142
  // src/services/bridges/bridge-endpoint-manager.ts
172916
174143
  var MAX_ENTITY_ID_LENGTH = 150;
172917
174144
  var ENDPOINT_REMOVAL_GRACE_MS = 6e4;
172918
174145
  var BridgeEndpointManager = class extends Service {
172919
- constructor(client, registry2, mappingStorage, identityStorage, bridgeId, log, pluginManager, pluginRegistry, pluginInstaller) {
174146
+ constructor(client, registry3, mappingStorage, identityStorage, bridgeId, log, pluginManager, pluginRegistry, pluginInstaller) {
172920
174147
  super("BridgeEndpointManager");
172921
174148
  this.client = client;
172922
- this.registry = registry2;
174149
+ this.registry = registry3;
172923
174150
  this.mappingStorage = mappingStorage;
174151
+ this.identityStorage = identityStorage;
172924
174152
  this.bridgeId = bridgeId;
172925
174153
  this.log = log;
172926
174154
  this.pluginManager = pluginManager;
@@ -172942,6 +174170,7 @@ var BridgeEndpointManager = class extends Service {
172942
174170
  client;
172943
174171
  registry;
172944
174172
  mappingStorage;
174173
+ identityStorage;
172945
174174
  bridgeId;
172946
174175
  log;
172947
174176
  pluginManager;
@@ -173339,6 +174568,17 @@ var BridgeEndpointManager = class extends Service {
173339
174568
  this.mappingFingerprints.set(entityId, fp);
173340
174569
  }
173341
174570
  }
174571
+ const fullEntities = this.registry.fullEntities;
174572
+ stampIdentityPresence(
174573
+ this.identityStorage,
174574
+ this.bridgeId,
174575
+ buildPresentIdentityKeys(fullEntities)
174576
+ );
174577
+ stampMappingPresence(
174578
+ this.mappingStorage,
174579
+ this.bridgeId,
174580
+ buildPresentEntityIds(fullEntities)
174581
+ );
173342
174582
  const existingEndpoints = [];
173343
174583
  const now = Date.now();
173344
174584
  for (const endpoint of endpoints) {
@@ -173617,8 +174857,8 @@ init_send_ha_message();
173617
174857
  import { callService as callService2 } from "home-assistant-js-websocket";
173618
174858
  import { keys as keys2, pickBy, values as values3 } from "lodash-es";
173619
174859
  var BridgeRegistry = class _BridgeRegistry {
173620
- constructor(registry2, dataProvider, client) {
173621
- this.registry = registry2;
174860
+ constructor(registry3, dataProvider, client) {
174861
+ this.registry = registry3;
173622
174862
  this.dataProvider = dataProvider;
173623
174863
  this.client = client;
173624
174864
  this.refresh();
@@ -173658,6 +174898,11 @@ var BridgeRegistry = class _BridgeRegistry {
173658
174898
  initialState(entityId) {
173659
174899
  return this._states[entityId];
173660
174900
  }
174901
+ // The complete HA entity set (unfiltered). Used by orphan tombstone stamping
174902
+ // so a filter change or a scope narrowing never looks like a removal.
174903
+ get fullEntities() {
174904
+ return this.registry.entities;
174905
+ }
173661
174906
  // composed sub-entities may sit outside the bridge filter (#408), so these
173662
174907
  // fall back to the full HA registry. keep them separate from the strict
173663
174908
  // accessors above, every other caller must stay filtered.
@@ -174375,6 +175620,7 @@ init_dist();
174375
175620
  init_esm7();
174376
175621
  import * as os10 from "node:os";
174377
175622
  init_diagnostic_event_bus();
175623
+ var imWrapMarker2 = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
174378
175624
  var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
174379
175625
  var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2 = 2500;
174380
175626
  var MDNS_ADDRESS_CHECK_INTERVAL_MS2 = 6e4;
@@ -174382,11 +175628,11 @@ function makeWarmStartState(state, now = (/* @__PURE__ */ new Date()).toISOStrin
174382
175628
  return { ...state, last_updated: now };
174383
175629
  }
174384
175630
  var ServerModeBridge = class {
174385
- constructor(logger247, dataProvider, endpointManager, server) {
175631
+ constructor(logger251, dataProvider, endpointManager, server) {
174386
175632
  this.dataProvider = dataProvider;
174387
175633
  this.endpointManager = endpointManager;
174388
175634
  this.server = server;
174389
- this.log = logger247.get(`ServerModeBridge / ${dataProvider.id}`);
175635
+ this.log = logger251.get(`ServerModeBridge / ${dataProvider.id}`);
174390
175636
  }
174391
175637
  dataProvider;
174392
175638
  endpointManager;
@@ -174407,6 +175653,12 @@ var ServerModeBridge = class {
174407
175653
  sessionStartedAt = /* @__PURE__ */ new Map();
174408
175654
  rotationTimer = null;
174409
175655
  maxSessionAgeMs = 0;
175656
+ // Wedge watchdog: last inbound Interaction Model request time per session and
175657
+ // last time the watchdog rotated it, both keyed by the long-lived session
175658
+ // object so they clear when the session goes away.
175659
+ lastImRequestAt = /* @__PURE__ */ new WeakMap();
175660
+ wedgeLastRotatedAt = /* @__PURE__ */ new WeakMap();
175661
+ wedgeWatchdogTimer = null;
174410
175662
  // Watches the advertised interface addresses so a dynamic ISP IPv6 prefix
174411
175663
  // change forces a fresh operational announcement (#415).
174412
175664
  mdnsAddressTimer = null;
@@ -174470,6 +175722,7 @@ var ServerModeBridge = class {
174470
175722
  const sessionList = sessions.map((s) => {
174471
175723
  const subCount = s.subscriptions.size;
174472
175724
  totalSubscriptions += subCount;
175725
+ const subscriptions = summarizeSubscriptions(s.subscriptions);
174473
175726
  const fi = typeof s.fabric?.fabricIndex === "number" ? s.fabric.fabricIndex : null;
174474
175727
  if (fi !== null) {
174475
175728
  const existing = fabricMap.get(fi) ?? {
@@ -174483,14 +175736,18 @@ var ServerModeBridge = class {
174483
175736
  const nowMs = Date.now();
174484
175737
  const lastActiveMsAgo = typeof s.activeTimestamp === "number" && s.activeTimestamp > 0 ? nowMs - s.activeTimestamp : null;
174485
175738
  const lastAnyActivityMsAgo = typeof s.timestamp === "number" ? nowMs - s.timestamp : null;
175739
+ const lastImAt = this.lastImRequestAt.get(s);
175740
+ const lastImRequestMsAgo = lastImAt != null ? nowMs - lastImAt : null;
174486
175741
  const startedAt = this.sessionStartedAt.get(s.id);
174487
175742
  return {
174488
175743
  id: s.id,
174489
175744
  peerNodeId: String(s.peerNodeId),
174490
175745
  fabricIndex: fi,
174491
175746
  subscriptionCount: subCount,
175747
+ subscriptions,
174492
175748
  lastActiveMsAgo,
174493
175749
  lastAnyActivityMsAgo,
175750
+ lastImRequestMsAgo,
174494
175751
  isPeerActive: Boolean(s.isPeerActive),
174495
175752
  ageMsFromOpen: startedAt != null ? nowMs - startedAt : null
174496
175753
  };
@@ -174554,6 +175811,7 @@ var ServerModeBridge = class {
174554
175811
  this.wireSessionDiagnostics();
174555
175812
  this.wireFabricWarnings();
174556
175813
  this.startSessionRotation();
175814
+ this.startWedgeWatchdog();
174557
175815
  this.startMdnsAddressWatch();
174558
175816
  this.scheduleWarmStart();
174559
175817
  logMemoryUsage(this.log, "server mode bridge running");
@@ -174571,6 +175829,7 @@ ${e?.toString()}`);
174571
175829
  }
174572
175830
  async stop(code = BridgeStatus.Stopped, reason = "Manually stopped") {
174573
175831
  this.stopSessionRotation();
175832
+ this.stopWedgeWatchdog();
174574
175833
  this.stopMdnsAddressWatch();
174575
175834
  this.unwireSessionDiagnostics();
174576
175835
  this.unwireFabricWarnings();
@@ -174603,6 +175862,7 @@ ${e?.toString()}`);
174603
175862
  if (this.status.code === BridgeStatus.Running) {
174604
175863
  this.startAutoForceSyncIfEnabled();
174605
175864
  this.startSessionRotation();
175865
+ this.startWedgeWatchdog();
174606
175866
  }
174607
175867
  } catch (e) {
174608
175868
  const reason = "Failed to update server mode bridge due to error:";
@@ -174787,6 +176047,29 @@ ${e?.toString()}`);
174787
176047
  sessionManager.sessions.added.on(this.sessionAddedHandler);
174788
176048
  sessionManager.sessions.deleted.on(this.sessionDeletedHandler);
174789
176049
  seedExistingSessionStarts(this.sessionStartedAt, sessionManager.sessions);
176050
+ this.wireImRequestTracking();
176051
+ } catch {
176052
+ }
176053
+ }
176054
+ // Stamp the time of every inbound Interaction Model request per session by
176055
+ // wrapping InteractionServer.onNewExchange. The wedge watchdog reads these to
176056
+ // tell a live-but-consuming controller from one that only keeps acking.
176057
+ wireImRequestTracking() {
176058
+ try {
176059
+ const is = this.server.env.get(InteractionServer);
176060
+ const marked = is;
176061
+ if (marked[imWrapMarker2]) {
176062
+ return;
176063
+ }
176064
+ const original = is.onNewExchange.bind(is);
176065
+ is.onNewExchange = (exchange, message) => {
176066
+ const session = exchange.session;
176067
+ if (session) {
176068
+ this.lastImRequestAt.set(session, Date.now());
176069
+ }
176070
+ return original(exchange, message);
176071
+ };
176072
+ marked[imWrapMarker2] = true;
174790
176073
  } catch {
174791
176074
  }
174792
176075
  }
@@ -175004,6 +176287,72 @@ ${e?.toString()}`);
175004
176287
  this.rotationTimer = null;
175005
176288
  }
175006
176289
  }
176290
+ // Opt-in wedge watchdog: reuse the rotation check cadence (5min) to look for
176291
+ // the one session wedged on "Updating" and rotate just that one.
176292
+ startWedgeWatchdog() {
176293
+ this.stopWedgeWatchdog();
176294
+ if (!this.dataProvider.featureFlags?.wedgeWatchdog) {
176295
+ return;
176296
+ }
176297
+ this.wedgeWatchdogTimer = setInterval(
176298
+ () => this.runWedgeWatchdogCheck(),
176299
+ ROTATION_CHECK_INTERVAL_MS
176300
+ );
176301
+ this.log.info(
176302
+ `Wedge watchdog: checking every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
176303
+ );
176304
+ }
176305
+ stopWedgeWatchdog() {
176306
+ if (this.wedgeWatchdogTimer) {
176307
+ clearInterval(this.wedgeWatchdogTimer);
176308
+ this.wedgeWatchdogTimer = null;
176309
+ }
176310
+ }
176311
+ // Rotate exactly the sessions the pure rule flags as wedged. Closing is the
176312
+ // same graceful-then-force path age rotation uses, so a false positive just
176313
+ // re-CASEs the controller.
176314
+ runWedgeWatchdogCheck() {
176315
+ try {
176316
+ const sessionManager = this.server.env.get(SessionManager);
176317
+ const now = Date.now();
176318
+ const closes = [];
176319
+ for (const s of [...sessionManager.sessions]) {
176320
+ if (s.isClosing) continue;
176321
+ const startedAt = this.sessionStartedAt.get(s.id);
176322
+ const sessionAgeMs = startedAt != null ? now - startedAt : 0;
176323
+ const lastImAt = this.lastImRequestAt.get(s);
176324
+ const lastImRequestMsAgo = lastImAt != null ? now - lastImAt : null;
176325
+ const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
176326
+ const lastRotatedMsAgo = lastRotatedAt != null ? now - lastRotatedAt : null;
176327
+ if (!decideWedgeRotation({
176328
+ subscriptionCount: s.subscriptions.size,
176329
+ sessionAgeMs,
176330
+ lastImRequestMsAgo,
176331
+ lastRotatedMsAgo
176332
+ })) {
176333
+ continue;
176334
+ }
176335
+ const silenceMin = Math.round(
176336
+ (lastImRequestMsAgo ?? sessionAgeMs) / 6e4
176337
+ );
176338
+ this.log.info(
176339
+ `Wedge watchdog: rotating session ${s.id}, no inbound interaction for ${silenceMin}min`
176340
+ );
176341
+ this.wedgeLastRotatedAt.set(s, now);
176342
+ closes.push(
176343
+ s.initiateClose().catch(() => {
176344
+ return s.initiateForceClose({
176345
+ cause: new Error("wedge watchdog, forcing")
176346
+ });
176347
+ })
176348
+ );
176349
+ }
176350
+ if (closes.length > 0) {
176351
+ Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
176352
+ }
176353
+ } catch {
176354
+ }
176355
+ }
175007
176356
  // Poll the interface addresses mDNS advertises and re-announce when they
175008
176357
  // change. matter.js caches the operational records at first announcement, so
175009
176358
  // a dynamic ISP IPv6 prefix change keeps advertising the dead global address
@@ -175313,12 +176662,12 @@ function ServerModeVacuumDevice(homeAssistantEntity, includeOnOff = false, clean
175313
176662
  }
175314
176663
 
175315
176664
  // src/matter/endpoints/server-mode-vacuum-endpoint.ts
175316
- var logger245 = Logger.get("ServerModeVacuumEndpoint");
176665
+ var logger249 = Logger.get("ServerModeVacuumEndpoint");
175317
176666
  var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEndpoint {
175318
- static async create(registry2, entityId, mapping, endpointId) {
175319
- const deviceRegistry = registry2.deviceOf(entityId);
175320
- let state = registry2.initialState(entityId);
175321
- const entity = registry2.entity(entityId);
176667
+ static async create(registry3, entityId, mapping, endpointId) {
176668
+ const deviceRegistry = registry3.deviceOf(entityId);
176669
+ let state = registry3.initialState(entityId);
176670
+ const entity = registry3.entity(entityId);
175322
176671
  if (!state) {
175323
176672
  return void 0;
175324
176673
  }
@@ -175326,12 +176675,12 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175326
176675
  if (mapping?.disableBatteryMapping && mapping.batteryEntity) {
175327
176676
  effectiveMapping = { ...mapping, batteryEntity: void 0 };
175328
176677
  }
175329
- logger245.info(
176678
+ logger249.info(
175330
176679
  `${entityId}: device_id=${entity.device_id}, manualBattery=${mapping?.batteryEntity ?? "none"}`
175331
176680
  );
175332
176681
  if (entity.device_id) {
175333
176682
  if (!mapping?.batteryEntity && !mapping?.disableBatteryMapping) {
175334
- const batteryEntityId = registry2.findBatteryEntityForDevice(
176683
+ const batteryEntityId = registry3.findBatteryEntityForDevice(
175335
176684
  entity.device_id
175336
176685
  );
175337
176686
  if (batteryEntityId && batteryEntityId !== entityId) {
@@ -175340,22 +176689,22 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175340
176689
  entityId: effectiveMapping?.entityId ?? entityId,
175341
176690
  batteryEntity: batteryEntityId
175342
176691
  };
175343
- registry2.markBatteryEntityUsed(batteryEntityId);
175344
- logger245.info(`${entityId}: Auto-assigned battery ${batteryEntityId}`);
176692
+ registry3.markBatteryEntityUsed(batteryEntityId);
176693
+ logger249.info(`${entityId}: Auto-assigned battery ${batteryEntityId}`);
175345
176694
  } else {
175346
176695
  const attrs = state.attributes;
175347
176696
  if (attrs.battery_level != null || attrs.battery != null) {
175348
- logger245.info(
176697
+ logger249.info(
175349
176698
  `${entityId}: No battery entity found, using battery attribute from vacuum state`
175350
176699
  );
175351
176700
  } else {
175352
- logger245.warn(
176701
+ logger249.warn(
175353
176702
  `${entityId}: No battery entity found for device ${entity.device_id}`
175354
176703
  );
175355
176704
  }
175356
176705
  }
175357
176706
  }
175358
- const vacuumEntities = registry2.findVacuumSelectEntities(
176707
+ const vacuumEntities = registry3.findVacuumSelectEntities(
175359
176708
  entity.device_id
175360
176709
  );
175361
176710
  if (!effectiveMapping?.cleaningModeEntity && vacuumEntities.cleaningModeEntity) {
@@ -175364,7 +176713,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175364
176713
  entityId: effectiveMapping?.entityId ?? entityId,
175365
176714
  cleaningModeEntity: vacuumEntities.cleaningModeEntity
175366
176715
  };
175367
- logger245.info(
176716
+ logger249.info(
175368
176717
  `${entityId}: Auto-assigned cleaningMode ${vacuumEntities.cleaningModeEntity}`
175369
176718
  );
175370
176719
  }
@@ -175374,7 +176723,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175374
176723
  entityId: effectiveMapping?.entityId ?? entityId,
175375
176724
  suctionLevelEntity: vacuumEntities.suctionLevelEntity
175376
176725
  };
175377
- logger245.info(
176726
+ logger249.info(
175378
176727
  `${entityId}: Auto-assigned suctionLevel ${vacuumEntities.suctionLevelEntity}`
175379
176728
  );
175380
176729
  }
@@ -175384,7 +176733,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175384
176733
  entityId: effectiveMapping?.entityId ?? entityId,
175385
176734
  mopIntensityEntity: vacuumEntities.mopIntensityEntity
175386
176735
  };
175387
- logger245.info(
176736
+ logger249.info(
175388
176737
  `${entityId}: Auto-assigned mopIntensity ${vacuumEntities.mopIntensityEntity}`
175389
176738
  );
175390
176739
  }
@@ -175394,12 +176743,12 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175394
176743
  entityId: effectiveMapping?.entityId ?? entityId,
175395
176744
  currentRoomEntity: vacuumEntities.currentRoomEntity
175396
176745
  };
175397
- logger245.info(
176746
+ logger249.info(
175398
176747
  `${entityId}: Auto-assigned currentRoom ${vacuumEntities.currentRoomEntity}`
175399
176748
  );
175400
176749
  }
175401
176750
  const supportedFeatures = state.attributes.supported_features ?? 0;
175402
- const cleanAreaRooms = await registry2.resolveCleanAreaRooms(
176751
+ const cleanAreaRooms = await registry3.resolveCleanAreaRooms(
175403
176752
  entityId,
175404
176753
  supportedFeatures
175405
176754
  );
@@ -175409,13 +176758,13 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175409
176758
  entityId: effectiveMapping?.entityId ?? entityId,
175410
176759
  cleanAreaRooms
175411
176760
  };
175412
- logger245.info(
176761
+ logger249.info(
175413
176762
  `${entityId}: Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA`
175414
176763
  );
175415
176764
  }
175416
176765
  const vacAttrs = state.attributes;
175417
176766
  if (cleanAreaRooms.length === 0 && !vacAttrs.rooms && !vacAttrs.segments && !vacAttrs.room_mapping) {
175418
- const valetudoRooms = registry2.findValetudoMapSegments(
176767
+ const valetudoRooms = registry3.findValetudoMapSegments(
175419
176768
  entity.device_id
175420
176769
  );
175421
176770
  if (valetudoRooms.length > 0) {
@@ -175430,11 +176779,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175430
176779
  rooms: roomsObj
175431
176780
  }
175432
176781
  };
175433
- logger245.info(
176782
+ logger249.info(
175434
176783
  `${entityId}: Auto-detected ${valetudoRooms.length} Valetudo segments`
175435
176784
  );
175436
176785
  } else {
175437
- const roborockRooms = await registry2.resolveRoborockRooms(entityId);
176786
+ const roborockRooms = await registry3.resolveRoborockRooms(entityId);
175438
176787
  if (roborockRooms.length > 0) {
175439
176788
  const roomsObj = {};
175440
176789
  for (const r of roborockRooms) {
@@ -175447,14 +176796,14 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175447
176796
  rooms: roomsObj
175448
176797
  }
175449
176798
  };
175450
- logger245.info(
176799
+ logger249.info(
175451
176800
  `${entityId}: Auto-detected ${roborockRooms.length} Roborock rooms`
175452
176801
  );
175453
176802
  }
175454
176803
  }
175455
176804
  }
175456
176805
  } else {
175457
- logger245.warn(`${entityId}: No device_id, cannot auto-assign battery`);
176806
+ logger249.warn(`${entityId}: No device_id, cannot auto-assign battery`);
175458
176807
  }
175459
176808
  const payload = {
175460
176809
  entity_id: entityId,
@@ -175465,7 +176814,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175465
176814
  const vacAttrsForClean = state.attributes;
175466
176815
  let cleaningModeOptions;
175467
176816
  if (effectiveMapping?.cleaningModeEntity) {
175468
- const cmState = registry2.initialState(
176817
+ const cmState = registry3.initialState(
175469
176818
  effectiveMapping.cleaningModeEntity
175470
176819
  );
175471
176820
  cleaningModeOptions = cmState?.attributes?.options;
@@ -175485,7 +176834,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175485
176834
  customName,
175486
176835
  mapping: effectiveMapping
175487
176836
  },
175488
- registry2.isServerModeVacuumOnOffEnabled(),
176837
+ registry3.isServerModeVacuumOnOffEnabled(),
175489
176838
  cleaningModeOptions
175490
176839
  );
175491
176840
  if (!endpointType) {
@@ -175527,11 +176876,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175527
176876
  }
175528
176877
  if (mappedChanged) {
175529
176878
  this.pendingMappedChange = true;
175530
- logger245.debug(
176879
+ logger249.debug(
175531
176880
  `Mapped entity change detected for ${this.entityId}, forcing update`
175532
176881
  );
175533
176882
  }
175534
- logger245.debug(
176883
+ logger249.debug(
175535
176884
  `State update received for ${this.entityId}: state=${state.state}`
175536
176885
  );
175537
176886
  this.lastState = state;
@@ -175571,12 +176920,13 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
175571
176920
  // src/services/bridges/server-mode-endpoint-manager.ts
175572
176921
  var MAX_SERVER_MODE_DEVICES = 10;
175573
176922
  var ServerModeEndpointManager = class extends Service {
175574
- constructor(serverNode, client, registry2, mappingStorage, identityStorage, dataProvider, log) {
176923
+ constructor(serverNode, client, registry3, mappingStorage, identityStorage, dataProvider, log) {
175575
176924
  super("ServerModeEndpointManager");
175576
176925
  this.serverNode = serverNode;
175577
176926
  this.client = client;
175578
- this.registry = registry2;
176927
+ this.registry = registry3;
175579
176928
  this.mappingStorage = mappingStorage;
176929
+ this.identityStorage = identityStorage;
175580
176930
  this.dataProvider = dataProvider;
175581
176931
  this.log = log;
175582
176932
  this.identityResolver = new IdentityResolver(
@@ -175588,6 +176938,7 @@ var ServerModeEndpointManager = class extends Service {
175588
176938
  client;
175589
176939
  registry;
175590
176940
  mappingStorage;
176941
+ identityStorage;
175591
176942
  dataProvider;
175592
176943
  log;
175593
176944
  entityIds = [];
@@ -175687,6 +177038,17 @@ var ServerModeEndpointManager = class extends Service {
175687
177038
  this.registry.refresh();
175688
177039
  this._failedEntities = [];
175689
177040
  this.entityIds = this.registry.entityIds;
177041
+ const fullEntities = this.registry.fullEntities;
177042
+ stampIdentityPresence(
177043
+ this.identityStorage,
177044
+ this.dataProvider.id,
177045
+ buildPresentIdentityKeys(fullEntities)
177046
+ );
177047
+ stampMappingPresence(
177048
+ this.mappingStorage,
177049
+ this.dataProvider.id,
177050
+ buildPresentEntityIds(fullEntities)
177051
+ );
175690
177052
  try {
175691
177053
  if (this.entityIds.length === 0) {
175692
177054
  this.log.warn("Server mode bridge has no entities configured");
@@ -176096,10 +177458,10 @@ var BridgeEnvironmentFactory = class extends BridgeFactory {
176096
177458
  // src/core/ioc/app-environment.ts
176097
177459
  var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
176098
177460
  constructor(rootEnv, options) {
176099
- const logger247 = rootEnv.get(LoggerService);
177461
+ const logger251 = rootEnv.get(LoggerService);
176100
177462
  super({
176101
177463
  id: "App",
176102
- log: logger247.get("AppContainer"),
177464
+ log: logger251.get("AppContainer"),
176103
177465
  parent: rootEnv
176104
177466
  });
176105
177467
  this.options = options;
@@ -176113,8 +177475,8 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
176113
177475
  }
176114
177476
  construction;
176115
177477
  async init() {
176116
- const logger247 = this.get(LoggerService);
176117
- this.set(LoggerService, logger247);
177478
+ const logger251 = this.get(LoggerService);
177479
+ this.set(LoggerService, logger251);
176118
177480
  this.set(AppStorage, new AppStorage(await this.load(StorageService)));
176119
177481
  this.set(BridgeStorage, new BridgeStorage(await this.load(AppStorage)));
176120
177482
  this.set(
@@ -176135,7 +177497,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
176135
177497
  );
176136
177498
  this.set(
176137
177499
  HomeAssistantClient,
176138
- new HomeAssistantClient(logger247, this.options.homeAssistant)
177500
+ new HomeAssistantClient(logger251, this.options.homeAssistant)
176139
177501
  );
176140
177502
  this.set(
176141
177503
  HomeAssistantConfig,
@@ -176143,7 +177505,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
176143
177505
  );
176144
177506
  this.set(
176145
177507
  HomeAssistantActions,
176146
- new HomeAssistantActions(logger247, await this.load(HomeAssistantClient))
177508
+ new HomeAssistantActions(logger251, await this.load(HomeAssistantClient))
176147
177509
  );
176148
177510
  this.set(
176149
177511
  HomeAssistantRegistry,
@@ -176179,7 +177541,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
176179
177541
  this.set(
176180
177542
  WebApi,
176181
177543
  new WebApi(
176182
- logger247,
177544
+ logger251,
176183
177545
  await this.load(BridgeService),
176184
177546
  await this.load(HomeAssistantClient),
176185
177547
  await this.load(HomeAssistantRegistry),
@@ -176200,7 +177562,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
176200
177562
 
176201
177563
  // src/matter/patches/patch-level-control-tlv.ts
176202
177564
  init_esm();
176203
- var logger246 = Logger.get("PatchLevelControlTlv");
177565
+ var logger250 = Logger.get("PatchLevelControlTlv");
176204
177566
  var optionalFieldModels = /* @__PURE__ */ new WeakSet();
176205
177567
  var fieldModelMandatoryGetterPatched = false;
176206
177568
  function patchLevelControlTlv() {
@@ -176256,11 +177618,11 @@ function patchLevelControlTlv() {
176256
177618
  patched++;
176257
177619
  }
176258
177620
  if (patched > 0) {
176259
- logger246.info(
177621
+ logger250.info(
176260
177622
  `Patched ${patched} LevelControl TLV schema(s): transitionTime is now optional (Google Home compatibility)`
176261
177623
  );
176262
177624
  } else {
176263
- logger246.warn(
177625
+ logger250.warn(
176264
177626
  "Failed to patch LevelControl TLV schemas, field definitions not found. Google Home brightness adjustment may not work.",
176265
177627
  {
176266
177628
  moveToLevel: describeFields(