@riddix/hamh 2.0.54 → 2.0.55

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(logger253) {
1771
- this.#logger = logger253;
1770
+ static set logger(logger255) {
1771
+ this.#logger = logger255;
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, logger253, options) {
3478
+ static addLogger(identifier, logger255, 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 = logger253;
3484
+ legacy.log = logger255;
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 logger253 = Logger.get(this.#subject.constructor.name);
5691
- logger253.error(...args);
5690
+ const logger255 = Logger.get(this.#subject.constructor.name);
5691
+ logger255.error(...args);
5692
5692
  }
5693
5693
  #createErrorHandler(name) {
5694
5694
  return (e) => {
@@ -64126,8 +64126,8 @@ var init_NodeSession = __esm({
64126
64126
  return session?.type === SessionType.Unicast;
64127
64127
  }
64128
64128
  NodeSession2.is = is;
64129
- function logNew(logger253, operation, session, messenger, fabric, peerNodeId) {
64130
- logger253.info(
64129
+ function logNew(logger255, operation, session, messenger, fabric, peerNodeId) {
64130
+ logger255.info(
64131
64131
  session.via,
64132
64132
  `${operation} session with`,
64133
64133
  Diagnostic.strong(PeerAddress({ fabricIndex: fabric.fabricIndex, nodeId: peerNodeId }).toString()),
@@ -118041,11 +118041,11 @@ var init_Api = __esm({
118041
118041
  }
118042
118042
  Api2.resourceFor = resourceFor;
118043
118043
  function log(level, facility, id, ...message) {
118044
- let logger253 = loggers.get(facility);
118045
- if (!logger253) {
118046
- loggers.set(facility, logger253 = Logger.get(facility));
118044
+ let logger255 = loggers.get(facility);
118045
+ if (!logger255) {
118046
+ loggers.set(facility, logger255 = Logger.get(facility));
118047
118047
  }
118048
- logger253[level](Diagnostic.via(id || "(anon)"), message);
118048
+ logger255[level](Diagnostic.via(id || "(anon)"), message);
118049
118049
  }
118050
118050
  Api2.log = log;
118051
118051
  function logRequest(facility, id, method, target) {
@@ -132383,6 +132383,14 @@ var init_bridge_config_schema = __esm({
132383
132383
  maximum: 5e3,
132384
132384
  default: 0
132385
132385
  },
132386
+ fanSliderDebounceMs: {
132387
+ title: "Fan Slider Debounce (ms)",
132388
+ description: "Wait this long after the last inbound fan-speed write before sending it to Home Assistant. Controllers stream percentSetting while the user is still dragging - one Google Home drag was measured emitting nine writes in eight seconds - and on IR or UART bridged air conditioners every write is a device frame that makes the unit beep. 0 (the default) sends every write immediately, matching previous behaviour. Try 1000-2000 ms for bridged ACs. A per-entity override on a single fan or climate wins over this flag.",
132389
+ type: "number",
132390
+ minimum: 0,
132391
+ maximum: 1e4,
132392
+ default: 0
132393
+ },
132386
132394
  fastSessionRecovery: {
132387
132395
  title: "Fast Session Recovery (Google offline workaround)",
132388
132396
  description: "When a controller drops all subscriptions, clean up the dead session and re-announce after 5 seconds instead of 60. Opt-in for Google Home users whose devices go offline after a cancelled subscription (#386). It shortens the offline window but cannot stop the controller from rejecting the subscription. Default off.",
@@ -132763,6 +132771,14 @@ var init_esm7 = __esm({
132763
132771
  }
132764
132772
  });
132765
132773
 
132774
+ // ../../node_modules/.pnpm/@matter+main@0.17.9/node_modules/@matter/main/dist/esm/protocol.js
132775
+ var init_protocol3 = __esm({
132776
+ "../../node_modules/.pnpm/@matter+main@0.17.9/node_modules/@matter/main/dist/esm/protocol.js"() {
132777
+ init_nodejs();
132778
+ init_esm4();
132779
+ }
132780
+ });
132781
+
132766
132782
  // src/utils/debounce-context.ts
132767
132783
  import debounce from "debounce";
132768
132784
  var DebounceContext;
@@ -132842,7 +132858,7 @@ async function withRetry(fn, options = {}) {
132842
132858
  return await fn();
132843
132859
  } catch (error) {
132844
132860
  lastError = error;
132845
- if (attempt === opts.maxAttempts) {
132861
+ if (attempt === opts.maxAttempts || options.shouldRetry?.(error) === false) {
132846
132862
  break;
132847
132863
  }
132848
132864
  const delayMs = Math.min(
@@ -132906,6 +132922,12 @@ var init_retry = __esm({
132906
132922
  get isOpen() {
132907
132923
  return this.state === "open";
132908
132924
  }
132925
+ // Open AND still inside the reset window. Callers that pre-check
132926
+ // availability must use this, not isOpen, or the half-open probe never
132927
+ // happens and the breaker stays latched forever (#446).
132928
+ get isBlocking() {
132929
+ return this.state === "open" && Date.now() - this.lastFailureTime < this.resetTimeoutMs;
132930
+ }
132909
132931
  reset() {
132910
132932
  this.failures = 0;
132911
132933
  this.state = "closed";
@@ -132942,8 +132964,7 @@ var init_send_ha_message = __esm({
132942
132964
  });
132943
132965
 
132944
132966
  // src/services/home-assistant/home-assistant-actions.ts
132945
- import { callService } from "home-assistant-js-websocket";
132946
- var defaultConfig, HomeAssistantActions;
132967
+ var TARGET_FAILURE_THRESHOLD, defaultConfig, HomeAssistantActions;
132947
132968
  var init_home_assistant_actions = __esm({
132948
132969
  "src/services/home-assistant/home-assistant-actions.ts"() {
132949
132970
  "use strict";
@@ -132952,6 +132973,7 @@ var init_home_assistant_actions = __esm({
132952
132973
  init_retry();
132953
132974
  init_send_ha_message();
132954
132975
  init_diagnostic_event_bus();
132976
+ TARGET_FAILURE_THRESHOLD = 3;
132955
132977
  defaultConfig = {
132956
132978
  retryAttempts: 3,
132957
132979
  retryBaseDelayMs: 100,
@@ -132960,10 +132982,10 @@ var init_home_assistant_actions = __esm({
132960
132982
  circuitBreakerResetMs: 3e4
132961
132983
  };
132962
132984
  HomeAssistantActions = class extends Service {
132963
- constructor(logger253, client, config8) {
132985
+ constructor(logger255, client, config8) {
132964
132986
  super("HomeAssistantActions");
132965
132987
  this.client = client;
132966
- this.log = logger253.get(this);
132988
+ this.log = logger255.get(this);
132967
132989
  this.config = { ...defaultConfig, ...config8 };
132968
132990
  this.circuitBreaker = new CircuitBreaker(
132969
132991
  this.config.circuitBreakerThreshold,
@@ -132971,6 +132993,13 @@ var init_home_assistant_actions = __esm({
132971
132993
  );
132972
132994
  }
132973
132995
  client;
132996
+ // #446: a Matter command must not report success while HA cannot take the
132997
+ // call. Only the transport counts here: the breaker is app-wide, so ten
132998
+ // failures from one broken entity would otherwise make every device on
132999
+ // every bridge fail.
133000
+ get available() {
133001
+ return this.client.haRunning;
133002
+ }
132974
133003
  log;
132975
133004
  debounceContext = new DebounceContext(
132976
133005
  this.processAction.bind(this)
@@ -132979,6 +133008,27 @@ var init_home_assistant_actions = __esm({
132979
133008
  config;
132980
133009
  consecutiveFailures = 0;
132981
133010
  lastSuccessTime = Date.now();
133011
+ // Per entity, not app wide: the shared breaker would let one broken entity
133012
+ // make every device on every bridge fail (#446).
133013
+ targetFailures = /* @__PURE__ */ new Map();
133014
+ // A target that just failed repeatedly is reported as unavailable, so the
133015
+ // controller sees the failure on that device instead of a false success.
133016
+ isTargetBlocked(entityId) {
133017
+ const entry = this.targetFailures.get(entityId);
133018
+ return entry != null && Date.now() < entry.blockedUntil;
133019
+ }
133020
+ recordTargetResult(entityId, failed) {
133021
+ if (!entityId) return;
133022
+ if (!failed) {
133023
+ this.targetFailures.delete(entityId);
133024
+ return;
133025
+ }
133026
+ const count = (this.targetFailures.get(entityId)?.count ?? 0) + 1;
133027
+ this.targetFailures.set(entityId, {
133028
+ count,
133029
+ blockedUntil: count >= TARGET_FAILURE_THRESHOLD ? Date.now() + this.config.circuitBreakerResetMs : 0
133030
+ });
133031
+ }
132982
133032
  processAction(_key, calls) {
132983
133033
  const skipTarget = calls[0].target === false;
132984
133034
  const entity_id = skipTarget ? void 0 : calls[0].target || calls[0].entityId;
@@ -132986,7 +133036,9 @@ var init_home_assistant_actions = __esm({
132986
133036
  const data = Object.assign({}, ...calls.map((c) => c.data));
132987
133037
  const [domain, actionName] = action.split(".");
132988
133038
  const target = entity_id ? { entity_id } : {};
132989
- this.callAction(domain, actionName, data, target, false).catch((error) => {
133039
+ const origin = calls[0].entityId;
133040
+ this.callAction(domain, actionName, data, target, false).then(() => this.recordTargetResult(origin, false)).catch((error) => {
133041
+ this.recordTargetResult(origin, true);
132990
133042
  const errorMsg = this.formatError(error);
132991
133043
  this.log.error(
132992
133044
  `Failed to call action '${action}' for entity '${entity_id ?? "(no target)"}': ${errorMsg}`
@@ -133022,13 +133074,17 @@ var init_home_assistant_actions = __esm({
133022
133074
  const result = await this.circuitBreaker.execute(
133023
133075
  () => withRetry(
133024
133076
  async () => {
133025
- const res = await callService(
133077
+ const res = await sendHaMessage(
133026
133078
  this.client.connection,
133027
- domain,
133028
- action,
133029
- data,
133030
- target,
133031
- returnResponse
133079
+ {
133080
+ type: "call_service",
133081
+ domain,
133082
+ service: action,
133083
+ service_data: data,
133084
+ target,
133085
+ return_response: returnResponse
133086
+ },
133087
+ this.client.messageTimeoutMs
133032
133088
  );
133033
133089
  return res;
133034
133090
  },
@@ -133036,6 +133092,9 @@ var init_home_assistant_actions = __esm({
133036
133092
  maxAttempts: this.config.retryAttempts,
133037
133093
  baseDelayMs: this.config.retryBaseDelayMs,
133038
133094
  maxDelayMs: this.config.retryMaxDelayMs,
133095
+ // A timeout does not mean HA ignored the call, it may have run it.
133096
+ // Repeating a button press or a script would be worse than failing.
133097
+ shouldRetry: (error) => !(error instanceof Error && error.message.includes("timed out")),
133039
133098
  onRetry: (attempt, error, delayMs) => {
133040
133099
  const errorMsg = this.formatError(error);
133041
133100
  this.log.warn(
@@ -133109,6 +133168,14 @@ var init_home_assistant_actions = __esm({
133109
133168
  }
133110
133169
  });
133111
133170
 
133171
+ // ../../node_modules/.pnpm/@matter+main@0.17.9/node_modules/@matter/main/dist/esm/types.js
133172
+ var init_types2 = __esm({
133173
+ "../../node_modules/.pnpm/@matter+main@0.17.9/node_modules/@matter/main/dist/esm/types.js"() {
133174
+ init_nodejs();
133175
+ init_esm3();
133176
+ }
133177
+ });
133178
+
133112
133179
  // src/utils/async-observable.ts
133113
133180
  var AsyncObservable2;
133114
133181
  var init_async_observable = __esm({
@@ -133119,6 +133186,17 @@ var init_async_observable = __esm({
133119
133186
  }
133120
133187
  });
133121
133188
 
133189
+ // src/utils/transaction-is-offline.ts
133190
+ function transactionIsOffline(context) {
133191
+ return !context || hasLocalActor(context);
133192
+ }
133193
+ var init_transaction_is_offline = __esm({
133194
+ "src/utils/transaction-is-offline.ts"() {
133195
+ "use strict";
133196
+ init_protocol3();
133197
+ }
133198
+ });
133199
+
133122
133200
  // src/matter/behaviors/home-assistant-entity-behavior.ts
133123
133201
  var home_assistant_entity_behavior_exports = {};
133124
133202
  __export(home_assistant_entity_behavior_exports, {
@@ -133130,8 +133208,10 @@ var init_home_assistant_entity_behavior = __esm({
133130
133208
  "use strict";
133131
133209
  init_dist();
133132
133210
  init_esm7();
133211
+ init_types2();
133133
133212
  init_home_assistant_actions();
133134
133213
  init_async_observable();
133214
+ init_transaction_is_offline();
133135
133215
  HomeAssistantEntityBehavior = class extends Behavior {
133136
133216
  static id = ClusterId2.homeAssistantEntity;
133137
133217
  get entityId() {
@@ -133146,7 +133226,24 @@ var init_home_assistant_entity_behavior = __esm({
133146
133226
  get isAvailable() {
133147
133227
  return this.entity.state.state !== "unavailable" && this.entity.state.state !== "unknown";
133148
133228
  }
133229
+ // #446: fail the command instead of telling the controller it worked while
133230
+ // the call never reaches HA. `=== false` on purpose: test stubs do not
133231
+ // define `available`, and undefined must read as "no opinion".
133232
+ // ponytail: commands only. Attribute writes that fan and thermostat handle
133233
+ // in reactors run as a local actor, so they still go through and rely on HA
133234
+ // state to correct them. Plumb it there too if that shows up in reports.
133235
+ assertAvailable() {
133236
+ const actions = this.env.get(HomeAssistantActions);
133237
+ const blocked = actions.available === false || actions.isTargetBlocked?.(this.entityId) === true;
133238
+ if (blocked && !transactionIsOffline(this.context)) {
133239
+ throw new StatusResponseError(
133240
+ "Home Assistant is not reachable",
133241
+ StatusCode.Failure
133242
+ );
133243
+ }
133244
+ }
133149
133245
  callAction(action) {
133246
+ this.assertAvailable();
133150
133247
  const actions = this.env.get(HomeAssistantActions);
133151
133248
  actions.call(action, this.entityId);
133152
133249
  }
@@ -133309,10 +133406,10 @@ var DiagnosticService = class {
133309
133406
  };
133310
133407
 
133311
133408
  // src/api/access-log.ts
133312
- function accessLogger(logger253) {
133409
+ function accessLogger(logger255) {
133313
133410
  return (req, res, next) => {
133314
133411
  res.on("finish", () => {
133315
- logger253.debug(
133412
+ logger255.debug(
133316
133413
  `${req.method} ${decodeURI(req.originalUrl)} ${res.statusCode} ${res.statusMessage} from ${req.socket.remoteAddress}`
133317
133414
  );
133318
133415
  });
@@ -133593,6 +133690,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133593
133690
  valetudoIdentifier: config8.valetudoIdentifier,
133594
133691
  coverSwapOpenClose: config8.coverSwapOpenClose,
133595
133692
  coverSliderDebounceMs: config8.coverSliderDebounceMs,
133693
+ fanSliderDebounceMs: config8.fanSliderDebounceMs,
133596
133694
  disableClimateOnOff: config8.disableClimateOnOff,
133597
133695
  disableClimateFanControl: config8.disableClimateFanControl,
133598
133696
  climateAutoMode: config8.climateAutoMode,
@@ -133762,6 +133860,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133762
133860
  valetudoIdentifier: config8.valetudoIdentifier,
133763
133861
  coverSwapOpenClose: config8.coverSwapOpenClose,
133764
133862
  coverSliderDebounceMs: config8.coverSliderDebounceMs,
133863
+ fanSliderDebounceMs: config8.fanSliderDebounceMs,
133765
133864
  disableClimateOnOff: config8.disableClimateOnOff,
133766
133865
  disableClimateFanControl: config8.disableClimateFanControl,
133767
133866
  climateAutoMode: config8.climateAutoMode,
@@ -134644,6 +134743,7 @@ function entityMappingApi(mappingStorage, identityStorage) {
134644
134743
  selectSwitchOnOption: body.selectSwitchOnOption,
134645
134744
  selectSwitchOffOption: body.selectSwitchOffOption,
134646
134745
  coverSliderDebounceMs: body.coverSliderDebounceMs,
134746
+ fanSliderDebounceMs: body.fanSliderDebounceMs,
134647
134747
  updateThrottleMs: body.updateThrottleMs,
134648
134748
  disableClimateOnOff: body.disableClimateOnOff,
134649
134749
  disableClimateFanControl: body.disableClimateFanControl,
@@ -135142,6 +135242,7 @@ function configToProfileEntry(config8) {
135142
135242
  valetudoIdentifier: config8.valetudoIdentifier,
135143
135243
  coverSwapOpenClose: config8.coverSwapOpenClose,
135144
135244
  coverSliderDebounceMs: config8.coverSliderDebounceMs,
135245
+ fanSliderDebounceMs: config8.fanSliderDebounceMs,
135145
135246
  disableClimateOnOff: config8.disableClimateOnOff,
135146
135247
  disableClimateFanControl: config8.disableClimateFanControl,
135147
135248
  climateKeepModeOnIdle: config8.climateKeepModeOnIdle,
@@ -135287,6 +135388,7 @@ function mappingProfileApi(mappingStorage) {
135287
135388
  valetudoIdentifier: entry.valetudoIdentifier,
135288
135389
  coverSwapOpenClose: entry.coverSwapOpenClose,
135289
135390
  coverSliderDebounceMs: entry.coverSliderDebounceMs,
135391
+ fanSliderDebounceMs: entry.fanSliderDebounceMs,
135290
135392
  disableClimateOnOff: entry.disableClimateOnOff,
135291
135393
  disableClimateFanControl: entry.disableClimateFanControl,
135292
135394
  climateKeepModeOnIdle: entry.climateKeepModeOnIdle,
@@ -138045,7 +138147,7 @@ var WebSocketApi = class {
138045
138147
 
138046
138148
  // src/api/web-api.ts
138047
138149
  var WebApi = class extends Service {
138048
- constructor(logger253, bridgeService, haClient, haRegistry, bridgeStorage, mappingStorage, identityStorage, lockCredentialStorage, settingsStorage, backupService, props) {
138150
+ constructor(logger255, bridgeService, haClient, haRegistry, bridgeStorage, mappingStorage, identityStorage, lockCredentialStorage, settingsStorage, backupService, props) {
138049
138151
  super("WebApi");
138050
138152
  this.bridgeService = bridgeService;
138051
138153
  this.haClient = haClient;
@@ -138057,8 +138159,8 @@ var WebApi = class extends Service {
138057
138159
  this.settingsStorage = settingsStorage;
138058
138160
  this.backupService = backupService;
138059
138161
  this.props = props;
138060
- this.logger = logger253;
138061
- this.log = logger253.get(this);
138162
+ this.logger = logger255;
138163
+ this.log = logger255.get(this);
138062
138164
  this.accessLogger = accessLogger(this.log.createChild("Access Log"));
138063
138165
  this.startTime = Date.now();
138064
138166
  this.wsApi = new WebSocketApi(
@@ -138479,12 +138581,9 @@ var BetterLogger = class _BetterLogger extends Logger {
138479
138581
  // src/core/app/mdns.ts
138480
138582
  init_esm();
138481
138583
  init_esm7();
138584
+ init_protocol3();
138482
138585
  import * as os5 from "node:os";
138483
138586
 
138484
- // ../../node_modules/.pnpm/@matter+main@0.17.9/node_modules/@matter/main/dist/esm/protocol.js
138485
- init_nodejs();
138486
- init_esm4();
138487
-
138488
138587
  // src/core/app/filtered-network.ts
138489
138588
  init_esm6();
138490
138589
  function filterAdvertisedIpv6(ipV6) {
@@ -139234,6 +139333,7 @@ init_home_assistant_actions();
139234
139333
  // src/services/home-assistant/home-assistant-client.ts
139235
139334
  init_service();
139236
139335
  init_retry();
139336
+ init_send_ha_message();
139237
139337
  import {
139238
139338
  createConnection as createConnection2,
139239
139339
  createLongLivedTokenAuth,
@@ -139259,30 +139359,92 @@ function isTransientConnectError(reason) {
139259
139359
  return msg.includes("socket hang up") || msg.includes("tls") || msg.includes("TLS");
139260
139360
  }
139261
139361
  var HomeAssistantClient = class extends Service {
139262
- constructor(logger253, options) {
139362
+ constructor(logger255, options) {
139263
139363
  super("HomeAssistantClient");
139264
139364
  this.options = options;
139265
- this.log = logger253.get(this);
139365
+ this.log = logger255.get(this);
139266
139366
  }
139267
139367
  options;
139268
139368
  static Options = /* @__PURE__ */ Symbol.for("HomeAssistantClientProps");
139269
139369
  _connection;
139270
139370
  log;
139371
+ _haRunning = false;
139372
+ _runningSince = 0;
139373
+ runningPollGeneration = 0;
139271
139374
  get connection() {
139272
139375
  return this._connection;
139273
139376
  }
139377
+ // False during HA's own startup and after any WS drop, true only once
139378
+ // getConfig reports RUNNING again. Endpoint managers use this to not trust
139379
+ // partial registry snapshots (#438).
139380
+ get haRunning() {
139381
+ return this._haRunning;
139382
+ }
139383
+ // When HA last reached RUNNING. The removal grace has to outlast HA's own
139384
+ // start, so it counts from here, not from wall time (#438).
139385
+ get runningSince() {
139386
+ return this._runningSince;
139387
+ }
139274
139388
  get baseUrl() {
139275
139389
  return this.options.url;
139276
139390
  }
139277
139391
  get accessToken() {
139278
139392
  return this.options.accessToken;
139279
139393
  }
139394
+ get messageTimeoutMs() {
139395
+ return this.options.messageTimeoutMs;
139396
+ }
139280
139397
  async initialize() {
139281
139398
  this._connection = await this.createConnection(this.options);
139399
+ this.watchRunningState(this._connection);
139400
+ this.setRunning(this._connection.connected ?? true);
139401
+ }
139402
+ setRunning(running) {
139403
+ if (running && !this._haRunning) {
139404
+ this._runningSince = Date.now();
139405
+ }
139406
+ this._haRunning = running;
139282
139407
  }
139283
139408
  async dispose() {
139409
+ this.runningPollGeneration++;
139284
139410
  this.connection?.close();
139285
139411
  }
139412
+ // The websocket library reconnects on its own after a drop and nothing
139413
+ // re-checks HA's lifecycle state, so a core restart resumed registry reads
139414
+ // mid-boot (#438). Force false on every drop and only flip back once a
139415
+ // fresh getConfig poll sees RUNNING. The generation counter kills stale
139416
+ // polls from an older reconnect.
139417
+ watchRunningState(connection) {
139418
+ connection.addEventListener("disconnected", () => {
139419
+ this.runningPollGeneration++;
139420
+ this.setRunning(false);
139421
+ });
139422
+ connection.addEventListener("ready", () => {
139423
+ const generation = ++this.runningPollGeneration;
139424
+ this.setRunning(false);
139425
+ this.confirmRunning(connection, generation).catch((e) => {
139426
+ this.log.warnCtx("HA running state check failed", {
139427
+ reason: e instanceof Error ? e.message : String(e)
139428
+ });
139429
+ });
139430
+ });
139431
+ }
139432
+ async confirmRunning(connection, generation) {
139433
+ while (this.runningPollGeneration === generation) {
139434
+ const config8 = await sendHaMessage(
139435
+ connection,
139436
+ { type: "get_config" },
139437
+ this.options.messageTimeoutMs
139438
+ ).catch(() => void 0);
139439
+ if (this.runningPollGeneration !== generation) return;
139440
+ if (config8?.state === "RUNNING") {
139441
+ this.setRunning(true);
139442
+ this.log.infoCtx("Home Assistant is up and running again", {});
139443
+ return;
139444
+ }
139445
+ await new Promise((resolve11) => setTimeout(resolve11, 5e3));
139446
+ }
139447
+ }
139286
139448
  async createConnection(props) {
139287
139449
  const maxConnectAttempts = 60;
139288
139450
  for (let attempt = 1; attempt <= maxConnectAttempts; attempt++) {
@@ -139466,6 +139628,16 @@ var HomeAssistantRegistry = class extends Service {
139466
139628
  options;
139467
139629
  autoRefresh;
139468
139630
  lastRegistryFingerprint = "";
139631
+ // Bumped on every successful reload, even an unchanged one. Endpoint
139632
+ // removal requires a bump between stamp and delete, so a stale cached
139633
+ // snapshot can never confirm a removal (#438).
139634
+ _snapshotGeneration = 0;
139635
+ get snapshotGeneration() {
139636
+ return this._snapshotGeneration;
139637
+ }
139638
+ // Last HA uptime stamp the auto-refresh saw, so a restart between ticks
139639
+ // still drives one reconcile (#438).
139640
+ lastRunningSince = 0;
139469
139641
  _devices = {};
139470
139642
  get devices() {
139471
139643
  return this._devices;
@@ -139509,8 +139681,11 @@ var HomeAssistantRegistry = class extends Service {
139509
139681
  }
139510
139682
  refreshing = true;
139511
139683
  try {
139684
+ const runningSince = this.client.runningSince;
139685
+ const restarted = runningSince !== this.lastRunningSince;
139512
139686
  const changed = await this.reload();
139513
- if (changed) {
139687
+ this.lastRunningSince = runningSince;
139688
+ if (changed || restarted) {
139514
139689
  await onRefresh();
139515
139690
  }
139516
139691
  } catch (e) {
@@ -139573,6 +139748,7 @@ var HomeAssistantRegistry = class extends Service {
139573
139748
  });
139574
139749
  }
139575
139750
  async runRegistryQueries() {
139751
+ const trustedAtStart = this.client.haRunning;
139576
139752
  const connection = this.client.connection;
139577
139753
  const timeoutMs = this.options.messageTimeoutMs;
139578
139754
  const [entityRegistry, statesList, deviceRegistry, labels, areas] = await Promise.all([
@@ -139612,6 +139788,12 @@ var HomeAssistantRegistry = class extends Service {
139612
139788
  for (const a of areas) hash2.update(`${a.area_id}\0${a.name}
139613
139789
  `);
139614
139790
  const fingerprint = hash2.digest("hex");
139791
+ if (!trustedAtStart || !this.client.haRunning) {
139792
+ logger183.debug("Discarding registry snapshot, HA was not running");
139793
+ this.lastRegistryFingerprint = "";
139794
+ return false;
139795
+ }
139796
+ this._snapshotGeneration++;
139615
139797
  this._states = keyBy(statesList, "entity_id");
139616
139798
  if (fingerprint === this.lastRegistryFingerprint) {
139617
139799
  logger183.debug("Registry unchanged, skipping full refresh");
@@ -140103,6 +140285,7 @@ var EntityMappingStorage = class extends Service {
140103
140285
  selectSwitchOnOption: request.selectSwitchOnOption || void 0,
140104
140286
  selectSwitchOffOption: request.selectSwitchOffOption || void 0,
140105
140287
  coverSliderDebounceMs: sanitizeDebounceMs(request.coverSliderDebounceMs),
140288
+ fanSliderDebounceMs: sanitizeDebounceMs(request.fanSliderDebounceMs),
140106
140289
  updateThrottleMs: sanitizeThrottleMs(request.updateThrottleMs),
140107
140290
  disableClimateOnOff: request.disableClimateOnOff || void 0,
140108
140291
  disableClimateFanControl: request.disableClimateFanControl || void 0,
@@ -140112,7 +140295,7 @@ var EntityMappingStorage = class extends Service {
140112
140295
  composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0,
140113
140296
  disableMomentaryFlip: request.disableMomentaryFlip || void 0
140114
140297
  };
140115
- if (!config8.matterDeviceType && !config8.customName && !config8.customProductName && !config8.customVendorName && !config8.customSerialNumber && config8.customVendorId === void 0 && config8.disabled !== true && !config8.filterLifeEntity && !config8.cleaningModeEntity && !config8.temperatureEntity && !config8.humidityEntity && !config8.batteryEntity && !config8.disableBatteryMapping && !config8.chargingStateEntity && !config8.roomEntities && !config8.disableLockPin && !config8.lockUsercodeService && config8.lockUsercodeSlot === void 0 && config8.lockPinMinLength === void 0 && config8.lockPinMaxLength === void 0 && !config8.powerEntity && !config8.energyEntity && !config8.meterSerialNumber && !config8.pointOfDelivery && !config8.voltageEntity && !config8.currentEntity && !config8.batteryPowerEntity && !config8.batteryEnergyEntity && !config8.chargingSwitchEntity && !config8.currentLimitEntity && !config8.pressureEntity && !config8.suctionLevelEntity && !config8.mopIntensityEntity && (!config8.customServiceAreas || config8.customServiceAreas.length === 0) && (!config8.customFanSpeedTags || Object.keys(config8.customFanSpeedTags).length === 0) && (!config8.fanWindPresets || (config8.fanWindPresets.natural?.length ?? 0) === 0 && (config8.fanWindPresets.sleep?.length ?? 0) === 0) && !config8.fanRestoreSpeedOnPowerOn && !config8.currentRoomEntity && !config8.cleanedAreaEntity && !config8.vacuumAscendingRoomOrder && !config8.vacuumRoomSwitches && !config8.disableCustomAreaRoomModes && !config8.valetudoIdentifier && !config8.coverSwapOpenClose && !config8.coverExposeAsDimmableLight && !config8.selectExposeAsSwitch && !config8.selectSwitchOnOption && !config8.selectSwitchOffOption && !config8.coverSliderDebounceMs && !config8.updateThrottleMs && !config8.disableClimateOnOff && !config8.disableClimateFanControl && !config8.climateKeepModeOnIdle && !config8.climateExposeFan && !config8.climateAutoMode && (!config8.composedEntities || config8.composedEntities.length === 0) && !config8.disableMomentaryFlip) {
140298
+ if (!config8.matterDeviceType && !config8.customName && !config8.customProductName && !config8.customVendorName && !config8.customSerialNumber && config8.customVendorId === void 0 && config8.disabled !== true && !config8.filterLifeEntity && !config8.cleaningModeEntity && !config8.temperatureEntity && !config8.humidityEntity && !config8.batteryEntity && !config8.disableBatteryMapping && !config8.chargingStateEntity && !config8.roomEntities && !config8.disableLockPin && !config8.lockUsercodeService && config8.lockUsercodeSlot === void 0 && config8.lockPinMinLength === void 0 && config8.lockPinMaxLength === void 0 && !config8.powerEntity && !config8.energyEntity && !config8.meterSerialNumber && !config8.pointOfDelivery && !config8.voltageEntity && !config8.currentEntity && !config8.batteryPowerEntity && !config8.batteryEnergyEntity && !config8.chargingSwitchEntity && !config8.currentLimitEntity && !config8.pressureEntity && !config8.suctionLevelEntity && !config8.mopIntensityEntity && (!config8.customServiceAreas || config8.customServiceAreas.length === 0) && (!config8.customFanSpeedTags || Object.keys(config8.customFanSpeedTags).length === 0) && (!config8.fanWindPresets || (config8.fanWindPresets.natural?.length ?? 0) === 0 && (config8.fanWindPresets.sleep?.length ?? 0) === 0) && !config8.fanRestoreSpeedOnPowerOn && !config8.currentRoomEntity && !config8.cleanedAreaEntity && !config8.vacuumAscendingRoomOrder && !config8.vacuumRoomSwitches && !config8.disableCustomAreaRoomModes && !config8.valetudoIdentifier && !config8.coverSwapOpenClose && !config8.coverExposeAsDimmableLight && !config8.selectExposeAsSwitch && !config8.selectSwitchOnOption && !config8.selectSwitchOffOption && !config8.coverSliderDebounceMs && !config8.fanSliderDebounceMs && !config8.updateThrottleMs && !config8.disableClimateOnOff && !config8.disableClimateFanControl && !config8.climateKeepModeOnIdle && !config8.climateExposeFan && !config8.climateAutoMode && (!config8.composedEntities || config8.composedEntities.length === 0) && !config8.disableMomentaryFlip) {
140116
140299
  bridgeMap.delete(request.entityId);
140117
140300
  } else {
140118
140301
  bridgeMap.set(request.entityId, config8);
@@ -155038,9 +155221,8 @@ var WindowCoveringDevice = WindowCoveringDeviceDefinition;
155038
155221
  init_nodejs();
155039
155222
  init_esm5();
155040
155223
 
155041
- // ../../node_modules/.pnpm/@matter+main@0.17.9/node_modules/@matter/main/dist/esm/types.js
155042
- init_nodejs();
155043
- init_esm3();
155224
+ // src/matter/endpoints/server-mode-server-node.ts
155225
+ init_types2();
155044
155226
 
155045
155227
  // src/utils/sanitize-matter-string.ts
155046
155228
  function sanitizeMatterString(value) {
@@ -155224,6 +155406,9 @@ import * as path12 from "node:path";
155224
155406
  // src/plugins/plugin-device-factory.ts
155225
155407
  init_esm();
155226
155408
 
155409
+ // src/matter/behaviors/identify-server.ts
155410
+ init_esm();
155411
+
155227
155412
  // ../../node_modules/.pnpm/@matter+main@0.17.9/node_modules/@matter/main/dist/esm/behaviors.js
155228
155413
  init_nodejs();
155229
155414
 
@@ -156419,14 +156604,138 @@ init_wi_fi_network_diagnostics2();
156419
156604
  init_ClientBehavior();
156420
156605
  var WiFiNetworkManagementClientConstructor = ClientBehavior(WiFiNetworkManagement3);
156421
156606
 
156607
+ // ../../node_modules/.pnpm/@matter+main@0.17.9/node_modules/@matter/main/dist/esm/clusters.js
156608
+ init_nodejs();
156609
+
156610
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
156611
+ init_access_control();
156612
+ init_actions();
156613
+ init_administrator_commissioning();
156614
+
156615
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/alarm-base.js
156616
+ init_ClusterType();
156617
+ init_esm2();
156618
+ var AlarmBase3 = ClusterType(AlarmBase2);
156619
+
156620
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
156621
+ init_basic_information();
156622
+ init_binding();
156623
+ init_bridged_device_basic_information();
156624
+ init_commissioner_control();
156625
+
156626
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/concentration-measurement.js
156627
+ init_ClusterType();
156628
+ init_esm2();
156629
+ var ConcentrationMeasurement3 = ClusterType(ConcentrationMeasurement2);
156630
+
156631
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
156632
+ init_descriptor();
156633
+ init_diagnostic_logs();
156634
+ init_ethernet_network_diagnostics();
156635
+ init_general_commissioning();
156636
+ init_general_diagnostics();
156637
+ init_group_key_management();
156638
+ init_groupcast();
156639
+ init_icd_management();
156640
+ init_identify();
156641
+
156642
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/label.js
156643
+ init_ClusterType();
156644
+ init_esm2();
156645
+ var Label3 = ClusterType(Label2);
156646
+
156647
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
156648
+ init_localization_configuration();
156649
+ init_network_commissioning();
156650
+ init_operational_credentials();
156651
+ init_ota_software_update_provider();
156652
+ init_ota_software_update_requestor();
156653
+ init_power_source();
156654
+ init_power_source_configuration();
156655
+
156656
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/resource-monitoring.js
156657
+ init_ClusterType();
156658
+ init_esm2();
156659
+ var ResourceMonitoring3 = ClusterType(ResourceMonitoring2);
156660
+
156661
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
156662
+ init_software_diagnostics();
156663
+ init_thread_network_diagnostics();
156664
+ init_time_format_localization();
156665
+ init_time_synchronization();
156666
+ init_tls_certificate_management();
156667
+ init_tls_client_management();
156668
+ init_unit_localization();
156669
+
156670
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/web-rtc-transport-definitions.js
156671
+ init_ClusterType();
156672
+ init_esm2();
156673
+ var WebRtcTransportDefinitions3 = ClusterType(WebRtcTransportDefinitions2);
156674
+
156675
+ // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
156676
+ init_wi_fi_network_diagnostics();
156677
+
156422
156678
  // src/matter/behaviors/identify-server.ts
156679
+ init_home_assistant_entity_behavior();
156680
+ var logger198 = Logger.get("IdentifyServer");
156681
+ var IDENTIFY_BUTTON_SUFFIXES = ["_identify", "_locate", "_find_me"];
156423
156682
  var IdentifyServer2 = class extends IdentifyServer {
156683
+ identify(request) {
156684
+ if (request.identifyTime > 0) {
156685
+ this.identifyInHomeAssistant("identify");
156686
+ }
156687
+ return super.identify(request);
156688
+ }
156689
+ triggerEffect(effect) {
156690
+ if (effect.effectIdentifier !== Identify3.EffectIdentifier.StopEffect && effect.effectIdentifier !== Identify3.EffectIdentifier.FinishEffect) {
156691
+ this.identifyInHomeAssistant("triggerEffect");
156692
+ }
156693
+ return super.triggerEffect(effect);
156694
+ }
156695
+ identifyInHomeAssistant(source) {
156696
+ if (!this.agent.has(HomeAssistantEntityBehavior)) return;
156697
+ const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
156698
+ const target = this.findIdentifyEntity(homeAssistant);
156699
+ if (!target) {
156700
+ logger198.debug(
156701
+ `${source} for ${homeAssistant.entityId}, no identify entity on this device`
156702
+ );
156703
+ return;
156704
+ }
156705
+ logger198.info(
156706
+ `${source} \u2192 button.press ${target} for ${homeAssistant.entityId}`
156707
+ );
156708
+ homeAssistant.callAction({ action: "button.press", target });
156709
+ }
156710
+ // Searches the FULL registry: an identify button rarely matches the bridge
156711
+ // filter, same reason the battery and problem lookups do it.
156712
+ findIdentifyEntity(homeAssistant) {
156713
+ const deviceId = homeAssistant.entity.registry?.device_id;
156714
+ if (!deviceId) return void 0;
156715
+ const registry3 = this.env.maybeGet(HomeAssistantRegistry);
156716
+ if (!registry3) return void 0;
156717
+ let byName;
156718
+ for (const entity of Object.values(registry3.entities)) {
156719
+ if (entity.device_id !== deviceId) continue;
156720
+ if (entity.disabled_by != null) continue;
156721
+ if (!entity.entity_id.startsWith("button.")) continue;
156722
+ const deviceClass = registry3.states[entity.entity_id]?.attributes?.device_class;
156723
+ if (deviceClass === "identify") return entity.entity_id;
156724
+ const uniqueId = entity.unique_id ?? "";
156725
+ if (byName == null && IDENTIFY_BUTTON_SUFFIXES.some(
156726
+ (s) => entity.entity_id.endsWith(s) || uniqueId.endsWith(s)
156727
+ )) {
156728
+ byName = entity.entity_id;
156729
+ }
156730
+ }
156731
+ return byName;
156732
+ }
156424
156733
  };
156425
156734
 
156426
156735
  // src/matter/endpoints/validate-endpoint-type.ts
156427
156736
  init_esm();
156428
156737
  init_esm7();
156429
- var logger198 = Logger.get("EndpointValidation");
156738
+ var logger199 = Logger.get("EndpointValidation");
156430
156739
  function toCamelCase(name) {
156431
156740
  return name.charAt(0).toLowerCase() + name.slice(1);
156432
156741
  }
@@ -156456,12 +156765,12 @@ function validateEndpointType(endpointType, entityId) {
156456
156765
  }
156457
156766
  const prefix = entityId ? `[${entityId}] ` : "";
156458
156767
  if (missingMandatory.length > 0) {
156459
- logger198.warn(
156768
+ logger199.warn(
156460
156769
  `${prefix}${deviceTypeModel.name} (0x${endpointType.deviceType.toString(16)}): missing mandatory clusters: ${missingMandatory.join(", ")}`
156461
156770
  );
156462
156771
  }
156463
156772
  if (availableOptional.length > 0) {
156464
- logger198.debug(
156773
+ logger199.debug(
156465
156774
  `${prefix}${deviceTypeModel.name} (0x${endpointType.deviceType.toString(16)}): optional clusters not used: ${availableOptional.join(", ")}`
156466
156775
  );
156467
156776
  }
@@ -156585,7 +156894,7 @@ var BridgeDataProvider = class extends Service {
156585
156894
 
156586
156895
  // src/utils/apply-patch-state.ts
156587
156896
  init_esm();
156588
- var logger199 = Logger.get("ApplyPatchState");
156897
+ var logger200 = Logger.get("ApplyPatchState");
156589
156898
  function applyPatchState(state, patch, options) {
156590
156899
  return applyPatch(state, patch, options?.force);
156591
156900
  }
@@ -156612,23 +156921,23 @@ function applyPatch(state, patch, force = false) {
156612
156921
  if (errorMessage.includes(
156613
156922
  "Endpoint storage inaccessible because endpoint is not a node and is not owned by another endpoint"
156614
156923
  )) {
156615
- logger199.debug(
156924
+ logger200.debug(
156616
156925
  `Suppressed endpoint storage error, patch not applied: ${JSON.stringify(actualPatch)}`
156617
156926
  );
156618
156927
  return actualPatch;
156619
156928
  }
156620
156929
  if (errorMessage.includes("synchronous-transaction-conflict")) {
156621
- logger199.warn(
156930
+ logger200.warn(
156622
156931
  `Transaction conflict, state update DROPPED: ${JSON.stringify(actualPatch)}`
156623
156932
  );
156624
156933
  return actualPatch;
156625
156934
  }
156626
156935
  failedKeys.push(key);
156627
- logger199.warn(`Failed to set property '${key}': ${errorMessage}`);
156936
+ logger200.warn(`Failed to set property '${key}': ${errorMessage}`);
156628
156937
  }
156629
156938
  }
156630
156939
  if (failedKeys.length > 0) {
156631
- logger199.warn(
156940
+ logger200.warn(
156632
156941
  `${failedKeys.length} properties failed to update: [${failedKeys.join(", ")}]`
156633
156942
  );
156634
156943
  }
@@ -156698,7 +157007,7 @@ function truncate(maxLength, value) {
156698
157007
  }
156699
157008
 
156700
157009
  // src/plugins/plugin-device-factory.ts
156701
- var logger200 = Logger.get("PluginDeviceFactory");
157010
+ var logger201 = Logger.get("PluginDeviceFactory");
156702
157011
  var deviceTypeMap = {
156703
157012
  on_off_light: () => OnOffLightDevice.with(
156704
157013
  IdentifyServer2,
@@ -156804,7 +157113,7 @@ var deviceTypeMap = {
156804
157113
  function createPluginEndpointType(deviceType) {
156805
157114
  const factory = deviceTypeMap[deviceType];
156806
157115
  if (!factory) {
156807
- logger200.warn(`Unsupported plugin device type: "${deviceType}"`);
157116
+ logger201.warn(`Unsupported plugin device type: "${deviceType}"`);
156808
157117
  return void 0;
156809
157118
  }
156810
157119
  const endpoint = factory();
@@ -156817,7 +157126,7 @@ function getSupportedPluginDeviceTypes() {
156817
157126
 
156818
157127
  // src/plugins/safe-plugin-runner.ts
156819
157128
  init_esm();
156820
- var logger201 = Logger.get("SafePluginRunner");
157129
+ var logger202 = Logger.get("SafePluginRunner");
156821
157130
  var DEFAULT_TIMEOUT_MS = 1e4;
156822
157131
  var CIRCUIT_BREAKER_THRESHOLD = 3;
156823
157132
  var SafePluginRunner = class {
@@ -156850,7 +157159,7 @@ var SafePluginRunner = class {
156850
157159
  async run(pluginName, operation, fn, timeoutMs = DEFAULT_TIMEOUT_MS) {
156851
157160
  const state = this.getState(pluginName);
156852
157161
  if (state.disabled) {
156853
- logger201.debug(
157162
+ logger202.debug(
156854
157163
  `Plugin "${pluginName}" is disabled (circuit breaker open), skipping ${operation}`
156855
157164
  );
156856
157165
  return void 0;
@@ -156868,13 +157177,13 @@ var SafePluginRunner = class {
156868
157177
  timeout.clear();
156869
157178
  state.failures++;
156870
157179
  state.lastError = error instanceof Error ? error.message : String(error);
156871
- logger201.error(
157180
+ logger202.error(
156872
157181
  `Plugin "${pluginName}" failed during ${operation} (failure ${state.failures}/${CIRCUIT_BREAKER_THRESHOLD}): ${state.lastError}`
156873
157182
  );
156874
157183
  if (state.failures >= CIRCUIT_BREAKER_THRESHOLD) {
156875
157184
  state.disabled = true;
156876
157185
  state.disabledAt = Date.now();
156877
- logger201.error(
157186
+ logger202.error(
156878
157187
  `Plugin "${pluginName}" DISABLED after ${CIRCUIT_BREAKER_THRESHOLD} consecutive failures. Last error: ${state.lastError}`
156879
157188
  );
156880
157189
  }
@@ -156897,7 +157206,7 @@ var SafePluginRunner = class {
156897
157206
  return result;
156898
157207
  } catch (error) {
156899
157208
  timeout.clear();
156900
- logger201.error(
157209
+ logger202.error(
156901
157210
  `Plugin "${pluginName}" failed during ${operation}: ${error instanceof Error ? error.message : String(error)}`
156902
157211
  );
156903
157212
  return void 0;
@@ -156918,13 +157227,13 @@ var SafePluginRunner = class {
156918
157227
  } catch (error) {
156919
157228
  state.failures++;
156920
157229
  state.lastError = error instanceof Error ? error.message : String(error);
156921
- logger201.error(
157230
+ logger202.error(
156922
157231
  `Plugin "${pluginName}" failed during ${operation} (sync, failure ${state.failures}/${CIRCUIT_BREAKER_THRESHOLD}): ${state.lastError}`
156923
157232
  );
156924
157233
  if (state.failures >= CIRCUIT_BREAKER_THRESHOLD) {
156925
157234
  state.disabled = true;
156926
157235
  state.disabledAt = Date.now();
156927
- logger201.error(
157236
+ logger202.error(
156928
157237
  `Plugin "${pluginName}" DISABLED after ${CIRCUIT_BREAKER_THRESHOLD} consecutive failures.`
156929
157238
  );
156930
157239
  }
@@ -156950,7 +157259,7 @@ var SafePluginRunner = class {
156950
157259
  };
156951
157260
 
156952
157261
  // src/plugins/plugin-manager.ts
156953
- var logger202 = Logger.get("PluginManager");
157262
+ var logger203 = Logger.get("PluginManager");
156954
157263
  var PLUGIN_API_VERSION = 1;
156955
157264
  var MAX_PLUGIN_DEVICE_ID_LENGTH = 100;
156956
157265
  var LEGACY_ENABLED_KEY = "__enabled";
@@ -157045,7 +157354,7 @@ var PluginManager = class {
157045
157354
  throw new Error(`Plugin at ${packagePath} package.json missing "main"`);
157046
157355
  }
157047
157356
  if (manifest.hamhPluginApiVersion != null && manifest.hamhPluginApiVersion !== PLUGIN_API_VERSION) {
157048
- logger202.warn(
157357
+ logger203.warn(
157049
157358
  `Plugin "${manifest.name}" declares API version ${manifest.hamhPluginApiVersion}, current is ${PLUGIN_API_VERSION}. It may not work correctly.`
157050
157359
  );
157051
157360
  }
@@ -157076,7 +157385,7 @@ var PluginManager = class {
157076
157385
  };
157077
157386
  await this.register(plugin, metadata);
157078
157387
  } catch (e) {
157079
- logger202.error(`Failed to load external plugin from ${packagePath}:`, e);
157388
+ logger203.error(`Failed to load external plugin from ${packagePath}:`, e);
157080
157389
  throw e;
157081
157390
  }
157082
157391
  }
@@ -157101,7 +157410,7 @@ var PluginManager = class {
157101
157410
  }
157102
157411
  if (enabled === false) {
157103
157412
  metadata.enabled = false;
157104
- logger202.info(`Plugin "${plugin.name}" stays disabled (persisted)`);
157413
+ logger203.info(`Plugin "${plugin.name}" stays disabled (persisted)`);
157105
157414
  }
157106
157415
  const devices = /* @__PURE__ */ new Map();
157107
157416
  const pluginLogger = Logger.get(`Plugin:${plugin.name}`);
@@ -157140,7 +157449,7 @@ var PluginManager = class {
157140
157449
  }
157141
157450
  devices.delete(deviceId);
157142
157451
  await this.onDeviceUnregistered?.(plugin.name, deviceId, {
157143
- keepIdentity: this.instances.get(plugin.name)?.suspending === true
157452
+ keepIdentity: true
157144
157453
  });
157145
157454
  pluginLogger.debug(`Unregistered device: ${deviceId}`);
157146
157455
  },
@@ -157189,7 +157498,7 @@ var PluginManager = class {
157189
157498
  }),
157190
157499
  suspending: false
157191
157500
  });
157192
- logger202.info(
157501
+ logger203.info(
157193
157502
  `Registered plugin: ${plugin.name} v${plugin.version} (${metadata.source})`
157194
157503
  );
157195
157504
  }
@@ -157214,13 +157523,13 @@ var PluginManager = class {
157214
157523
  async startPlugin(name, instance) {
157215
157524
  if (!instance.metadata.enabled) return;
157216
157525
  if (this.runner.isDisabled(name)) {
157217
- logger202.warn(
157526
+ logger203.warn(
157218
157527
  `Plugin "${name}" is disabled (circuit breaker), skipping start`
157219
157528
  );
157220
157529
  instance.metadata.enabled = false;
157221
157530
  return;
157222
157531
  }
157223
- logger202.info(`Starting plugin: ${name}`);
157532
+ logger203.info(`Starting plugin: ${name}`);
157224
157533
  const epoch = ++instance.epoch;
157225
157534
  await this.runner.run(
157226
157535
  name,
@@ -157279,7 +157588,7 @@ var PluginManager = class {
157279
157588
  storage2.flush();
157280
157589
  }
157281
157590
  instance.started = false;
157282
- logger202.info(`Plugin "${name}" shut down`);
157591
+ logger203.info(`Plugin "${name}" shut down`);
157283
157592
  });
157284
157593
  }
157285
157594
  this.instances.clear();
@@ -157375,13 +157684,13 @@ var PluginManager = class {
157375
157684
  instance.metadata.config = pending;
157376
157685
  instance.pendingConfig = void 0;
157377
157686
  } catch (e) {
157378
- logger202.warn(
157687
+ logger203.warn(
157379
157688
  `Failed to persist the parked config for "${pluginName}", it stays parked:`,
157380
157689
  e
157381
157690
  );
157382
157691
  }
157383
157692
  }
157384
- logger202.info(`Starting plugin: ${pluginName}`);
157693
+ logger203.info(`Starting plugin: ${pluginName}`);
157385
157694
  const epoch = ++instance.epoch;
157386
157695
  await this.runner.run(
157387
157696
  pluginName,
@@ -157414,7 +157723,7 @@ var PluginManager = class {
157414
157723
  return JSON.parse(fs10.readFileSync(this.stateFile, "utf-8"));
157415
157724
  }
157416
157725
  } catch (e) {
157417
- logger202.warn(`Failed to read the plugin state file:`, e);
157726
+ logger203.warn(`Failed to read the plugin state file:`, e);
157418
157727
  }
157419
157728
  return {};
157420
157729
  }
@@ -157425,7 +157734,7 @@ var PluginManager = class {
157425
157734
  fs10.mkdirSync(path12.dirname(this.stateFile), { recursive: true });
157426
157735
  fs10.writeFileSync(this.stateFile, JSON.stringify(state, null, 2));
157427
157736
  } catch (e) {
157428
- logger202.warn(
157737
+ logger203.warn(
157429
157738
  `Failed to persist enabled=${enabled} for "${pluginName}":`,
157430
157739
  e
157431
157740
  );
@@ -157459,7 +157768,7 @@ var PluginManager = class {
157459
157768
  if (instance.plugin.onConfigChanged) {
157460
157769
  if (!instance.metadata.enabled) {
157461
157770
  instance.pendingConfig = config8;
157462
- logger202.info(
157771
+ logger203.info(
157463
157772
  `Plugin "${pluginName}" is disabled, the config applies on enable`
157464
157773
  );
157465
157774
  } else {
@@ -157479,6 +157788,7 @@ var PluginManager = class {
157479
157788
  init_dist();
157480
157789
  init_esm7();
157481
157790
  import * as os9 from "node:os";
157791
+ init_protocol3();
157482
157792
 
157483
157793
  // src/utils/json/create-bridge-server-config.ts
157484
157794
  import crypto5 from "node:crypto";
@@ -157735,6 +158045,7 @@ var SecondaryNetworkInterfaceEndpointDefinition = MutableEndpoint({
157735
158045
  Object.freeze(SecondaryNetworkInterfaceEndpointDefinition);
157736
158046
 
157737
158047
  // src/utils/json/create-bridge-server-config.ts
158048
+ init_types2();
157738
158049
  function createBridgeServerConfig(data, options) {
157739
158050
  return {
157740
158051
  type: ServerNode.RootEndpoint,
@@ -157831,6 +158142,7 @@ async function runMdnsAddressWatchTick(deps) {
157831
158142
  }
157832
158143
 
157833
158144
  // src/utils/ensure-commissioning-config.ts
158145
+ init_protocol3();
157834
158146
  var ServerNodeConfigProvider = class extends CommissioningConfigProvider {
157835
158147
  #node;
157836
158148
  constructor(node) {
@@ -157878,6 +158190,10 @@ function deadSessionTimeoutMs(flags2) {
157878
158190
  return flags2?.fastSessionRecovery ? FAST_DEAD_SESSION_TIMEOUT_MS : DEAD_SESSION_TIMEOUT_MS;
157879
158191
  }
157880
158192
  var STALE_SESSION_QUIET_WINDOW_MS = 5 * 60 * 1e3;
158193
+ var PRIMING_GRACE_MS = 3e4;
158194
+ function replacedSessionTimeoutMs(flags2) {
158195
+ return Math.max(deadSessionTimeoutMs(flags2), PRIMING_GRACE_MS);
158196
+ }
157881
158197
  function staleSessionQuietWindowMs(flags2) {
157882
158198
  return flags2?.fastSessionRecovery ? 0 : STALE_SESSION_QUIET_WINDOW_MS;
157883
158199
  }
@@ -158024,11 +158340,11 @@ var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
158024
158340
  var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS = 2500;
158025
158341
  var MDNS_ADDRESS_CHECK_INTERVAL_MS = 6e4;
158026
158342
  var Bridge = class {
158027
- constructor(env, logger253, dataProvider, endpointManager, serverOptions) {
158343
+ constructor(env, logger255, dataProvider, endpointManager, serverOptions) {
158028
158344
  this.dataProvider = dataProvider;
158029
158345
  this.endpointManager = endpointManager;
158030
158346
  this.serverOptions = serverOptions;
158031
- this.log = logger253.get(`Bridge / ${dataProvider.id}`);
158347
+ this.log = logger255.get(`Bridge / ${dataProvider.id}`);
158032
158348
  this.server = new BridgeServerNode(
158033
158349
  env,
158034
158350
  this.dataProvider,
@@ -158486,12 +158802,18 @@ ${e?.toString()}`);
158486
158802
  for (const s of [...sessionManager.sessions]) {
158487
158803
  if (s !== newSession && !s.isClosing && s.peerNodeId === newSession.peerNodeId && s.fabric?.fabricIndex === newSession.fabric?.fabricIndex && s.subscriptions.size === 0) {
158488
158804
  this.log.info(
158489
- `Closing stale session ${s.id} (peer ${s.peerNodeId}, 0 subs), replaced by session ${newSession.id}`
158805
+ `Session ${s.id} (peer ${s.peerNodeId}, 0 subs) replaced by session ${newSession.id}, closing it once it goes quiet`
158806
+ );
158807
+ const staleId = s.id;
158808
+ const armed = this.staleSessionTimers.get(staleId);
158809
+ if (armed) clearTimeout(armed);
158810
+ this.staleSessionTimers.set(
158811
+ staleId,
158812
+ setTimeout(() => {
158813
+ this.staleSessionTimers.delete(staleId);
158814
+ this.closeStaleSession(staleId, PRIMING_GRACE_MS);
158815
+ }, replacedSessionTimeoutMs(this.dataProvider.featureFlags))
158490
158816
  );
158491
- s.initiateForceClose({
158492
- cause: new Error("stale session replaced by new session")
158493
- }).catch(() => {
158494
- });
158495
158817
  }
158496
158818
  }
158497
158819
  const quietWindowMs = staleSessionQuietWindowMs(
@@ -158527,6 +158849,11 @@ ${e?.toString()}`);
158527
158849
  };
158528
158850
  this.sessionDeletedHandler = (session) => {
158529
158851
  this.sessionStartedAt.delete(session.id);
158852
+ const armed = this.staleSessionTimers.get(session.id);
158853
+ if (armed) {
158854
+ clearTimeout(armed);
158855
+ this.staleSessionTimers.delete(session.id);
158856
+ }
158530
158857
  const sessions = [...sessionManager.sessions];
158531
158858
  this.log.warn(
158532
158859
  `Session closed: id=${session.id} peer=${session.peerNodeId} | remaining sessions=${sessions.length}`
@@ -158625,7 +158952,7 @@ ${e?.toString()}`);
158625
158952
  );
158626
158953
  });
158627
158954
  }
158628
- closeStaleSession(sessionId) {
158955
+ closeStaleSession(sessionId, minQuietMs = 0) {
158629
158956
  try {
158630
158957
  const sessionManager = this.server.env.get(SessionManager);
158631
158958
  for (const s of [...sessionManager.sessions]) {
@@ -158635,7 +158962,10 @@ ${e?.toString()}`);
158635
158962
  const idleSec = Math.round((Date.now() - s.timestamp) / 1e3);
158636
158963
  if (!staleSessionShouldClose(
158637
158964
  s,
158638
- staleSessionQuietWindowMs(this.dataProvider.featureFlags)
158965
+ Math.max(
158966
+ staleSessionQuietWindowMs(this.dataProvider.featureFlags),
158967
+ minQuietMs
158968
+ )
158639
158969
  )) {
158640
158970
  this.log.info(
158641
158971
  `Keeping session ${s.id} (peer ${s.peerNodeId}, 0 subs, last traffic ${idleSec}s ago)`
@@ -158644,7 +158974,7 @@ ${e?.toString()}`);
158644
158974
  sessionId,
158645
158975
  setTimeout(() => {
158646
158976
  this.staleSessionTimers.delete(sessionId);
158647
- this.closeStaleSession(sessionId);
158977
+ this.closeStaleSession(sessionId, minQuietMs);
158648
158978
  }, deadSessionTimeoutMs(this.dataProvider.featureFlags))
158649
158979
  );
158650
158980
  break;
@@ -158673,6 +159003,9 @@ ${e?.toString()}`);
158673
159003
  if (s.isClosing || s.subscriptions.size > 0) {
158674
159004
  continue;
158675
159005
  }
159006
+ if (this.staleSessionTimers.has(s.id)) {
159007
+ continue;
159008
+ }
158676
159009
  const idleSec = Math.round((Date.now() - s.timestamp) / 1e3);
158677
159010
  if (!staleSessionShouldClose(
158678
159011
  s,
@@ -159315,77 +159648,6 @@ var EntityStateProvider = class extends Service {
159315
159648
  }
159316
159649
  };
159317
159650
 
159318
- // ../../node_modules/.pnpm/@matter+main@0.17.9/node_modules/@matter/main/dist/esm/clusters.js
159319
- init_nodejs();
159320
-
159321
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
159322
- init_access_control();
159323
- init_actions();
159324
- init_administrator_commissioning();
159325
-
159326
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/alarm-base.js
159327
- init_ClusterType();
159328
- init_esm2();
159329
- var AlarmBase3 = ClusterType(AlarmBase2);
159330
-
159331
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
159332
- init_basic_information();
159333
- init_binding();
159334
- init_bridged_device_basic_information();
159335
- init_commissioner_control();
159336
-
159337
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/concentration-measurement.js
159338
- init_ClusterType();
159339
- init_esm2();
159340
- var ConcentrationMeasurement3 = ClusterType(ConcentrationMeasurement2);
159341
-
159342
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
159343
- init_descriptor();
159344
- init_diagnostic_logs();
159345
- init_ethernet_network_diagnostics();
159346
- init_general_commissioning();
159347
- init_general_diagnostics();
159348
- init_group_key_management();
159349
- init_groupcast();
159350
- init_icd_management();
159351
- init_identify();
159352
-
159353
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/label.js
159354
- init_ClusterType();
159355
- init_esm2();
159356
- var Label3 = ClusterType(Label2);
159357
-
159358
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
159359
- init_localization_configuration();
159360
- init_network_commissioning();
159361
- init_operational_credentials();
159362
- init_ota_software_update_provider();
159363
- init_ota_software_update_requestor();
159364
- init_power_source();
159365
- init_power_source_configuration();
159366
-
159367
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/resource-monitoring.js
159368
- init_ClusterType();
159369
- init_esm2();
159370
- var ResourceMonitoring3 = ClusterType(ResourceMonitoring2);
159371
-
159372
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
159373
- init_software_diagnostics();
159374
- init_thread_network_diagnostics();
159375
- init_time_format_localization();
159376
- init_time_synchronization();
159377
- init_tls_certificate_management();
159378
- init_tls_client_management();
159379
- init_unit_localization();
159380
-
159381
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/web-rtc-transport-definitions.js
159382
- init_ClusterType();
159383
- init_esm2();
159384
- var WebRtcTransportDefinitions3 = ClusterType(WebRtcTransportDefinitions2);
159385
-
159386
- // ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/index.js
159387
- init_wi_fi_network_diagnostics();
159388
-
159389
159651
  // src/utils/converters/fan-mode.ts
159390
159652
  var FanMode = class _FanMode {
159391
159653
  constructor(mode, sequence) {
@@ -159574,7 +159836,7 @@ init_esm();
159574
159836
  init_esm7();
159575
159837
  import crypto6 from "node:crypto";
159576
159838
  init_home_assistant_entity_behavior();
159577
- var logger203 = Logger.get("BasicInformationServer");
159839
+ var logger204 = Logger.get("BasicInformationServer");
159578
159840
  var appliedUniqueIds = /* @__PURE__ */ new Map();
159579
159841
  var BasicInformationServer2 = class extends BridgedDeviceBasicInformationServer {
159580
159842
  async initialize() {
@@ -159620,7 +159882,7 @@ var BasicInformationServer2 = class extends BridgedDeviceBasicInformationServer
159620
159882
  // records keyed on it can be shed (#385).
159621
159883
  uniqueId: this.frozenUniqueId(anchor)
159622
159884
  });
159623
- logger203.debug(
159885
+ logger204.debug(
159624
159886
  `[${entity.entity_id}] basicInfo vendor=${this.state.vendorName} product=${this.state.productName} label=${this.state.productLabel} serial=${this.state.serialNumber} node=${this.state.nodeLabel} uniqueId=${this.state.uniqueId}`
159625
159887
  );
159626
159888
  }
@@ -159650,7 +159912,7 @@ function isValidVendorId(value) {
159650
159912
  // src/matter/behaviors/electrical-energy-measurement-server.ts
159651
159913
  init_esm();
159652
159914
  init_home_assistant_entity_behavior();
159653
- var logger204 = Logger.get("ElectricalEnergyMeasurementServer");
159915
+ var logger205 = Logger.get("ElectricalEnergyMeasurementServer");
159654
159916
  var FeaturedBase = ElectricalEnergyMeasurementServer.with("CumulativeEnergy", "ImportedEnergy");
159655
159917
  var ElectricalEnergyMeasurementServerBase = class extends FeaturedBase {
159656
159918
  async initialize() {
@@ -159659,7 +159921,7 @@ var ElectricalEnergyMeasurementServerBase = class extends FeaturedBase {
159659
159921
  const entityId = homeAssistant.entityId;
159660
159922
  const energyEntity = homeAssistant.state.mapping?.energyEntity;
159661
159923
  if (energyEntity) {
159662
- logger204.debug(
159924
+ logger205.debug(
159663
159925
  `[${entityId}] ElectricalEnergyMeasurement using mapped energy entity: ${energyEntity}`
159664
159926
  );
159665
159927
  }
@@ -159715,7 +159977,7 @@ var HaElectricalEnergyMeasurementServer = ElectricalEnergyMeasurementServerBase.
159715
159977
  // src/matter/behaviors/electrical-power-measurement-server.ts
159716
159978
  init_esm();
159717
159979
  init_home_assistant_entity_behavior();
159718
- var logger205 = Logger.get("ElectricalPowerMeasurementServer");
159980
+ var logger206 = Logger.get("ElectricalPowerMeasurementServer");
159719
159981
  var FeaturedBase2 = ElectricalPowerMeasurementServer.with("AlternatingCurrent");
159720
159982
  var ElectricalPowerMeasurementServerBase = class extends FeaturedBase2 {
159721
159983
  async initialize() {
@@ -159724,7 +159986,7 @@ var ElectricalPowerMeasurementServerBase = class extends FeaturedBase2 {
159724
159986
  const entityId = homeAssistant.entityId;
159725
159987
  const powerEntity = homeAssistant.state.mapping?.powerEntity;
159726
159988
  if (powerEntity) {
159727
- logger205.debug(
159989
+ logger206.debug(
159728
159990
  `[${entityId}] ElectricalPowerMeasurement using mapped power entity: ${powerEntity}`
159729
159991
  );
159730
159992
  }
@@ -159863,7 +160125,7 @@ init_home_assistant_entity_behavior();
159863
160125
  // src/matter/behaviors/humidity-measurement-server.ts
159864
160126
  init_esm();
159865
160127
  init_home_assistant_entity_behavior();
159866
- var logger206 = Logger.get("HumidityMeasurementServer");
160128
+ var logger207 = Logger.get("HumidityMeasurementServer");
159867
160129
  var HumidityMeasurementServerBase = class extends RelativeHumidityMeasurementServer {
159868
160130
  async initialize() {
159869
160131
  await super.initialize();
@@ -159876,7 +160138,7 @@ var HumidityMeasurementServerBase = class extends RelativeHumidityMeasurementSer
159876
160138
  return;
159877
160139
  }
159878
160140
  const humidity = this.getHumidity(this.state.config, entity.state);
159879
- logger206.debug(
160141
+ logger207.debug(
159880
160142
  `Humidity ${entity.state.entity_id} raw=${entity.state.state} measuredValue=${humidity}`
159881
160143
  );
159882
160144
  applyPatchState(this.state, {
@@ -159906,7 +160168,7 @@ function HumidityMeasurementServer(config8) {
159906
160168
  // src/matter/behaviors/power-source-server.ts
159907
160169
  init_esm();
159908
160170
  init_home_assistant_entity_behavior();
159909
- var logger207 = Logger.get("PowerSourceServer");
160171
+ var logger208 = Logger.get("PowerSourceServer");
159910
160172
  var FeaturedBase3 = PowerSourceServer.with("Battery", "Rechargeable");
159911
160173
  var PowerSourceServerBase = class extends FeaturedBase3 {
159912
160174
  async initialize() {
@@ -159918,17 +160180,17 @@ var PowerSourceServerBase = class extends FeaturedBase3 {
159918
160180
  applyPatchState(this.state, {
159919
160181
  endpointList: [endpointNumber]
159920
160182
  });
159921
- logger207.debug(
160183
+ logger208.debug(
159922
160184
  `[${entityId}] PowerSource initialized with endpointList=[${endpointNumber}]`
159923
160185
  );
159924
160186
  } else {
159925
- logger207.warn(
160187
+ logger208.warn(
159926
160188
  `[${entityId}] PowerSource endpoint number is null during initialize - endpointList will be empty!`
159927
160189
  );
159928
160190
  }
159929
160191
  const batteryEntity = homeAssistant.state.mapping?.batteryEntity;
159930
160192
  if (batteryEntity) {
159931
- logger207.debug(
160193
+ logger208.debug(
159932
160194
  `[${entityId}] PowerSource using mapped battery entity: ${batteryEntity}`
159933
160195
  );
159934
160196
  }
@@ -160143,6 +160405,7 @@ init_dist();
160143
160405
 
160144
160406
  // src/matter/behaviors/fan-control-server.ts
160145
160407
  init_esm();
160408
+ init_home_assistant_actions();
160146
160409
 
160147
160410
  // src/utils/converters/fan-mode-order.ts
160148
160411
  var SPEED_RANK = {
@@ -160181,26 +160444,36 @@ var SPEED_RANK = {
160181
160444
  boost: 7,
160182
160445
  jet: 7
160183
160446
  };
160184
- function rankOf(token) {
160185
- const key = token.toLowerCase().replace(/[ _-]/g, "");
160186
- if (key in SPEED_RANK) {
160187
- return SPEED_RANK[key];
160188
- }
160447
+ function speedTokenKey(token) {
160448
+ return token.toLowerCase().replace(/[ _-]/g, "");
160449
+ }
160450
+ function speedNumericValue(token) {
160451
+ const key = speedTokenKey(token).replace(/%$/, "");
160189
160452
  const level = key.match(/^level(\d+)$/);
160190
160453
  if (level) {
160191
160454
  return Number(level[1]);
160192
160455
  }
160193
- if (/^\d+$/.test(key)) {
160194
- return Number(key);
160195
- }
160196
- return void 0;
160456
+ return /^\d+$/.test(key) ? Number(key) : void 0;
160457
+ }
160458
+ function rankOf(token) {
160459
+ return SPEED_RANK[speedTokenKey(token)];
160460
+ }
160461
+ function sortByRank(presets, ranks) {
160462
+ return presets.map((value, index) => ({ value, index, rank: ranks[index] })).sort((a, b) => a.rank - b.rank || a.index - b.index).map((entry) => entry.value);
160197
160463
  }
160198
160464
  function toAscendingSpeedPresets(presets) {
160465
+ const numerics = presets.map(speedNumericValue);
160466
+ if (numerics.every((value) => value !== void 0)) {
160467
+ return sortByRank(presets, numerics);
160468
+ }
160469
+ if (numerics.some((value) => value !== void 0)) {
160470
+ return presets.slice();
160471
+ }
160199
160472
  const ranks = presets.map(rankOf);
160200
160473
  if (ranks.some((rank) => rank === void 0)) {
160201
160474
  return presets.slice();
160202
160475
  }
160203
- return presets.map((value, index) => ({ value, index, rank: ranks[index] })).sort((a, b) => a.rank - b.rank || a.index - b.index).map((entry) => entry.value);
160476
+ return sortByRank(presets, ranks);
160204
160477
  }
160205
160478
  function percentToPresetIndex(percentage, count) {
160206
160479
  if (count <= 0) {
@@ -160238,12 +160511,8 @@ var FanSpeed = class _FanSpeed {
160238
160511
  }
160239
160512
  };
160240
160513
 
160241
- // src/utils/transaction-is-offline.ts
160242
- function transactionIsOffline(context) {
160243
- return !context || hasLocalActor(context);
160244
- }
160245
-
160246
160514
  // src/matter/behaviors/fan-control-server.ts
160515
+ init_transaction_is_offline();
160247
160516
  init_home_assistant_entity_behavior();
160248
160517
 
160249
160518
  // src/matter/behaviors/on-off-server.ts
@@ -160297,7 +160566,7 @@ function notifyLightTurnedOff(entityId, haLastChanged) {
160297
160566
  sweepLastTurnOff(now);
160298
160567
  lastTurnOffTimestamps.set(entityId, { ts: now, haLastChanged });
160299
160568
  }
160300
- var logger208 = Logger.get("LevelControlServer");
160569
+ var logger209 = Logger.get("LevelControlServer");
160301
160570
  var FeaturedBase5 = LevelControlServer.with("OnOff", "Lighting");
160302
160571
  var LevelControlServerBase = class extends FeaturedBase5 {
160303
160572
  pendingTransitionTime;
@@ -160315,7 +160584,7 @@ var LevelControlServerBase = class extends FeaturedBase5 {
160315
160584
  try {
160316
160585
  await super.initialize();
160317
160586
  } catch (error) {
160318
- logger208.error("super.initialize() failed:", error);
160587
+ logger209.error("super.initialize() failed:", error);
160319
160588
  throw error;
160320
160589
  }
160321
160590
  const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
@@ -160433,7 +160702,7 @@ var LevelControlServerBase = class extends FeaturedBase5 {
160433
160702
  expectedLevel: remembered,
160434
160703
  timestamp: now2
160435
160704
  });
160436
- logger208.debug(
160705
+ logger209.debug(
160437
160706
  `[${entityId}] Storing level ${level} without calling HA - moveToLevel arrived ${sinceTurnOff}ms after a Matter off while still off (#434)`
160438
160707
  );
160439
160708
  return;
@@ -160444,7 +160713,7 @@ var LevelControlServerBase = class extends FeaturedBase5 {
160444
160713
  const lastTurnOn = lastTurnOnTimestamps.get(entityId);
160445
160714
  const timeSinceTurnOn = lastTurnOn ? Date.now() - lastTurnOn : Infinity;
160446
160715
  if (level >= this.maxLevel && timeSinceTurnOn < 200) {
160447
- logger208.debug(
160716
+ logger209.debug(
160448
160717
  `[${entityId}] Ignoring moveToLevel(${level}) - Alexa brightness reset detected (${timeSinceTurnOn}ms after turn-on)`
160449
160718
  );
160450
160719
  return;
@@ -160490,7 +160759,7 @@ function LevelControlServer2(config8) {
160490
160759
  }
160491
160760
 
160492
160761
  // src/matter/behaviors/on-off-server.ts
160493
- var logger209 = Logger.get("OnOffServer");
160762
+ var logger210 = Logger.get("OnOffServer");
160494
160763
  var optimisticOnOffState = /* @__PURE__ */ new Map();
160495
160764
  var OPTIMISTIC_TIMEOUT_MS2 = 3e3;
160496
160765
  function sweepOptimisticOnOff(now) {
@@ -160554,7 +160823,7 @@ var OnOffServerBase = class extends OnOffServer {
160554
160823
  if (!action) {
160555
160824
  return;
160556
160825
  }
160557
- logger209.info(`[${homeAssistant.entityId}] Turning ON -> ${action.action}`);
160826
+ logger210.info(`[${homeAssistant.entityId}] Turning ON -> ${action.action}`);
160558
160827
  notifyLightTurnedOn(homeAssistant.entityId);
160559
160828
  if (!skipMomentaryFlip) {
160560
160829
  const now = Date.now();
@@ -160585,7 +160854,7 @@ var OnOffServerBase = class extends OnOffServer {
160585
160854
  if (!action) {
160586
160855
  return;
160587
160856
  }
160588
- logger209.info(`[${homeAssistant.entityId}] Turning OFF -> ${action.action}`);
160857
+ logger210.info(`[${homeAssistant.entityId}] Turning OFF -> ${action.action}`);
160589
160858
  const now = Date.now();
160590
160859
  sweepOptimisticOnOff(now);
160591
160860
  optimisticOnOffState.set(homeAssistant.entityId, {
@@ -160619,10 +160888,26 @@ function setOptimisticOnOff(entityId, expectedOnOff) {
160619
160888
  }
160620
160889
 
160621
160890
  // src/matter/behaviors/fan-control-server.ts
160622
- var logger210 = Logger.get("FanControlServer");
160891
+ var logger211 = Logger.get("FanControlServer");
160623
160892
  var defaultStepSize = 33.33;
160624
160893
  var minSpeedMax = 3;
160625
160894
  var maxSpeedMax = 100;
160895
+ var fanDebounce = /* @__PURE__ */ new WeakMap();
160896
+ function getFanDebounce(endpoint) {
160897
+ let st = fanDebounce.get(endpoint);
160898
+ if (!st) {
160899
+ st = { timer: null, pending: null };
160900
+ fanDebounce.set(endpoint, st);
160901
+ }
160902
+ return st;
160903
+ }
160904
+ function clearFanDebounce(st) {
160905
+ if (st.timer) {
160906
+ clearTimeout(st.timer);
160907
+ st.timer = null;
160908
+ }
160909
+ st.pending = null;
160910
+ }
160626
160911
  var FeaturedBase6 = FanControlServer.with(
160627
160912
  "Step",
160628
160913
  "MultiSpeed",
@@ -160653,6 +160938,14 @@ var FanControlServerBase = class extends FeaturedBase6 {
160653
160938
  auto: this.lastIsAutoMode
160654
160939
  };
160655
160940
  }
160941
+ async [Symbol.asyncDispose]() {
160942
+ const st = fanDebounce.get(this.endpoint);
160943
+ if (st) {
160944
+ clearFanDebounce(st);
160945
+ fanDebounce.delete(this.endpoint);
160946
+ }
160947
+ await super[Symbol.asyncDispose]();
160948
+ }
160656
160949
  async initialize() {
160657
160950
  if (this.state.fanModeSequence == null) {
160658
160951
  this.state.fanModeSequence = this.features.auto ? FanControl3.FanModeSequence.OffLowMedHighAuto : FanControl3.FanModeSequence.OffLowMedHigh;
@@ -160814,7 +161107,7 @@ var FanControlServerBase = class extends FeaturedBase6 {
160814
161107
  if (!homeAssistant.isAvailable) {
160815
161108
  return;
160816
161109
  }
160817
- this.syncOnOff(percentSetting !== 0);
161110
+ this.syncOnOffFromSpeed(percentSetting !== 0);
160818
161111
  if (percentSetting === 0) {
160819
161112
  homeAssistant.callAction(this.state.config.turnOff(void 0, this.agent));
160820
161113
  } else {
@@ -160859,7 +161152,7 @@ var FanControlServerBase = class extends FeaturedBase6 {
160859
161152
  }
160860
161153
  const targetFanMode = FanMode.create(fanMode, this.state.fanModeSequence);
160861
161154
  if (targetFanMode.mode === FanControl3.FanMode.Auto) {
160862
- this.syncOnOff(true);
161155
+ this.syncOnOffFromSpeed(true);
160863
161156
  homeAssistant.callAction(
160864
161157
  this.state.config.setAutoMode(void 0, this.agent)
160865
161158
  );
@@ -160879,6 +161172,53 @@ var FanControlServerBase = class extends FeaturedBase6 {
160879
161172
  this.applyPercentageAction(percentage);
160880
161173
  });
160881
161174
  }
161175
+ // Per-entity override wins over the per-bridge flag; both must be > 0 to
161176
+ // count. Mirrors WindowCoveringServerBase.resolveDebounceOverride.
161177
+ resolveFanDebounceMs(homeAssistant) {
161178
+ const fromEntity = homeAssistant.state.mapping?.fanSliderDebounceMs;
161179
+ if (typeof fromEntity === "number" && fromEntity > 0) {
161180
+ return fromEntity;
161181
+ }
161182
+ const fromBridge = this.env.get(BridgeDataProvider).featureFlags?.fanSliderDebounceMs;
161183
+ if (typeof fromBridge === "number" && fromBridge > 0) {
161184
+ return fromBridge;
161185
+ }
161186
+ return null;
161187
+ }
161188
+ // Send a speed-derived action, or park it in the debounce window so only the
161189
+ // last write of a burst reaches HA. Cluster state was already updated per
161190
+ // write, so the controller sees immediate feedback either way. With no
161191
+ // window configured this is a plain callAction, the historic path.
161192
+ dispatchSpeedAction(homeAssistant, action) {
161193
+ const debounceMs = this.resolveFanDebounceMs(homeAssistant);
161194
+ if (debounceMs == null) {
161195
+ homeAssistant.callAction(action);
161196
+ return;
161197
+ }
161198
+ homeAssistant.assertAvailable();
161199
+ const entityId = homeAssistant.entityId;
161200
+ const actions = this.env.get(HomeAssistantActions);
161201
+ const st = getFanDebounce(this.endpoint);
161202
+ st.pending = { action, entityId, actions };
161203
+ if (st.timer) {
161204
+ clearTimeout(st.timer);
161205
+ }
161206
+ st.timer = setTimeout(() => {
161207
+ st.timer = null;
161208
+ const pending = st.pending;
161209
+ st.pending = null;
161210
+ if (pending) {
161211
+ pending.actions.call(pending.action, pending.entityId);
161212
+ }
161213
+ }, debounceMs);
161214
+ }
161215
+ // Devices whose fan cannot stop (air conditioners) opt out of driving OnOff
161216
+ // from speed: speed zero is a speed change, not a power change (#442).
161217
+ syncOnOffFromSpeed(on) {
161218
+ if (this.state.config.syncOnOffWithSpeed !== false) {
161219
+ this.syncOnOff(on);
161220
+ }
161221
+ }
160882
161222
  applyPercentageAction(percentage) {
160883
161223
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
160884
161224
  if (!homeAssistant.isAvailable) {
@@ -160888,7 +161228,7 @@ var FanControlServerBase = class extends FeaturedBase6 {
160888
161228
  const wasOff = homeAssistant.entity.state?.state === "off" || this.agent.has(OnOffBehavior) && !this.agent.get(OnOffBehavior).state.onOff;
160889
161229
  const remembered = this.remembered();
160890
161230
  if (wasOff) {
160891
- logger210.debug(
161231
+ logger211.debug(
160892
161232
  `[${homeAssistant.entityId}] power-on write ${percentage}%: restore flag=${restoreOnPowerOn}, last speed=${remembered.percent}`
160893
161233
  );
160894
161234
  }
@@ -160911,9 +161251,12 @@ var FanControlServerBase = class extends FeaturedBase6 {
160911
161251
  homeAssistant.entity.state,
160912
161252
  this.agent
160913
161253
  );
160914
- this.syncOnOff(percentage !== 0);
161254
+ this.syncOnOffFromSpeed(percentage !== 0);
160915
161255
  if (percentage === 0) {
160916
- homeAssistant.callAction(config8.turnOff(void 0, this.agent));
161256
+ this.dispatchSpeedAction(
161257
+ homeAssistant,
161258
+ config8.turnOff(void 0, this.agent)
161259
+ );
160917
161260
  } else if (supportsPercentage) {
160918
161261
  const stepSize = config8.getStepSize(
160919
161262
  homeAssistant.entity.state,
@@ -160924,7 +161267,10 @@ var FanControlServerBase = class extends FeaturedBase6 {
160924
161267
  stepSize ?? 1,
160925
161268
  Math.min(100, roundedPercentage)
160926
161269
  );
160927
- homeAssistant.callAction(config8.turnOn(clampedPercentage, this.agent));
161270
+ this.dispatchSpeedAction(
161271
+ homeAssistant,
161272
+ config8.turnOn(clampedPercentage, this.agent)
161273
+ );
160928
161274
  this.rememberSpeed(clampedPercentage);
160929
161275
  } else {
160930
161276
  const presetModes = config8.getPresetModes(homeAssistant.entity.state, this.agent) ?? [];
@@ -160937,7 +161283,8 @@ var FanControlServerBase = class extends FeaturedBase6 {
160937
161283
  speedPresets.length
160938
161284
  );
160939
161285
  const targetPreset = speedPresets[presetIndex];
160940
- homeAssistant.callAction(
161286
+ this.dispatchSpeedAction(
161287
+ homeAssistant,
160941
161288
  config8.setPresetMode(targetPreset, this.agent)
160942
161289
  );
160943
161290
  this.rememberSpeed(percentage);
@@ -161074,7 +161421,7 @@ var FanControlServerBase = class extends FeaturedBase6 {
161074
161421
  const entityId = this.agent.get(HomeAssistantEntityBehavior).entity.entity_id;
161075
161422
  setOptimisticOnOff(entityId, on);
161076
161423
  } catch (e) {
161077
- logger210.debug(
161424
+ logger211.debug(
161078
161425
  `syncOnOff(${on}) failed: ${e instanceof Error ? e.message : String(e)}`
161079
161426
  );
161080
161427
  }
@@ -161217,7 +161564,7 @@ var FanOnOffServer = OnOffServer2({
161217
161564
  });
161218
161565
 
161219
161566
  // src/matter/endpoints/composed/composed-air-purifier-endpoint.ts
161220
- var logger211 = Logger.get("ComposedAirPurifierEndpoint");
161567
+ var logger212 = Logger.get("ComposedAirPurifierEndpoint");
161221
161568
  var temperatureConfig = {
161222
161569
  getValue(entity, agent) {
161223
161570
  const fallbackUnit = agent.env.get(HomeAssistantConfig).unitSystem.temperature;
@@ -161435,7 +161782,7 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
161435
161782
  config8.powerEntityId ? "+Pwr" : "",
161436
161783
  config8.energyEntityId ? "+Nrg" : ""
161437
161784
  ].filter(Boolean).join("");
161438
- logger211.info(
161785
+ logger212.info(
161439
161786
  `Created composed air purifier ${primaryEntityId}: ${clusterLabels}`
161440
161787
  );
161441
161788
  return endpoint;
@@ -161608,6 +161955,19 @@ function rockSettingToSwingMode(setting) {
161608
161955
  }
161609
161956
  return "off";
161610
161957
  }
161958
+ function climateFanOffMode(fanModes) {
161959
+ const modes = fanModes ?? [];
161960
+ const declaredOff = modes.find(
161961
+ (mode) => ["off", "none", "stop"].includes(mode.trim().toLowerCase())
161962
+ );
161963
+ if (declaredOff) {
161964
+ return declaredOff;
161965
+ }
161966
+ const speeds = toAscendingSpeedPresets(
161967
+ modes.filter((mode) => mode.toLowerCase() !== "auto")
161968
+ );
161969
+ return speeds[0] ?? "off";
161970
+ }
161611
161971
  var climateFanControlConfig = {
161612
161972
  getPercentage: () => void 0,
161613
161973
  getStepSize: () => void 0,
@@ -161623,16 +161983,33 @@ var climateFanControlConfig = {
161623
161983
  return attributes4(entity).fan_mode ?? void 0;
161624
161984
  },
161625
161985
  supportsPercentage: () => false,
161986
+ // An AC's fan cannot stop while the compressor runs, so speed zero must not
161987
+ // report the unit as powered off. Without this the Matter OnOff cluster flips
161988
+ // false at zero, the controller shows the AC off, HA reports it still on, and
161989
+ // the tile flaps back. Power stays with the OnOff cluster's own commands.
161990
+ syncOnOffWithSpeed: false,
161626
161991
  isOscillating: (entity) => attributes4(entity).swing_mode?.toLowerCase() !== "off" && attributes4(entity).swing_mode != null,
161627
161992
  supportsOscillation: (entity) => (attributes4(entity).swing_modes?.length ?? 0) > 0,
161628
161993
  getRockSetting: (entity) => swingModeToRockSetting(attributes4(entity).swing_mode),
161629
161994
  // Climate devices don't typically support wind modes
161630
161995
  getWindMode: () => void 0,
161631
161996
  supportsWind: () => false,
161632
- turnOff: () => ({
161633
- action: "climate.set_fan_mode",
161634
- data: { fan_mode: "off" }
161635
- }),
161997
+ // A controller writing percentSetting 0 lands here. An air conditioner's fan
161998
+ // cannot stop independently of the compressor, and HA climate entities almost
161999
+ // never declare an "off" fan mode - sending the literal "off" just fails the
162000
+ // service call, so the slider's bottom position silently does nothing.
162001
+ // Clamp to the slowest real speed instead, unless the entity genuinely offers
162002
+ // an off-like mode, in which case use its own spelling (HA fan mode names are
162003
+ // case-sensitive). Power stays with the OnOff cluster, which is where a
162004
+ // controller's actual "off" belongs.
162005
+ turnOff: (_, agent) => {
162006
+ const entityState = agent.get(HomeAssistantEntityBehavior).state.entity.state;
162007
+ const fanMode = climateFanOffMode(attributes4(entityState).fan_modes);
162008
+ return {
162009
+ action: "climate.set_fan_mode",
162010
+ data: { fan_mode: fanMode }
162011
+ };
162012
+ },
161636
162013
  turnOn: () => ({
161637
162014
  action: "climate.set_fan_mode",
161638
162015
  data: { fan_mode: "on" }
@@ -161721,11 +162098,16 @@ var humidityConfig2 = {
161721
162098
  var ClimateHumidityMeasurementServer = HumidityMeasurementServer(humidityConfig2);
161722
162099
 
161723
162100
  // src/matter/endpoints/legacy/climate/behaviors/climate-on-off-server.ts
162101
+ init_esm();
161724
162102
  init_home_assistant_entity_behavior();
162103
+ var logger213 = Logger.get("ClimateOnOffServer");
161725
162104
  var ClimateOnOffServer = OnOffServer2({
161726
162105
  turnOn: (_value, agent) => {
161727
162106
  const entity = agent.get(HomeAssistantEntityBehavior).entity;
161728
- if (entity.state.state !== "off") {
162107
+ if (entity.state.state !== "off" && agent.get(OnOffServer).state.onOff) {
162108
+ logger213.debug(
162109
+ `[${entity.entity_id}] Skipping redundant OnOff.on(), cached state "${entity.state.state}"`
162110
+ );
161729
162111
  return void 0;
161730
162112
  }
161731
162113
  return { action: "climate.turn_on" };
@@ -161757,7 +162139,8 @@ init_home_assistant_entity_behavior();
161757
162139
  // src/matter/behaviors/thermostat-server.ts
161758
162140
  init_esm();
161759
162141
  init_home_assistant_entity_behavior();
161760
- var logger212 = Logger.get("ThermostatServer");
162142
+ init_transaction_is_offline();
162143
+ var logger214 = Logger.get("ThermostatServer");
161761
162144
  var SystemMode = Thermostat3.SystemMode;
161762
162145
  var RunningMode = Thermostat3.ThermostatRunningMode;
161763
162146
  var nudgingSetpoints = /* @__PURE__ */ new Set();
@@ -161836,7 +162219,7 @@ function clearInactiveSetpointState(self, scope) {
161836
162219
  }
161837
162220
  function thermostatPreInitialize(self) {
161838
162221
  const currentLocal = self.state.localTemperature;
161839
- logger212.debug(
162222
+ logger214.debug(
161840
162223
  `initialize: features - heating=${self.features.heating}, cooling=${self.features.cooling}`
161841
162224
  );
161842
162225
  const localValue = typeof currentLocal === "number" && !Number.isNaN(currentLocal) ? currentLocal : currentLocal === null ? null : 2100;
@@ -161883,7 +162266,7 @@ function thermostatPreInitialize(self) {
161883
162266
  } else {
161884
162267
  clearInactiveSetpointState(self, "Cool");
161885
162268
  }
161886
- logger212.debug(
162269
+ logger214.debug(
161887
162270
  `initialize: after force-set - local=${self.state.localTemperature}`
161888
162271
  );
161889
162272
  self.state.thermostatRunningState = runningStateAllOff;
@@ -161976,7 +162359,7 @@ var ThermostatServerBase = class extends FullFeaturedBase {
161976
162359
  maxCoolLimit,
161977
162360
  "cool"
161978
162361
  );
161979
- logger212.debug(
162362
+ logger214.debug(
161980
162363
  `update: limits heat=[${minHeatLimit}, ${maxHeatLimit}], cool=[${minCoolLimit}, ${maxCoolLimit}], systemMode=${systemMode}, runningMode=${runningMode}`
161981
162364
  );
161982
162365
  let controlSequence = config8.getControlSequence(entity.state, this.agent);
@@ -162049,18 +162432,18 @@ var ThermostatServerBase = class extends FullFeaturedBase {
162049
162432
  */
162050
162433
  // biome-ignore lint/correctness/noUnusedPrivateClassMembers: Called via thermostatPostInitialize + prototype copy
162051
162434
  heatingSetpointChanging(value, _oldValue, context) {
162052
- logger212.debug(
162435
+ logger214.debug(
162053
162436
  `heatingSetpointChanging: value=${value}, oldValue=${_oldValue}, isOffline=${transactionIsOffline(context)}`
162054
162437
  );
162055
162438
  if (transactionIsOffline(context)) {
162056
- logger212.debug(
162439
+ logger214.debug(
162057
162440
  "heatingSetpointChanging: skipping - transaction is offline"
162058
162441
  );
162059
162442
  return;
162060
162443
  }
162061
162444
  const next = Temperature.celsius(value / 100);
162062
162445
  if (!next) {
162063
- logger212.debug("heatingSetpointChanging: skipping - invalid temperature");
162446
+ logger214.debug("heatingSetpointChanging: skipping - invalid temperature");
162064
162447
  return;
162065
162448
  }
162066
162449
  this.agent.asLocalActor(() => {
@@ -162071,7 +162454,7 @@ var ThermostatServerBase = class extends FullFeaturedBase {
162071
162454
  this.agent
162072
162455
  );
162073
162456
  const currentMode2 = this.state.systemMode;
162074
- logger212.debug(
162457
+ logger214.debug(
162075
162458
  `heatingSetpointChanging: supportsRange=${supportsRange}, systemMode=${currentMode2}, features.heating=${this.features.heating}, features.cooling=${this.features.cooling}`
162076
162459
  );
162077
162460
  if (!supportsRange) {
@@ -162081,12 +162464,12 @@ var ThermostatServerBase = class extends FullFeaturedBase {
162081
162464
  const isOff = currentMode2 === Thermostat3.SystemMode.Off;
162082
162465
  if (isOff && this.features.heating) {
162083
162466
  if (nudgingSetpoints.has(homeAssistant.entityId)) {
162084
- logger212.debug(
162467
+ logger214.debug(
162085
162468
  `heatingSetpointChanging: skipping auto-resume - nudge write in progress`
162086
162469
  );
162087
162470
  return;
162088
162471
  }
162089
- logger212.info(
162472
+ logger214.info(
162090
162473
  `heatingSetpointChanging: auto-resume - switching to Heat (was Off)`
162091
162474
  );
162092
162475
  const modeAction = config8.setSystemMode(
@@ -162095,17 +162478,17 @@ var ThermostatServerBase = class extends FullFeaturedBase {
162095
162478
  );
162096
162479
  homeAssistant.callAction(modeAction);
162097
162480
  } else if (!isAutoMode && !isHeatingMode) {
162098
- logger212.debug(
162481
+ logger214.debug(
162099
162482
  `heatingSetpointChanging: skipping - not in heating/auto mode (mode=${currentMode2}, haMode=${haHvacMode})`
162100
162483
  );
162101
162484
  return;
162102
162485
  }
162103
- logger212.debug(
162486
+ logger214.debug(
162104
162487
  `heatingSetpointChanging: proceeding - isAutoMode=${isAutoMode}, isHeatingMode=${isHeatingMode}, isOff=${isOff}, haMode=${haHvacMode}`
162105
162488
  );
162106
162489
  }
162107
162490
  const coolingSetpoint = this.features.cooling ? this.state.occupiedCoolingSetpoint : value;
162108
- logger212.debug(
162491
+ logger214.debug(
162109
162492
  `heatingSetpointChanging: calling setTemperature with heat=${next.celsius(true)}, cool=${coolingSetpoint}`
162110
162493
  );
162111
162494
  this.setTemperature(
@@ -162144,12 +162527,12 @@ var ThermostatServerBase = class extends FullFeaturedBase {
162144
162527
  const isOff = currentMode2 === Thermostat3.SystemMode.Off;
162145
162528
  if (isOff && !this.features.heating && this.features.cooling) {
162146
162529
  if (nudgingSetpoints.has(homeAssistant.entityId)) {
162147
- logger212.debug(
162530
+ logger214.debug(
162148
162531
  `coolingSetpointChanging: skipping auto-resume - nudge write in progress`
162149
162532
  );
162150
162533
  return;
162151
162534
  }
162152
- logger212.info(
162535
+ logger214.info(
162153
162536
  `coolingSetpointChanging: auto-resume - switching to Cool (was Off)`
162154
162537
  );
162155
162538
  const modeAction = config8.setSystemMode(
@@ -162158,12 +162541,12 @@ var ThermostatServerBase = class extends FullFeaturedBase {
162158
162541
  );
162159
162542
  homeAssistant.callAction(modeAction);
162160
162543
  } else if (!isAutoMode && !isCoolingMode) {
162161
- logger212.debug(
162544
+ logger214.debug(
162162
162545
  `coolingSetpointChanging: skipping - not in cooling/auto mode (mode=${currentMode2}, haMode=${haHvacMode})`
162163
162546
  );
162164
162547
  return;
162165
162548
  }
162166
- logger212.debug(
162549
+ logger214.debug(
162167
162550
  `coolingSetpointChanging: proceeding - isAutoMode=${isAutoMode}, isCoolingMode=${isCoolingMode}, isOff=${isOff}, haMode=${haHvacMode}`
162168
162551
  );
162169
162552
  }
@@ -162277,7 +162660,7 @@ var ThermostatServerBase = class extends FullFeaturedBase {
162277
162660
  const effectiveMax = max ?? 5e3;
162278
162661
  if (value == null || Number.isNaN(value)) {
162279
162662
  const defaultValue = type === "heat" ? 2e3 : 2400;
162280
- logger212.debug(
162663
+ logger214.debug(
162281
162664
  `${type} setpoint is undefined, using default: ${defaultValue}`
162282
162665
  );
162283
162666
  return Math.max(effectiveMin, Math.min(effectiveMax, defaultValue));
@@ -162855,7 +163238,7 @@ function ClimateDevice(homeAssistantEntity, includeBasicInformation = true) {
162855
163238
  }
162856
163239
 
162857
163240
  // src/matter/endpoints/composed/composed-climate-fan-endpoint.ts
162858
- var logger213 = Logger.get("ComposedClimateFanEndpoint");
163241
+ var logger215 = Logger.get("ComposedClimateFanEndpoint");
162859
163242
  function createEndpointId3(entityId, customName) {
162860
163243
  const baseName = customName || entityId;
162861
163244
  return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
@@ -162893,7 +163276,7 @@ var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpo
162893
163276
  climateSub = new Endpoint(climateType, { id: `${endpointId}_climate` });
162894
163277
  } catch (error) {
162895
163278
  const message = error instanceof Error ? error.message : String(error);
162896
- logger213.warn(
163279
+ logger215.warn(
162897
163280
  `Companion fan: climate sub build failed for ${primaryEntityId}: ${message}`
162898
163281
  );
162899
163282
  return void 0;
@@ -162937,7 +163320,7 @@ var ComposedClimateFanEndpoint = class _ComposedClimateFanEndpoint extends Endpo
162937
163320
  endpointId,
162938
163321
  [climateSub, fanSub]
162939
163322
  );
162940
- logger213.info(`Created composed climate+fan endpoint ${primaryEntityId}`);
163323
+ logger215.info(`Created composed climate+fan endpoint ${primaryEntityId}`);
162941
163324
  return endpoint;
162942
163325
  }
162943
163326
  constructor(type, entityId, id, parts) {
@@ -163032,7 +163415,7 @@ init_home_assistant_entity_behavior();
163032
163415
  // src/matter/behaviors/pressure-measurement-server.ts
163033
163416
  init_esm();
163034
163417
  init_home_assistant_entity_behavior();
163035
- var logger214 = Logger.get("PressureMeasurementServer");
163418
+ var logger216 = Logger.get("PressureMeasurementServer");
163036
163419
  var MIN_PRESSURE = 300;
163037
163420
  var MAX_PRESSURE = 1100;
163038
163421
  var PressureMeasurementServerBase = class extends PressureMeasurementServer {
@@ -163060,7 +163443,7 @@ var PressureMeasurementServerBase = class extends PressureMeasurementServer {
163060
163443
  }
163061
163444
  const rounded = Math.round(value);
163062
163445
  if (rounded < MIN_PRESSURE || rounded > MAX_PRESSURE) {
163063
- logger214.warn(
163446
+ logger216.warn(
163064
163447
  `Pressure value ${rounded} (raw: ${value}) for ${entity.entity_id} is outside valid range [${MIN_PRESSURE}-${MAX_PRESSURE}], ignoring`
163065
163448
  );
163066
163449
  return null;
@@ -163079,7 +163462,7 @@ function PressureMeasurementServer2(config8) {
163079
163462
  }
163080
163463
 
163081
163464
  // src/matter/endpoints/composed/composed-sensor-endpoint.ts
163082
- var logger215 = Logger.get("ComposedSensorEndpoint");
163465
+ var logger217 = Logger.get("ComposedSensorEndpoint");
163083
163466
  var temperatureConfig2 = {
163084
163467
  getValue(entity, agent) {
163085
163468
  const fallbackUnit = agent.env.get(HomeAssistantConfig).unitSystem.temperature;
@@ -163257,7 +163640,7 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
163257
163640
  if (config8.pressureEntityId && pressSub) {
163258
163641
  endpoint.subEndpoints.set(config8.pressureEntityId, pressSub);
163259
163642
  }
163260
- logger215.info(
163643
+ logger217.info(
163261
163644
  `Created composed sensor ${primaryEntityId} with ${parts.length} sub-endpoint(s): T${humSub ? "+H" : ""}${pressSub ? "+P" : ""}${config8.batteryEntityId ? "+Bat" : ""}${config8.powerEntityId ? "+Pwr" : ""}${config8.energyEntityId ? "+Nrg" : ""}`
163262
163645
  );
163263
163646
  return endpoint;
@@ -163394,7 +163777,7 @@ init_home_assistant_entity_behavior();
163394
163777
  // src/matter/behaviors/mode-select-server.ts
163395
163778
  init_esm();
163396
163779
  init_home_assistant_entity_behavior();
163397
- var logger216 = Logger.get("ModeSelectServer");
163780
+ var logger218 = Logger.get("ModeSelectServer");
163398
163781
  function buildSupportedModes(options) {
163399
163782
  return options.map((label, index) => ({
163400
163783
  label: label.length > 64 ? label.substring(0, 64) : label,
@@ -163431,13 +163814,13 @@ var ModeSelectServerBase = class extends ModeSelectServer {
163431
163814
  const options = config8.getOptions(homeAssistant.entity);
163432
163815
  const { newMode } = request;
163433
163816
  if (newMode < 0 || newMode >= options.length) {
163434
- logger216.warn(
163817
+ logger218.warn(
163435
163818
  `[${homeAssistant.entityId}] Invalid mode ${newMode}, options: [${options.join(", ")}]`
163436
163819
  );
163437
163820
  return;
163438
163821
  }
163439
163822
  const option = options[newMode];
163440
- logger216.info(
163823
+ logger218.info(
163441
163824
  `[${homeAssistant.entityId}] changeToMode(${newMode}) -> "${option}"`
163442
163825
  );
163443
163826
  applyPatchState(this.state, { currentMode: newMode });
@@ -163988,7 +164371,7 @@ var CoAlarmWithBatteryType = SmokeCoAlarmDevice.with(
163988
164371
  );
163989
164372
 
163990
164373
  // src/matter/endpoints/legacy/binary-sensor/index.ts
163991
- var logger217 = Logger.get("BinarySensorDevice");
164374
+ var logger219 = Logger.get("BinarySensorDevice");
163992
164375
  var deviceClasses = {
163993
164376
  [BinarySensorDeviceClass.CarbonMonoxide]: CoAlarmType,
163994
164377
  [BinarySensorDeviceClass.Gas]: CoAlarmType,
@@ -164039,11 +164422,11 @@ function BinarySensorDevice(homeAssistantEntity) {
164039
164422
  const originalTypeName = type.name;
164040
164423
  if (hasBattery && batteryTypes.has(type)) {
164041
164424
  type = batteryTypes.get(type);
164042
- logger217.info(
164425
+ logger219.info(
164043
164426
  `[${entityId}] Using battery variant: ${originalTypeName} -> ${type.name}, batteryAttr=${hasBatteryAttr}, batteryEntity=${homeAssistantEntity.mapping?.batteryEntity ?? "none"}`
164044
164427
  );
164045
164428
  } else if (hasBattery) {
164046
- logger217.warn(
164429
+ logger219.warn(
164047
164430
  `[${entityId}] Has battery but no variant available for ${originalTypeName}`
164048
164431
  );
164049
164432
  }
@@ -164130,7 +164513,7 @@ init_home_assistant_entity_behavior();
164130
164513
  init_esm();
164131
164514
  init_home_assistant_actions();
164132
164515
  init_home_assistant_entity_behavior();
164133
- var logger218 = Logger.get("WindowCoveringServer");
164516
+ var logger220 = Logger.get("WindowCoveringServer");
164134
164517
  var MovementStatus = WindowCovering3.MovementStatus;
164135
164518
  var FeaturedBase7 = WindowCoveringServer.with(
164136
164519
  "Lift",
@@ -164341,7 +164724,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164341
164724
  }
164342
164725
  return existing100ths ?? current100ths;
164343
164726
  };
164344
- logger218.debug(
164727
+ logger220.debug(
164345
164728
  `Cover update for ${entity.entity_id}: state=${state.state}, lift=${currentLift}%, tilt=${currentTilt}%, ha=${MovementStatus[movementStatus]}, effective=${MovementStatus[globalStatus]}`
164346
164729
  );
164347
164730
  const overrideType = config8.getCoverType?.(state, this.agent);
@@ -164386,9 +164769,9 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164386
164769
  );
164387
164770
  if (Object.keys(appliedPatch).length > 0) {
164388
164771
  const hasOperationalChange = "operationalStatus" in appliedPatch;
164389
- const log = hasOperationalChange ? logger218.info : logger218.debug;
164772
+ const log = hasOperationalChange ? logger220.info : logger220.debug;
164390
164773
  log.call(
164391
- logger218,
164774
+ logger220,
164392
164775
  `Cover ${entity.entity_id} state changed: ${JSON.stringify(appliedPatch)}`
164393
164776
  );
164394
164777
  }
@@ -164497,7 +164880,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164497
164880
  }
164498
164881
  });
164499
164882
  } catch (error) {
164500
- logger218.debug(
164883
+ logger220.debug(
164501
164884
  `Optimistic ${axis} timeout write failed (endpoint may be closing): ${error}`
164502
164885
  );
164503
164886
  }
@@ -164507,12 +164890,32 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164507
164890
  optimistic[axis] = { status: status3, startedAt, timer, expectedRestEnd };
164508
164891
  this.writeOperationalStatus({ [axis]: status3 });
164509
164892
  }
164893
+ // matter.js runs handleMovement as a worker, so a throw in there cannot fail
164894
+ // the command. The commands that start movement check up front instead, and
164895
+ // the HA call is deferred past the debounce anyway, outside the transaction
164896
+ // where a rollback could not undo the optimistic target (#446).
164897
+ async upOrOpen() {
164898
+ this.agent.get(HomeAssistantEntityBehavior).assertAvailable();
164899
+ return super.upOrOpen();
164900
+ }
164901
+ async downOrClose() {
164902
+ this.agent.get(HomeAssistantEntityBehavior).assertAvailable();
164903
+ return super.downOrClose();
164904
+ }
164905
+ async goToLiftPercentage(request) {
164906
+ this.agent.get(HomeAssistantEntityBehavior).assertAvailable();
164907
+ return super.goToLiftPercentage(request);
164908
+ }
164909
+ async goToTiltPercentage(request) {
164910
+ this.agent.get(HomeAssistantEntityBehavior).assertAvailable();
164911
+ return super.goToTiltPercentage(request);
164912
+ }
164510
164913
  async handleMovement(type, _, direction, targetPercent100ths) {
164511
164914
  const currentLift = this.state.currentPositionLiftPercent100ths ?? 0;
164512
164915
  const currentTilt = this.state.currentPositionTiltPercent100ths ?? 0;
164513
164916
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164514
164917
  const flags2 = this.state.config.getDiagnosticFlags?.(this.agent);
164515
- logger218.info(
164918
+ logger220.info(
164516
164919
  `handleMovement ${homeAssistant.entityId}: type=${MovementType[type]}, direction=${MovementDirection[direction]}, target=${targetPercent100ths}, currentLift=${currentLift}, currentTilt=${currentTilt}${flags2 ? `, ${flags2}` : ""}`
164517
164920
  );
164518
164921
  if (type === MovementType.Lift) {
@@ -164531,7 +164934,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164531
164934
  }
164532
164935
  } else if (type === MovementType.Tilt) {
164533
164936
  if (targetPercent100ths == null && this.lastLiftMovementDirection === direction && Date.now() - this.lastLiftMovementMs < 50) {
164534
- logger218.info(
164937
+ logger220.info(
164535
164938
  `Skipping tilt ${MovementDirection[direction]}, lift already moving in same direction`
164536
164939
  );
164537
164940
  return;
@@ -164576,7 +164979,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164576
164979
  } else {
164577
164980
  this.startOptimisticMovement("lift", MovementStatus.Opening, end);
164578
164981
  }
164579
- logger218.info(
164982
+ logger220.info(
164580
164983
  `handleLiftOpen ${homeAssistant.entityId}: calling action=${action.action}, atRest=${atRest}, ${this.state.config.getDiagnosticFlags?.(this.agent) ?? ""}`
164581
164984
  );
164582
164985
  homeAssistant.callAction(action);
@@ -164592,7 +164995,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164592
164995
  } else {
164593
164996
  this.startOptimisticMovement("lift", MovementStatus.Closing, end);
164594
164997
  }
164595
- logger218.info(
164998
+ logger220.info(
164596
164999
  `handleLiftClose ${homeAssistant.entityId}: calling action=${action.action}, atRest=${atRest}, ${this.state.config.getDiagnosticFlags?.(this.agent) ?? ""}`
164597
165000
  );
164598
165001
  homeAssistant.callAction(action);
@@ -164622,7 +165025,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164622
165025
  const isFirstInSequence = timeSinceLastCommand > _WindowCoveringServerBase.COMMAND_SEQUENCE_THRESHOLD_MS;
164623
165026
  const overrideMs = this.resolveDebounceOverride(homeAssistant);
164624
165027
  const debounceMs = overrideMs != null ? overrideMs : isFirstInSequence ? _WindowCoveringServerBase.DEBOUNCE_INITIAL_MS : _WindowCoveringServerBase.DEBOUNCE_SUBSEQUENT_MS;
164625
- logger218.debug(
165028
+ logger220.debug(
164626
165029
  `Lift command: target=${targetPosition}%, debounce=${debounceMs}ms (${overrideMs != null ? "override" : isFirstInSequence ? "initial" : "subsequent"})`
164627
165030
  );
164628
165031
  if (st.liftTimer) {
@@ -164654,7 +165057,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164654
165057
  this.startOptimisticMovement("tilt", MovementStatus.Opening, end);
164655
165058
  }
164656
165059
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164657
- logger218.info(
165060
+ logger220.info(
164658
165061
  `handleTiltOpen ${homeAssistant.entityId}: calling action=${action.action}, atRest=${atRest}, ${this.state.config.getDiagnosticFlags?.(this.agent) ?? ""}`
164659
165062
  );
164660
165063
  homeAssistant.callAction(action);
@@ -164672,7 +165075,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164672
165075
  this.startOptimisticMovement("tilt", MovementStatus.Closing, end);
164673
165076
  }
164674
165077
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164675
- logger218.info(
165078
+ logger220.info(
164676
165079
  `handleTiltClose ${homeAssistant.entityId}: calling action=${action.action}, atRest=${atRest}, ${this.state.config.getDiagnosticFlags?.(this.agent) ?? ""}`
164677
165080
  );
164678
165081
  homeAssistant.callAction(action);
@@ -164702,7 +165105,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
164702
165105
  const isFirstInSequence = timeSinceLastCommand > _WindowCoveringServerBase.COMMAND_SEQUENCE_THRESHOLD_MS;
164703
165106
  const overrideMs = this.resolveDebounceOverride(homeAssistant);
164704
165107
  const debounceMs = overrideMs != null ? overrideMs : isFirstInSequence ? _WindowCoveringServerBase.DEBOUNCE_INITIAL_MS : _WindowCoveringServerBase.DEBOUNCE_SUBSEQUENT_MS;
164705
- logger218.debug(
165108
+ logger220.debug(
164706
165109
  `Tilt command: target=${targetPosition}%, debounce=${debounceMs}ms (${overrideMs != null ? "override" : isFirstInSequence ? "initial" : "subsequent"})`
164707
165110
  );
164708
165111
  if (st.tiltTimer) {
@@ -164754,7 +165157,7 @@ function adjustPositionForWriting(position, flags2, matterSemantics) {
164754
165157
  }
164755
165158
 
164756
165159
  // src/matter/endpoints/legacy/cover/behaviors/cover-window-covering-server.ts
164757
- var logger219 = Logger.get("CoverWindowCoveringServer");
165160
+ var logger221 = Logger.get("CoverWindowCoveringServer");
164758
165161
  var attributes6 = (entity) => entity.attributes;
164759
165162
  var DEVICE_CLASS_TO_MATTER_TYPE = {
164760
165163
  curtain: {
@@ -164826,7 +165229,7 @@ var adjustPositionForReading2 = (position, agent) => {
164826
165229
  const { featureFlags } = agent.env.get(BridgeDataProvider);
164827
165230
  const matterSem = usesMatterSemantics(agent);
164828
165231
  const result = adjustPositionForReading(position, featureFlags, matterSem);
164829
- logger219.debug(`adjustPositionForReading: HA=${position}%, result=${result}%`);
165232
+ logger221.debug(`adjustPositionForReading: HA=${position}%, result=${result}%`);
164830
165233
  return result;
164831
165234
  };
164832
165235
  var adjustPositionForWriting2 = (position, agent) => {
@@ -165051,14 +165454,14 @@ var CoverAsDimmableLightWithBatteryType = DimmableLightDevice.with(
165051
165454
  );
165052
165455
 
165053
165456
  // src/matter/endpoints/legacy/cover/index.ts
165054
- var logger220 = Logger.get("CoverDevice");
165457
+ var logger222 = Logger.get("CoverDevice");
165055
165458
  var CoverDeviceType = (supportedFeatures, hasBattery, entityId) => {
165056
165459
  const features = /* @__PURE__ */ new Set();
165057
165460
  if (testBit(supportedFeatures, CoverSupportedFeatures.support_open)) {
165058
165461
  features.add("Lift");
165059
165462
  features.add("PositionAwareLift");
165060
165463
  } else {
165061
- logger220.warn(
165464
+ logger222.warn(
165062
165465
  `[${entityId}] Cover has no support_open feature (supported_features=${supportedFeatures}), adding Lift anyway`
165063
165466
  );
165064
165467
  features.add("Lift");
@@ -165073,7 +165476,7 @@ var CoverDeviceType = (supportedFeatures, hasBattery, entityId) => {
165073
165476
  features.add("PositionAwareTilt");
165074
165477
  }
165075
165478
  }
165076
- logger220.info(
165479
+ logger222.info(
165077
165480
  `[${entityId}] Creating WindowCovering with features: [${[...features].join(", ")}], supported_features=${supportedFeatures}`
165078
165481
  );
165079
165482
  const baseBehaviors2 = [
@@ -165097,16 +165500,16 @@ function CoverDevice(homeAssistantEntity) {
165097
165500
  const hasBatteryEntity = !!homeAssistantEntity.mapping?.batteryEntity;
165098
165501
  const hasBattery = hasBatteryAttr || hasBatteryEntity;
165099
165502
  if (hasBattery) {
165100
- logger220.info(
165503
+ logger222.info(
165101
165504
  `[${entityId}] Creating cover with PowerSource cluster, batteryAttr=${hasBatteryAttr}, batteryEntity=${homeAssistantEntity.mapping?.batteryEntity ?? "none"}`
165102
165505
  );
165103
165506
  } else {
165104
- logger220.debug(
165507
+ logger222.debug(
165105
165508
  `[${entityId}] Creating cover without battery (batteryAttr=${hasBatteryAttr}, batteryEntity=${homeAssistantEntity.mapping?.batteryEntity ?? "none"})`
165106
165509
  );
165107
165510
  }
165108
165511
  if (homeAssistantEntity.mapping?.coverExposeAsDimmableLight) {
165109
- logger220.info(`[${entityId}] Exposing cover as a Dimmable Light (#372)`);
165512
+ logger222.info(`[${entityId}] Exposing cover as a Dimmable Light (#372)`);
165110
165513
  const type = hasBattery ? CoverAsDimmableLightWithBatteryType : CoverAsDimmableLightType;
165111
165514
  return type.set({ homeAssistantEntity });
165112
165515
  }
@@ -165221,7 +165624,7 @@ function DishwasherEndpoint(homeAssistantEntity) {
165221
165624
  // src/matter/behaviors/generic-switch-server.ts
165222
165625
  init_esm();
165223
165626
  init_home_assistant_entity_behavior();
165224
- var logger221 = Logger.get("GenericSwitchServer");
165627
+ var logger223 = Logger.get("GenericSwitchServer");
165225
165628
  var SimpleBase = SwitchServer.with(
165226
165629
  "MomentarySwitch",
165227
165630
  "MomentarySwitchRelease",
@@ -165263,7 +165666,7 @@ var HaGenericSwitchServerBase = class extends SimpleBase {
165263
165666
  await super.initialize();
165264
165667
  const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
165265
165668
  const entityId = homeAssistant.entityId;
165266
- logger221.debug(`[${entityId}] GenericSwitch initialized (simple)`);
165669
+ logger223.debug(`[${entityId}] GenericSwitch initialized (simple)`);
165267
165670
  this.reactTo(homeAssistant.onChange, this.handleEventChange);
165268
165671
  }
165269
165672
  handleEventChange() {
@@ -165274,7 +165677,7 @@ var HaGenericSwitchServerBase = class extends SimpleBase {
165274
165677
  const eventType = attrs.event_type;
165275
165678
  if (!eventType) return;
165276
165679
  const entityId = homeAssistant.entityId;
165277
- logger221.debug(`[${entityId}] Event fired: ${eventType}`);
165680
+ logger223.debug(`[${entityId}] Event fired: ${eventType}`);
165278
165681
  this.triggerPress(eventType);
165279
165682
  }
165280
165683
  triggerPress(eventType) {
@@ -165320,7 +165723,7 @@ var HaGenericSwitchServerMultiBase = class extends FullBase {
165320
165723
  await super.initialize();
165321
165724
  const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
165322
165725
  const entityId = homeAssistant.entityId;
165323
- logger221.debug(`[${entityId}] GenericSwitch initialized (multi)`);
165726
+ logger223.debug(`[${entityId}] GenericSwitch initialized (multi)`);
165324
165727
  this.reactTo(homeAssistant.onChange, this.handleEventChange);
165325
165728
  }
165326
165729
  handleEventChange() {
@@ -165331,7 +165734,7 @@ var HaGenericSwitchServerMultiBase = class extends FullBase {
165331
165734
  const eventType = attrs.event_type;
165332
165735
  if (!eventType) return;
165333
165736
  const entityId = homeAssistant.entityId;
165334
- logger221.debug(`[${entityId}] Event fired: ${eventType}`);
165737
+ logger223.debug(`[${entityId}] Event fired: ${eventType}`);
165335
165738
  this.triggerPress(eventType);
165336
165739
  }
165337
165740
  triggerPress(eventType) {
@@ -165708,7 +166111,7 @@ init_nodejs();
165708
166111
  init_home_assistant_entity_behavior();
165709
166112
  var OperationalState4 = RvcOperationalState4.OperationalState;
165710
166113
  var ErrorState = RvcOperationalState4.ErrorState;
165711
- var logger222 = Logger.get("RvcOperationalStateServer");
166114
+ var logger224 = Logger.get("RvcOperationalStateServer");
165712
166115
  var activeStates = /* @__PURE__ */ new Set([
165713
166116
  OperationalState4.Running,
165714
166117
  OperationalState4.SeekingCharger
@@ -165760,7 +166163,7 @@ var RvcOperationalStateServerBase = class extends RvcOperationalStateServer {
165760
166163
  { force: true }
165761
166164
  );
165762
166165
  if (activeStates.has(previousState) && !activeStates.has(newState)) {
165763
- logger222.info(
166166
+ logger224.info(
165764
166167
  `Operation completed: ${OperationalState4[previousState]} -> ${OperationalState4[newState]}`
165765
166168
  );
165766
166169
  try {
@@ -165773,7 +166176,7 @@ var RvcOperationalStateServerBase = class extends RvcOperationalStateServer {
165773
166176
  this.context
165774
166177
  );
165775
166178
  } catch (e) {
165776
- logger222.debug("Failed to emit operationCompletion event:", e);
166179
+ logger224.debug("Failed to emit operationCompletion event:", e);
165777
166180
  }
165778
166181
  }
165779
166182
  }
@@ -165808,7 +166211,7 @@ var RvcOperationalStateServerBase = class extends RvcOperationalStateServer {
165808
166211
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
165809
166212
  homeAssistant.callAction(goHomeAction(void 0, this.agent));
165810
166213
  } else {
165811
- logger222.warn("GoHome command received but no goHome action configured");
166214
+ logger224.warn("GoHome command received but no goHome action configured");
165812
166215
  }
165813
166216
  return {
165814
166217
  commandResponseState: {
@@ -165828,7 +166231,7 @@ function RvcOperationalStateServer2(config8) {
165828
166231
  }
165829
166232
 
165830
166233
  // src/matter/endpoints/legacy/lawn-mower/behaviors/lawn-mower-rvc-operational-state-server.ts
165831
- var logger223 = Logger.get("LawnMowerRvcOperationalStateServer");
166234
+ var logger225 = Logger.get("LawnMowerRvcOperationalStateServer");
165832
166235
  function mapLawnMowerOperationalState(entity) {
165833
166236
  const state = entity.state;
165834
166237
  switch (state) {
@@ -165844,7 +166247,7 @@ function mapLawnMowerOperationalState(entity) {
165844
166247
  case "unavailable":
165845
166248
  return RvcOperationalState4.OperationalState.Error;
165846
166249
  default:
165847
- logger223.info(`Unknown lawn_mower state "${state}", treating as Stopped`);
166250
+ logger225.info(`Unknown lawn_mower state "${state}", treating as Stopped`);
165848
166251
  return RvcOperationalState4.OperationalState.Stopped;
165849
166252
  }
165850
166253
  }
@@ -165898,7 +166301,7 @@ function inferCleanedAreaProgress(cleanedSqm, orderedAreas) {
165898
166301
  }
165899
166302
 
165900
166303
  // src/matter/behaviors/rvc-run-mode-server.ts
165901
- var logger224 = Logger.get("RvcRunModeServer");
166304
+ var logger226 = Logger.get("RvcRunModeServer");
165902
166305
  var ROOM_MODE_BASE = 100;
165903
166306
  function isRoomMode(mode) {
165904
166307
  return mode >= ROOM_MODE_BASE;
@@ -166024,7 +166427,7 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
166024
166427
  const s = getSession(this.endpoint);
166025
166428
  if (s.loggedShortCircuits.has(reason)) return;
166026
166429
  s.loggedShortCircuits.add(reason);
166027
- logger224.info(message);
166430
+ logger226.info(message);
166028
166431
  }
166029
166432
  /**
166030
166433
  * Read the currentRoomEntity sensor and update currentArea + progress
@@ -166081,7 +166484,7 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
166081
166484
  }
166082
166485
  }
166083
166486
  if (matchedAreaId === null) {
166084
- logger224.info(
166487
+ logger226.info(
166085
166488
  `currentRoom sensor: no match for "${roomName}" (segmentId=${segmentId}), activeAreas=[${s.activeAreas.join(", ")}], supportedAreas=[${serviceArea.state.supportedAreas.map((a) => `${a.areaId}:${a.areaInfo.locationInfo?.locationName}`).join(", ")}]`
166086
166489
  );
166087
166490
  return;
@@ -166091,14 +166494,14 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
166091
166494
  s.completedAreas.add(s.lastCurrentArea);
166092
166495
  }
166093
166496
  s.lastCurrentArea = matchedAreaId;
166094
- logger224.info(
166497
+ logger226.info(
166095
166498
  `currentRoom sensor: transition to area ${matchedAreaId} ("${roomName}"), completed: [${[...s.completedAreas].join(", ")}]`
166096
166499
  );
166097
166500
  this.trySetCurrentArea(matchedAreaId);
166098
166501
  } catch (e) {
166099
166502
  const msg = e instanceof Error ? e.message : String(e);
166100
166503
  if (!msg.includes("No provider for") && !msg.includes("not supported")) {
166101
- logger224.warn(`currentRoom sensor update failed: ${msg}`);
166504
+ logger226.warn(`currentRoom sensor update failed: ${msg}`);
166102
166505
  }
166103
166506
  }
166104
166507
  }
@@ -166178,7 +166581,7 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
166178
166581
  cleaned,
166179
166582
  ordered
166180
166583
  );
166181
- logger224.debug(
166584
+ logger226.debug(
166182
166585
  `cleanedArea: raw=${raw} baseline=${s.cleanedAreaBaseline} cleaned=${cleaned} sizes=[${ordered.map((o) => `${o.areaId}:${o.sizeSqm}`).join(",")}] -> current=${currentArea} completed=[${completed.join(",")}]`
166183
166586
  );
166184
166587
  for (const id of completed) {
@@ -166192,7 +166595,7 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
166192
166595
  } catch (e) {
166193
166596
  const msg = e instanceof Error ? e.message : String(e);
166194
166597
  if (!msg.includes("No provider for") && !msg.includes("not supported")) {
166195
- logger224.warn(`cleanedArea room update failed: ${msg}`);
166598
+ logger226.warn(`cleanedArea room update failed: ${msg}`);
166196
166599
  }
166197
166600
  }
166198
166601
  }
@@ -166207,7 +166610,7 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
166207
166610
  const serviceArea = this.agent.get(ServiceAreaBehavior);
166208
166611
  if (serviceArea.state.currentArea !== areaId) {
166209
166612
  serviceArea.state.currentArea = areaId;
166210
- logger224.debug(`currentArea set to ${areaId}`);
166613
+ logger226.debug(`currentArea set to ${areaId}`);
166211
166614
  }
166212
166615
  this.updateProgress(serviceArea, areaId);
166213
166616
  } catch {
@@ -166490,7 +166893,7 @@ init_nodejs();
166490
166893
 
166491
166894
  // src/matter/behaviors/color-control-server.ts
166492
166895
  init_home_assistant_entity_behavior();
166493
- var logger225 = Logger.get("ColorControlServer");
166896
+ var logger227 = Logger.get("ColorControlServer");
166494
166897
  var optimisticColorState = /* @__PURE__ */ new Map();
166495
166898
  var OPTIMISTIC_TIMEOUT_MS3 = 3e3;
166496
166899
  var OPTIMISTIC_TOLERANCE2 = 5;
@@ -166529,7 +166932,7 @@ var ColorControlServerBase = class extends FeaturedBase8 {
166529
166932
  if (this.state.startUpColorTemperatureMireds == null) {
166530
166933
  this.state.startUpColorTemperatureMireds = defaultMireds;
166531
166934
  }
166532
- logger225.debug(
166935
+ logger227.debug(
166533
166936
  `initialize: set ColorTemperature defaults - min=${this.state.colorTempPhysicalMinMireds}, max=${this.state.colorTempPhysicalMaxMireds}, current=${this.state.colorTemperatureMireds}`
166534
166937
  );
166535
166938
  }
@@ -167083,8 +167486,9 @@ init_home_assistant_entity_behavior();
167083
167486
 
167084
167487
  // src/matter/behaviors/lock-server.ts
167085
167488
  init_esm();
167489
+ init_types2();
167086
167490
  init_home_assistant_entity_behavior();
167087
- var logger226 = Logger.get("LockServer");
167491
+ var logger228 = Logger.get("LockServer");
167088
167492
  var SUPPORTED_SLOT = 1;
167089
167493
  function normalizeSupportedIndex(index) {
167090
167494
  if (index === 0 || index === SUPPORTED_SLOT) {
@@ -167425,7 +167829,7 @@ var LockServerWithPinBase = class extends PinCredentialBase {
167425
167829
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
167426
167830
  const action = this.state.config.lock(void 0, this.agent);
167427
167831
  const hasPinProvided = !!request.pinCode;
167428
- logger226.debug(
167832
+ logger228.debug(
167429
167833
  `lockDoor called for ${homeAssistant.entityId}, PIN provided: ${hasPinProvided}`
167430
167834
  );
167431
167835
  if (request.pinCode) {
@@ -167438,12 +167842,12 @@ var LockServerWithPinBase = class extends PinCredentialBase {
167438
167842
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
167439
167843
  const action = this.state.config.unlock(void 0, this.agent);
167440
167844
  const hasPinProvided = !!request.pinCode;
167441
- logger226.debug(
167845
+ logger228.debug(
167442
167846
  `unlockDoor called for ${homeAssistant.entityId}, PIN provided: ${hasPinProvided}, requirePin: ${this.state.requirePinForRemoteOperation}`
167443
167847
  );
167444
167848
  if (this.state.requirePinForRemoteOperation) {
167445
167849
  if (!request.pinCode) {
167446
- logger226.info(
167850
+ logger228.info(
167447
167851
  `unlockDoor REJECTED for ${homeAssistant.entityId} - no PIN provided`
167448
167852
  );
167449
167853
  throw new StatusResponseError(
@@ -167453,12 +167857,12 @@ var LockServerWithPinBase = class extends PinCredentialBase {
167453
167857
  }
167454
167858
  const providedPin = new TextDecoder().decode(request.pinCode);
167455
167859
  if (!verifyStoredPinHelper(this.env, homeAssistant.entityId, providedPin)) {
167456
- logger226.info(
167860
+ logger228.info(
167457
167861
  `unlockDoor REJECTED for ${homeAssistant.entityId} - invalid PIN`
167458
167862
  );
167459
167863
  throw new StatusResponseError("Invalid PIN code", StatusCode.Failure);
167460
167864
  }
167461
- logger226.debug(`unlockDoor PIN verified for ${homeAssistant.entityId}`);
167865
+ logger228.debug(`unlockDoor PIN verified for ${homeAssistant.entityId}`);
167462
167866
  action.data = { ...action.data, code: providedPin };
167463
167867
  }
167464
167868
  homeAssistant.callAction(action);
@@ -167600,7 +168004,7 @@ var LockServerWithPinAndUnboltBase = class extends PinCredentialUnboltBase {
167600
168004
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
167601
168005
  const action = this.state.config.lock(void 0, this.agent);
167602
168006
  const hasPinProvided = !!request.pinCode;
167603
- logger226.debug(
168007
+ logger228.debug(
167604
168008
  `lockDoor called for ${homeAssistant.entityId}, PIN provided: ${hasPinProvided}`
167605
168009
  );
167606
168010
  if (request.pinCode) {
@@ -167614,12 +168018,12 @@ var LockServerWithPinAndUnboltBase = class extends PinCredentialUnboltBase {
167614
168018
  const unlatchConfig = this.state.config.unlatch;
167615
168019
  const action = unlatchConfig ? unlatchConfig(void 0, this.agent) : this.state.config.unlock(void 0, this.agent);
167616
168020
  const hasPinProvided = !!request.pinCode;
167617
- logger226.debug(
168021
+ logger228.debug(
167618
168022
  `unlockDoor called for ${homeAssistant.entityId}, PIN provided: ${hasPinProvided}, requirePin: ${this.state.requirePinForRemoteOperation}, usingUnlatch: ${!!unlatchConfig}`
167619
168023
  );
167620
168024
  if (this.state.requirePinForRemoteOperation) {
167621
168025
  if (!request.pinCode) {
167622
- logger226.info(
168026
+ logger228.info(
167623
168027
  `unlockDoor REJECTED for ${homeAssistant.entityId} - no PIN provided`
167624
168028
  );
167625
168029
  throw new StatusResponseError(
@@ -167629,12 +168033,12 @@ var LockServerWithPinAndUnboltBase = class extends PinCredentialUnboltBase {
167629
168033
  }
167630
168034
  const providedPin = new TextDecoder().decode(request.pinCode);
167631
168035
  if (!verifyStoredPinHelper(this.env, homeAssistant.entityId, providedPin)) {
167632
- logger226.info(
168036
+ logger228.info(
167633
168037
  `unlockDoor REJECTED for ${homeAssistant.entityId} - invalid PIN`
167634
168038
  );
167635
168039
  throw new StatusResponseError("Invalid PIN code", StatusCode.Failure);
167636
168040
  }
167637
- logger226.debug(`unlockDoor PIN verified for ${homeAssistant.entityId}`);
168041
+ logger228.debug(`unlockDoor PIN verified for ${homeAssistant.entityId}`);
167638
168042
  action.data = { ...action.data, code: providedPin };
167639
168043
  }
167640
168044
  homeAssistant.callAction(action);
@@ -167643,12 +168047,12 @@ var LockServerWithPinAndUnboltBase = class extends PinCredentialUnboltBase {
167643
168047
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
167644
168048
  const action = this.state.config.unlock(void 0, this.agent);
167645
168049
  const hasPinProvided = !!request.pinCode;
167646
- logger226.debug(
168050
+ logger228.debug(
167647
168051
  `unboltDoor called for ${homeAssistant.entityId}, PIN provided: ${hasPinProvided}, requirePin: ${this.state.requirePinForRemoteOperation}`
167648
168052
  );
167649
168053
  if (this.state.requirePinForRemoteOperation) {
167650
168054
  if (!request.pinCode) {
167651
- logger226.info(
168055
+ logger228.info(
167652
168056
  `unboltDoor REJECTED for ${homeAssistant.entityId} - no PIN provided`
167653
168057
  );
167654
168058
  throw new StatusResponseError(
@@ -167658,12 +168062,12 @@ var LockServerWithPinAndUnboltBase = class extends PinCredentialUnboltBase {
167658
168062
  }
167659
168063
  const providedPin = new TextDecoder().decode(request.pinCode);
167660
168064
  if (!verifyStoredPinHelper(this.env, homeAssistant.entityId, providedPin)) {
167661
- logger226.info(
168065
+ logger228.info(
167662
168066
  `unboltDoor REJECTED for ${homeAssistant.entityId} - invalid PIN`
167663
168067
  );
167664
168068
  throw new StatusResponseError("Invalid PIN code", StatusCode.Failure);
167665
168069
  }
167666
- logger226.debug(`unboltDoor PIN verified for ${homeAssistant.entityId}`);
168070
+ logger228.debug(`unboltDoor PIN verified for ${homeAssistant.entityId}`);
167667
168071
  action.data = { ...action.data, code: providedPin };
167668
168072
  }
167669
168073
  homeAssistant.callAction(action);
@@ -167802,7 +168206,7 @@ init_home_assistant_entity_behavior();
167802
168206
  init_dist();
167803
168207
  init_esm();
167804
168208
  init_home_assistant_entity_behavior();
167805
- var logger227 = Logger.get("MediaPlayerKeypadInputServer");
168209
+ var logger229 = Logger.get("MediaPlayerKeypadInputServer");
167806
168210
  var MediaPlayerKeypadInputServer = class extends KeypadInputServer {
167807
168211
  sendKey(request) {
167808
168212
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
@@ -167813,12 +168217,12 @@ var MediaPlayerKeypadInputServer = class extends KeypadInputServer {
167813
168217
  const features = attributes9.supported_features ?? 0;
167814
168218
  const action = this.mapKeyToAction(request.keyCode, features);
167815
168219
  if (!action) {
167816
- logger227.debug(
168220
+ logger229.debug(
167817
168221
  `Unsupported key code ${request.keyCode} for ${homeAssistant.entityId}`
167818
168222
  );
167819
168223
  return { status: KeypadInput3.Status.UnsupportedKey };
167820
168224
  }
167821
- logger227.debug(
168225
+ logger229.debug(
167822
168226
  `sendKey(${request.keyCode}) \u2192 ${action} for ${homeAssistant.entityId}`
167823
168227
  );
167824
168228
  homeAssistant.callAction({ action });
@@ -168097,7 +168501,7 @@ init_home_assistant_entity_behavior();
168097
168501
  // src/matter/behaviors/speaker-level-control-server.ts
168098
168502
  init_esm();
168099
168503
  init_home_assistant_entity_behavior();
168100
- var logger228 = Logger.get("SpeakerLevelControlServer");
168504
+ var logger230 = Logger.get("SpeakerLevelControlServer");
168101
168505
  var optimisticLevelState2 = /* @__PURE__ */ new Map();
168102
168506
  var OPTIMISTIC_TIMEOUT_MS4 = 3e3;
168103
168507
  var OPTIMISTIC_TOLERANCE3 = 5;
@@ -168140,7 +168544,7 @@ var SpeakerLevelControlServerBase = class extends FeaturedBase9 {
168140
168544
  currentLevel = Math.min(Math.max(minLevel, currentLevel), maxLevel);
168141
168545
  }
168142
168546
  const entityId = this.agent.get(HomeAssistantEntityBehavior).entity.entity_id;
168143
- logger228.debug(
168547
+ logger230.debug(
168144
168548
  `[${entityId}] Volume update: HA=${currentLevelPercent != null ? Math.round(currentLevelPercent * 100) : "null"}% -> currentLevel=${currentLevel}`
168145
168549
  );
168146
168550
  const optimistic = optimisticLevelState2.get(entity.entity_id);
@@ -168188,7 +168592,7 @@ var SpeakerLevelControlServerBase = class extends FeaturedBase9 {
168188
168592
  const config8 = this.state.config;
168189
168593
  const entityId = homeAssistant.entity.entity_id;
168190
168594
  const levelPercent = level / 254;
168191
- logger228.debug(
168595
+ logger230.debug(
168192
168596
  `[${entityId}] Volume command: level=${level} -> HA volume_level=${levelPercent}`
168193
168597
  );
168194
168598
  const current = config8.getValuePercent(
@@ -169075,6 +169479,7 @@ var ElectricalUtilityMeterType = ElectricalUtilityMeterDevice.with(
169075
169479
 
169076
169480
  // src/matter/endpoints/legacy/sensor/devices/energy-evse.ts
169077
169481
  init_esm();
169482
+ init_types2();
169078
169483
  init_home_assistant_actions();
169079
169484
  init_home_assistant_entity_behavior();
169080
169485
 
@@ -169132,7 +169537,7 @@ function mapEvseStatus(rawState, chargingSwitchOn) {
169132
169537
  }
169133
169538
 
169134
169539
  // src/matter/endpoints/legacy/sensor/devices/energy-evse.ts
169135
- var logger229 = Logger.get("EnergyEvse");
169540
+ var logger231 = Logger.get("EnergyEvse");
169136
169541
  var MIN_CHARGE_CURRENT_MA = 6e3;
169137
169542
  var MAX_CHARGE_CURRENT_MA = 32e3;
169138
169543
  var CIRCUIT_CAPACITY_MA = 32e3;
@@ -169216,7 +169621,7 @@ var EvseStatusServer = class _EvseStatusServer extends EnergyEvseServer {
169216
169621
  dispatched = true;
169217
169622
  }
169218
169623
  if (!dispatched) {
169219
- logger229.debug(
169624
+ logger231.debug(
169220
169625
  `enableCharging ignored for ${homeAssistant.entityId}: no charging switch or current limit mapped`
169221
169626
  );
169222
169627
  return;
@@ -169256,7 +169661,7 @@ var EvseStatusServer = class _EvseStatusServer extends EnergyEvseServer {
169256
169661
  chargingEnabledUntil: null
169257
169662
  });
169258
169663
  } catch (error) {
169259
- logger229.debug(
169664
+ logger231.debug(
169260
169665
  `EVSE charge-window expiry write failed (endpoint may be closing): ${error}`
169261
169666
  );
169262
169667
  } finally {
@@ -169287,7 +169692,7 @@ var EvseStatusServer = class _EvseStatusServer extends EnergyEvseServer {
169287
169692
  chargingEnabledUntil: null
169288
169693
  });
169289
169694
  } else {
169290
- logger229.debug(
169695
+ logger231.debug(
169291
169696
  `disable ignored for ${homeAssistant.entityId}: no charging switch mapped`
169292
169697
  );
169293
169698
  }
@@ -170092,7 +170497,7 @@ var TvocConcentrationMeasurementServer = class extends TvocConcentrationMeasurem
170092
170497
  };
170093
170498
 
170094
170499
  // src/matter/endpoints/legacy/sensor/devices/tvoc-sensor.ts
170095
- var logger230 = Logger.get("TvocSensor");
170500
+ var logger232 = Logger.get("TvocSensor");
170096
170501
  function airQualityFromUgm3(value) {
170097
170502
  if (value <= 300) return AirQuality3.AirQualityEnum.Good;
170098
170503
  if (value <= 1e3) return AirQuality3.AirQualityEnum.Fair;
@@ -170133,17 +170538,17 @@ var TvocAirQualityServer = class extends TvocAirQualityServerBase {
170133
170538
  const attributes9 = entity.state.attributes;
170134
170539
  const deviceClass = attributes9.device_class;
170135
170540
  let airQuality = AirQuality3.AirQualityEnum.Unknown;
170136
- logger230.debug(
170541
+ logger232.debug(
170137
170542
  `[${entity.entity_id}] TVOC update: state="${state}", device_class="${deviceClass}"`
170138
170543
  );
170139
170544
  if (state != null && !Number.isNaN(+state)) {
170140
170545
  const value = +state;
170141
170546
  airQuality = deviceClass === SensorDeviceClass.volatile_organic_compounds ? airQualityFromUgm3(value) : airQualityFromPpb(value);
170142
- logger230.debug(
170547
+ logger232.debug(
170143
170548
  `[${entity.entity_id}] TVOC value=${value} (${deviceClass}) -> airQuality=${AirQuality3.AirQualityEnum[airQuality]}`
170144
170549
  );
170145
170550
  } else {
170146
- logger230.warn(
170551
+ logger232.warn(
170147
170552
  `[${entity.entity_id}] TVOC state not a valid number: "${state}"`
170148
170553
  );
170149
170554
  }
@@ -170160,6 +170565,7 @@ var TvocSensorType = AirQualitySensorDevice.with(
170160
170565
 
170161
170566
  // src/matter/endpoints/legacy/sensor/index.ts
170162
170567
  init_dist();
170568
+ init_types2();
170163
170569
  init_diagnostic_event_bus();
170164
170570
 
170165
170571
  // src/matter/behaviors/carbon-dioxide-concentration-measurement-server.ts
@@ -170357,7 +170763,7 @@ init_home_assistant_entity_behavior();
170357
170763
  // src/matter/behaviors/pm25-concentration-measurement-server.ts
170358
170764
  init_esm();
170359
170765
  init_home_assistant_entity_behavior();
170360
- var logger231 = Logger.get("Pm25ConcentrationMeasurementServer");
170766
+ var logger233 = Logger.get("Pm25ConcentrationMeasurementServer");
170361
170767
  var Pm25ConcentrationMeasurementServerBase = Pm25ConcentrationMeasurementServer.with(
170362
170768
  ConcentrationMeasurement3.Feature.NumericMeasurement
170363
170769
  );
@@ -170381,11 +170787,11 @@ var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasure
170381
170787
  if (this.state.measurementMedium === void 0) {
170382
170788
  this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
170383
170789
  }
170384
- logger231.debug(
170790
+ logger233.debug(
170385
170791
  "Pm25ConcentrationMeasurementServer: before super.initialize()"
170386
170792
  );
170387
170793
  await super.initialize();
170388
- logger231.debug(
170794
+ logger233.debug(
170389
170795
  "Pm25ConcentrationMeasurementServer: after super.initialize()"
170390
170796
  );
170391
170797
  const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
@@ -170408,7 +170814,7 @@ var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasure
170408
170814
  };
170409
170815
 
170410
170816
  // src/matter/endpoints/legacy/sensor/devices/pm25-sensor.ts
170411
- var logger232 = Logger.get("Pm25AirQualityServer");
170817
+ var logger234 = Logger.get("Pm25AirQualityServer");
170412
170818
  var Pm25AirQualityServerBase = AirQualityServer.with(
170413
170819
  AirQuality3.Feature.Fair,
170414
170820
  AirQuality3.Feature.Moderate,
@@ -170420,9 +170826,9 @@ var Pm25AirQualityServer = class extends Pm25AirQualityServerBase {
170420
170826
  if (this.state.airQuality === void 0) {
170421
170827
  this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
170422
170828
  }
170423
- logger232.debug("Pm25AirQualityServer: before super.initialize()");
170829
+ logger234.debug("Pm25AirQualityServer: before super.initialize()");
170424
170830
  await super.initialize();
170425
- logger232.debug("Pm25AirQualityServer: after super.initialize()");
170831
+ logger234.debug("Pm25AirQualityServer: after super.initialize()");
170426
170832
  const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
170427
170833
  this.update(homeAssistant.entity);
170428
170834
  this.reactTo(homeAssistant.onChange, this.update, { offline: true });
@@ -170847,56 +171253,25 @@ init_home_assistant_entity_behavior();
170847
171253
  init_dist();
170848
171254
  init_esm();
170849
171255
  init_home_assistant_entity_behavior();
170850
- var logger233 = Logger.get("VacuumIdentifyServer");
170851
- var IDENTIFY_BUTTON_SUFFIXES = ["_identify", "_locate", "_find_me"];
171256
+ var logger235 = Logger.get("VacuumIdentifyServer");
170852
171257
  var VacuumIdentifyServer = class extends IdentifyServer2 {
170853
- triggerEffect(effect) {
170854
- this.#locate("triggerEffect");
170855
- return super.triggerEffect(effect);
170856
- }
170857
- identify(request) {
170858
- if (request.identifyTime > 0) {
170859
- this.#locate("identify");
170860
- }
170861
- return super.identify(request);
170862
- }
170863
- #locate(source) {
171258
+ identifyInHomeAssistant(source) {
170864
171259
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
170865
171260
  const features = homeAssistant.entity.state.attributes.supported_features ?? 0;
170866
171261
  if (testBit(features, VacuumDeviceFeature.LOCATE)) {
170867
- logger233.info(`${source} \u2192 vacuum.locate for ${homeAssistant.entityId}`);
171262
+ logger235.info(`${source} \u2192 vacuum.locate for ${homeAssistant.entityId}`);
170868
171263
  homeAssistant.callAction({ action: "vacuum.locate" });
170869
171264
  return;
170870
171265
  }
170871
- const sibling = this.#findIdentifyButton(homeAssistant);
170872
- if (sibling) {
170873
- logger233.info(
170874
- `${source} \u2192 button.press ${sibling} for ${homeAssistant.entityId}`
170875
- );
170876
- homeAssistant.callAction({ action: "button.press", target: sibling });
171266
+ if (this.findIdentifyEntity(homeAssistant)) {
171267
+ super.identifyInHomeAssistant(source);
170877
171268
  return;
170878
171269
  }
170879
- logger233.warn(
171270
+ logger235.warn(
170880
171271
  `${source} for ${homeAssistant.entityId}, LOCATE not in supported_features (${features}), trying vacuum.locate anyway`
170881
171272
  );
170882
171273
  homeAssistant.callAction({ action: "vacuum.locate" });
170883
171274
  }
170884
- #findIdentifyButton(homeAssistant) {
170885
- const deviceId = homeAssistant.entity.registry?.device_id;
170886
- if (!deviceId) return void 0;
170887
- const registry3 = this.env.get(HomeAssistantRegistry);
170888
- for (const entity of Object.values(registry3.entities)) {
170889
- if (entity.device_id !== deviceId) continue;
170890
- if (!entity.entity_id.startsWith("button.")) continue;
170891
- const uniqueId = entity.unique_id ?? "";
170892
- if (IDENTIFY_BUTTON_SUFFIXES.some(
170893
- (s) => entity.entity_id.endsWith(s) || uniqueId.endsWith(s)
170894
- )) {
170895
- return entity.entity_id;
170896
- }
170897
- }
170898
- return void 0;
170899
- }
170900
171275
  };
170901
171276
 
170902
171277
  // src/matter/endpoints/legacy/vacuum/behaviors/vacuum-rvc-run-mode-server.ts
@@ -171076,7 +171451,7 @@ init_esm();
171076
171451
  // src/matter/behaviors/service-area-server.ts
171077
171452
  init_esm();
171078
171453
  init_home_assistant_entity_behavior();
171079
- var logger234 = Logger.get("ServiceAreaServer");
171454
+ var logger236 = Logger.get("ServiceAreaServer");
171080
171455
  function orderSelection(areas, agent) {
171081
171456
  try {
171082
171457
  const ascending = agent?.get(HomeAssistantEntityBehavior).state.mapping?.vacuumAscendingRoomOrder === true;
@@ -171091,7 +171466,7 @@ var ServiceAreaWithProgress = ServiceAreaBehavior.with(
171091
171466
  var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
171092
171467
  selectAreas(request) {
171093
171468
  const { newAreas } = request;
171094
- logger234.info(
171469
+ logger236.info(
171095
171470
  `ServiceArea selectAreas called with: ${JSON.stringify(newAreas)}`
171096
171471
  );
171097
171472
  const uniqueAreas = orderSelection([...new Set(newAreas)], this.agent);
@@ -171100,7 +171475,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
171100
171475
  (id) => !supportedAreaIds.includes(id)
171101
171476
  );
171102
171477
  if (invalidAreas.length > 0) {
171103
- logger234.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
171478
+ logger236.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
171104
171479
  return {
171105
171480
  status: ServiceArea3.SelectAreasStatus.UnsupportedArea,
171106
171481
  statusText: `Invalid area IDs: ${invalidAreas.join(", ")}`
@@ -171112,7 +171487,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
171112
171487
  status: ServiceArea3.OperationalStatus.Pending
171113
171488
  }));
171114
171489
  this.state.currentArea = null;
171115
- logger234.info(
171490
+ logger236.info(
171116
171491
  `ServiceArea: Stored ${uniqueAreas.length} areas for cleaning: ${uniqueAreas.join(", ")}`
171117
171492
  );
171118
171493
  return {
@@ -171133,7 +171508,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
171133
171508
  ServiceAreaServerBase2.State = State;
171134
171509
  })(ServiceAreaServerBase || (ServiceAreaServerBase = {}));
171135
171510
  function ServiceAreaServer2(initialState) {
171136
- logger234.info(
171511
+ logger236.info(
171137
171512
  `Creating ServiceAreaServer with ${initialState.supportedAreas.length} areas`
171138
171513
  );
171139
171514
  return ServiceAreaServerBase.set({
@@ -171150,7 +171525,7 @@ var ServiceAreaWithMapsAndProgress = ServiceAreaBehavior.with(
171150
171525
  var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress {
171151
171526
  selectAreas(request) {
171152
171527
  const { newAreas } = request;
171153
- logger234.info(
171528
+ logger236.info(
171154
171529
  `ServiceArea selectAreas called with: ${JSON.stringify(newAreas)}`
171155
171530
  );
171156
171531
  const uniqueAreas = orderSelection([...new Set(newAreas)], this.agent);
@@ -171159,7 +171534,7 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
171159
171534
  (id) => !supportedAreaIds.includes(id)
171160
171535
  );
171161
171536
  if (invalidAreas.length > 0) {
171162
- logger234.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
171537
+ logger236.warn(`Invalid area IDs requested: ${invalidAreas.join(", ")}`);
171163
171538
  return {
171164
171539
  status: ServiceArea3.SelectAreasStatus.UnsupportedArea,
171165
171540
  statusText: `Invalid area IDs: ${invalidAreas.join(", ")}`
@@ -171171,7 +171546,7 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
171171
171546
  status: ServiceArea3.OperationalStatus.Pending
171172
171547
  }));
171173
171548
  this.state.currentArea = null;
171174
- logger234.info(
171549
+ logger236.info(
171175
171550
  `ServiceArea: Stored ${uniqueAreas.length} areas for cleaning: ${uniqueAreas.join(", ")}`
171176
171551
  );
171177
171552
  return {
@@ -171192,14 +171567,14 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
171192
171567
  ServiceAreaServerWithMapsBase2.State = State;
171193
171568
  })(ServiceAreaServerWithMapsBase || (ServiceAreaServerWithMapsBase = {}));
171194
171569
  function ServiceAreaServerWithMaps(initialState) {
171195
- logger234.info(
171570
+ logger236.info(
171196
171571
  `Creating ServiceAreaServer with Maps: ${initialState.supportedAreas.length} areas, ${initialState.supportedMaps.length} maps`
171197
171572
  );
171198
171573
  for (const map of initialState.supportedMaps) {
171199
171574
  const areaCount = initialState.supportedAreas.filter(
171200
171575
  (a) => a.mapId === map.mapId
171201
171576
  ).length;
171202
- logger234.info(` Map ${map.mapId}: "${map.name}" (${areaCount} areas)`);
171577
+ logger236.info(` Map ${map.mapId}: "${map.name}" (${areaCount} areas)`);
171203
171578
  }
171204
171579
  return ServiceAreaServerWithMapsBase.set({
171205
171580
  supportedAreas: initialState.supportedAreas,
@@ -171211,7 +171586,7 @@ function ServiceAreaServerWithMaps(initialState) {
171211
171586
  }
171212
171587
 
171213
171588
  // src/matter/endpoints/legacy/vacuum/behaviors/vacuum-service-area-server.ts
171214
- var logger235 = Logger.get("VacuumServiceAreaServer");
171589
+ var logger237 = Logger.get("VacuumServiceAreaServer");
171215
171590
  function toAreaId(roomId) {
171216
171591
  if (typeof roomId === "number") {
171217
171592
  return roomId;
@@ -171290,13 +171665,13 @@ function createVacuumServiceAreaServer(attributes9, roomEntities, includeUnnamed
171290
171665
  let rooms;
171291
171666
  if (roomEntities && roomEntities.length > 0) {
171292
171667
  rooms = buttonEntitiesToRooms(roomEntities, attributes9);
171293
- logger235.info(
171668
+ logger237.info(
171294
171669
  `Using ${rooms.length} button entities as rooms: ${rooms.map((r) => r.name).join(", ")}`
171295
171670
  );
171296
171671
  } else {
171297
171672
  rooms = parseVacuumRooms(attributes9, includeUnnamedRooms);
171298
171673
  if (rooms.length > 0) {
171299
- logger235.info(
171674
+ logger237.info(
171300
171675
  `Using ${rooms.length} rooms from attributes: ${rooms.map((r) => r.name).join(", ")}`
171301
171676
  );
171302
171677
  }
@@ -171350,7 +171725,7 @@ function createCustomServiceAreaServer(customAreas) {
171350
171725
  landmarkInfo: null
171351
171726
  }
171352
171727
  }));
171353
- logger235.info(
171728
+ logger237.info(
171354
171729
  `Using ${customAreas.length} custom service areas: ${customAreas.map((a) => a.name).join(", ")}`
171355
171730
  );
171356
171731
  return ServiceAreaServer2({
@@ -171372,7 +171747,7 @@ function createCleanAreaServiceAreaServer(cleanAreaRooms) {
171372
171747
  landmarkInfo: null
171373
171748
  }
171374
171749
  }));
171375
- logger235.info(
171750
+ logger237.info(
171376
171751
  `Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA: ${cleanAreaRooms.map((r) => r.name).join(", ")}`
171377
171752
  );
171378
171753
  return ServiceAreaServer2({
@@ -171408,11 +171783,11 @@ function getVacuumServiceAreas(attributes9, mapping) {
171408
171783
  }
171409
171784
 
171410
171785
  // src/matter/endpoints/legacy/vacuum/behaviors/vacuum-rvc-run-mode-server.ts
171411
- var logger236 = Logger.get("VacuumRvcRunModeServer");
171786
+ var logger238 = Logger.get("VacuumRvcRunModeServer");
171412
171787
  function buildValetudoSegmentAction(vacuumEntityId, segmentIds, valetudoIdentifier) {
171413
171788
  const identifier = valetudoIdentifier || vacuumEntityId.replace(/^vacuum\.valetudo_/, "");
171414
171789
  const topic = `valetudo/${identifier}/MapSegmentationCapability/clean/set`;
171415
- logger236.info(
171790
+ logger238.info(
171416
171791
  `Valetudo: mqtt.publish to ${topic}, segments: ${segmentIds.join(", ")}`
171417
171792
  );
171418
171793
  return {
@@ -171517,14 +171892,14 @@ function mergeBatchData(areas) {
171517
171892
  function handleCustomServiceAreas(selectedAreas, customAreas, session) {
171518
171893
  const matched = selectedAreas.map((areaId) => ({ areaId, area: customAreas[areaId - 1] })).filter((m) => !!m.area);
171519
171894
  if (matched.length === 0) {
171520
- logger236.warn(
171895
+ logger238.warn(
171521
171896
  `Custom service areas: no match for selected IDs ${selectedAreas.join(", ")}`
171522
171897
  );
171523
171898
  return { action: "vacuum.start" };
171524
171899
  }
171525
171900
  const batchArea = matched.find(({ area }) => area.batchDispatch === true);
171526
171901
  if (batchArea) {
171527
- logger236.info(
171902
+ logger238.info(
171528
171903
  `Custom service areas (batch): single call for ${matched.length} room(s): ${matched.map(({ area }) => area.name).join(", ")}`
171529
171904
  );
171530
171905
  session.pendingDispatches = [];
@@ -171545,7 +171920,7 @@ function handleCustomServiceAreas(selectedAreas, customAreas, session) {
171545
171920
  }
171546
171921
  };
171547
171922
  }
171548
- logger236.info(
171923
+ logger238.info(
171549
171924
  `Custom service areas: ${matched.length} room(s) queued: ${matched.map(({ area }) => `${area.service} (${area.name})`).join(", ")}`
171550
171925
  );
171551
171926
  session.pendingDispatches = matched.slice(1).map(({ areaId, area }) => ({
@@ -171603,7 +171978,7 @@ function dispatchRoomClean(attributes9, mapping, entityId, selectedAreas, seam)
171603
171978
  if (cleanAreaRooms && cleanAreaRooms.length > 0) {
171604
171979
  const haAreaIds = resolveCleanAreaIds(selectedAreas, cleanAreaRooms);
171605
171980
  if (haAreaIds.length > 0) {
171606
- logger236.info(`CLEAN_AREA: cleaning HA areas: ${haAreaIds.join(", ")}`);
171981
+ logger238.info(`CLEAN_AREA: cleaning HA areas: ${haAreaIds.join(", ")}`);
171607
171982
  return {
171608
171983
  action: {
171609
171984
  action: "vacuum.clean_area",
@@ -171623,7 +171998,7 @@ function dispatchRoomClean(attributes9, mapping, entityId, selectedAreas, seam)
171623
171998
  }
171624
171999
  }
171625
172000
  if (matched.length > 0) {
171626
- logger236.info(
172001
+ logger238.info(
171627
172002
  `Roborock: ${matched.length} room button(s) queued: ${matched.map((m) => m.entityId).join(", ")}`
171628
172003
  );
171629
172004
  return {
@@ -171658,12 +172033,12 @@ function dispatchRoomClean(attributes9, mapping, entityId, selectedAreas, seam)
171658
172033
  }
171659
172034
  }
171660
172035
  if (roomIds.length > 0) {
171661
- logger236.info(`Starting cleaning with selected areas: ${roomIds.join(", ")}`);
172036
+ logger238.info(`Starting cleaning with selected areas: ${roomIds.join(", ")}`);
171662
172037
  if (isDreameVacuum(attributes9)) {
171663
172038
  if (targetMapName) {
171664
172039
  const vacName = entityId.replace("vacuum.", "");
171665
172040
  const selectedMapEntity = `select.${vacName}_selected_map`;
171666
- logger236.info(
172041
+ logger238.info(
171667
172042
  `Dreame multi-floor: switching to map "${targetMapName}" via ${selectedMapEntity}`
171668
172043
  );
171669
172044
  seam.callAction({
@@ -171691,7 +172066,7 @@ function dispatchRoomClean(attributes9, mapping, entityId, selectedAreas, seam)
171691
172066
  }
171692
172067
  if (isEcovacsVacuum(attributes9)) {
171693
172068
  const roomIdStr = roomIds.join(",");
171694
- logger236.info(`Ecovacs vacuum: Using spot_area for rooms: ${roomIdStr}`);
172069
+ logger238.info(`Ecovacs vacuum: Using spot_area for rooms: ${roomIdStr}`);
171695
172070
  return {
171696
172071
  action: {
171697
172072
  action: "vacuum.send_command",
@@ -171703,7 +172078,7 @@ function dispatchRoomClean(attributes9, mapping, entityId, selectedAreas, seam)
171703
172078
  pending: []
171704
172079
  };
171705
172080
  }
171706
- logger236.warn(
172081
+ logger238.warn(
171707
172082
  `Room cleaning via send_command not supported for this vacuum type. Rooms: ${roomIds.join(", ")}. Falling back to vacuum.start`
171708
172083
  );
171709
172084
  }
@@ -171712,7 +172087,7 @@ function dispatchRoomClean(attributes9, mapping, entityId, selectedAreas, seam)
171712
172087
  var vacuumRvcRunModeConfig = {
171713
172088
  getCurrentMode: (entity) => {
171714
172089
  const isCleaning = vacuumIsCleaning(entity.state);
171715
- logger236.debug(
172090
+ logger238.debug(
171716
172091
  `Vacuum state: "${entity.state}", isCleaning: ${isCleaning}, currentMode: ${isCleaning ? "Cleaning" : "Idle"}`
171717
172092
  );
171718
172093
  return isCleaning ? 1 /* Cleaning */ : 0 /* Idle */;
@@ -171751,7 +172126,7 @@ var vacuumRvcRunModeConfig = {
171751
172126
  }
171752
172127
  } catch {
171753
172128
  }
171754
- logger236.info("Starting regular cleaning (no areas selected)");
172129
+ logger238.info("Starting regular cleaning (no areas selected)");
171755
172130
  return { action: "vacuum.start" };
171756
172131
  },
171757
172132
  returnToBase: () => ({ action: "vacuum.return_to_base" }),
@@ -171766,7 +172141,7 @@ var vacuumRvcRunModeConfig = {
171766
172141
  const homeAssistant = agent.get(HomeAssistantEntityBehavior);
171767
172142
  const entity = homeAssistant.entity;
171768
172143
  const attributes9 = entity.state.attributes;
171769
- logger236.info(`cleanRoom called: roomMode=${roomMode}`);
172144
+ logger238.info(`cleanRoom called: roomMode=${roomMode}`);
171770
172145
  const cleanAreaRooms = homeAssistant.state.mapping?.cleanAreaRooms;
171771
172146
  if (cleanAreaRooms && cleanAreaRooms.length > 0) {
171772
172147
  const sorted = [...cleanAreaRooms].sort(
@@ -171775,7 +172150,7 @@ var vacuumRvcRunModeConfig = {
171775
172150
  const areaIndex = roomMode - ROOM_MODE_BASE2 - 1;
171776
172151
  if (areaIndex >= 0 && areaIndex < sorted.length) {
171777
172152
  const area = sorted[areaIndex];
171778
- logger236.info(
172153
+ logger238.info(
171779
172154
  `cleanRoom: CLEAN_AREA "${area.name}" \u2192 vacuum.clean_area(${area.haAreaId})`
171780
172155
  );
171781
172156
  return {
@@ -171792,7 +172167,7 @@ var vacuumRvcRunModeConfig = {
171792
172167
  const areaIndex = roomMode - ROOM_MODE_BASE2 - 1;
171793
172168
  if (areaIndex >= 0 && areaIndex < sorted.length) {
171794
172169
  const area = sorted[areaIndex];
171795
- logger236.info(
172170
+ logger238.info(
171796
172171
  `cleanRoom: custom service area "${area.name}" \u2192 ${area.service}`
171797
172172
  );
171798
172173
  return {
@@ -171813,7 +172188,7 @@ var vacuumRvcRunModeConfig = {
171813
172188
  }
171814
172189
  const rooms = parseVacuumRooms(attributes9);
171815
172190
  const numericIdFromMode = getRoomIdFromMode(roomMode);
171816
- logger236.info(
172191
+ logger238.info(
171817
172192
  `cleanRoom: numericIdFromMode=${numericIdFromMode}, available rooms: ${JSON.stringify(rooms.map((r) => ({ id: r.id, name: r.name, modeValue: getRoomModeValue(r) })))}`
171818
172193
  );
171819
172194
  const room = rooms.find((r) => getRoomModeValue(r) === roomMode);
@@ -171823,7 +172198,7 @@ var vacuumRvcRunModeConfig = {
171823
172198
  if (room.mapName) {
171824
172199
  const vacuumName = vacuumEntityId.replace("vacuum.", "");
171825
172200
  const selectedMapEntity = `select.${vacuumName}_selected_map`;
171826
- logger236.info(
172201
+ logger238.info(
171827
172202
  `Dreame multi-floor: switching to map "${room.mapName}" via ${selectedMapEntity}`
171828
172203
  );
171829
172204
  homeAssistant.callAction({
@@ -171832,7 +172207,7 @@ var vacuumRvcRunModeConfig = {
171832
172207
  data: { option: room.mapName }
171833
172208
  });
171834
172209
  }
171835
- logger236.debug(
172210
+ logger238.debug(
171836
172211
  `Dreame vacuum detected, using dreame_vacuum.vacuum_clean_segment for room ${room.name} (commandId: ${commandId3}, id: ${room.id})`
171837
172212
  );
171838
172213
  return {
@@ -171843,7 +172218,7 @@ var vacuumRvcRunModeConfig = {
171843
172218
  };
171844
172219
  }
171845
172220
  if (isRoborockVacuum(attributes9) || isXiaomiMiotVacuum(attributes9)) {
171846
- logger236.debug(
172221
+ logger238.debug(
171847
172222
  `Using vacuum.send_command with app_segment_clean for room ${room.name} (commandId: ${commandId3}, id: ${room.id})`
171848
172223
  );
171849
172224
  return {
@@ -171856,7 +172231,7 @@ var vacuumRvcRunModeConfig = {
171856
172231
  }
171857
172232
  if (isEcovacsVacuum(attributes9)) {
171858
172233
  const roomIdStr = String(commandId3);
171859
- logger236.info(
172234
+ logger238.info(
171860
172235
  `Ecovacs vacuum: Using spot_area for room ${room.name} (id: ${roomIdStr})`
171861
172236
  );
171862
172237
  return {
@@ -171871,7 +172246,7 @@ var vacuumRvcRunModeConfig = {
171871
172246
  }
171872
172247
  };
171873
172248
  }
171874
- logger236.warn(
172249
+ logger238.warn(
171875
172250
  `Room cleaning via send_command not supported for this vacuum type. Room: ${room.name} (id=${commandId3}). Falling back to vacuum.start`
171876
172251
  );
171877
172252
  }
@@ -171888,20 +172263,20 @@ function createVacuumRvcRunModeServer(attributes9, includeUnnamedRooms = false,
171888
172263
  customAreas,
171889
172264
  disableRoomModes
171890
172265
  );
171891
- logger236.info(
172266
+ logger238.info(
171892
172267
  `Creating VacuumRvcRunModeServer with ${rooms.length} rooms, ${supportedModes2.length} total modes`
171893
172268
  );
171894
172269
  if (rooms.length > 0) {
171895
- logger236.info(`Rooms found: ${rooms.map((r) => r.name).join(", ")}`);
172270
+ logger238.info(`Rooms found: ${rooms.map((r) => r.name).join(", ")}`);
171896
172271
  }
171897
172272
  if (filteredCount > 0) {
171898
172273
  const filtered = allRooms.filter((r) => !rooms.some((x) => x.id === r.id));
171899
- logger236.info(
172274
+ logger238.info(
171900
172275
  `Filtered out ${filteredCount} unnamed room(s): ${filtered.map((r) => r.name).join(", ")}`
171901
172276
  );
171902
172277
  }
171903
172278
  if (allRooms.length === 0) {
171904
- logger236.debug(
172279
+ logger238.debug(
171905
172280
  `No rooms found. Attributes: rooms=${JSON.stringify(attributes9.rooms)}, segments=${JSON.stringify(attributes9.segments)}, room_list=${attributes9.room_list}`
171906
172281
  );
171907
172282
  }
@@ -171935,7 +172310,7 @@ function createCleanAreaRvcRunModeServer(cleanAreaRooms) {
171935
172310
  modeTags: [{ value: RvcRunMode3.ModeTag.Cleaning }]
171936
172311
  });
171937
172312
  }
171938
- logger236.info(
172313
+ logger238.info(
171939
172314
  `Creating CLEAN_AREA RvcRunModeServer with ${cleanAreaRooms.length} HA areas, ${modes.length} total modes`
171940
172315
  );
171941
172316
  return RvcRunModeServer2(vacuumRvcRunModeConfig, {
@@ -172025,7 +172400,7 @@ init_nodejs();
172025
172400
 
172026
172401
  // src/matter/behaviors/rvc-clean-mode-server.ts
172027
172402
  init_home_assistant_entity_behavior();
172028
- var logger237 = Logger.get("RvcCleanModeServerBase");
172403
+ var logger239 = Logger.get("RvcCleanModeServerBase");
172029
172404
  var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeServer {
172030
172405
  // Pending mode from a recent changeToMode command.
172031
172406
  // Prevents stale HA state (from a different entity like select.xxx)
@@ -172072,14 +172447,14 @@ var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeS
172072
172447
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
172073
172448
  const { newMode } = request;
172074
172449
  if (newMode !== this.state.currentMode && !this.state.supportedModes.some((m) => m.mode === newMode)) {
172075
- logger237.warn(`changeToMode(${newMode}) rejected: unsupported mode`);
172450
+ logger239.warn(`changeToMode(${newMode}) rejected: unsupported mode`);
172076
172451
  return {
172077
172452
  status: ModeBase3.ModeChangeStatus.UnsupportedMode,
172078
172453
  statusText: `Unsupported mode: ${newMode}`
172079
172454
  };
172080
172455
  }
172081
172456
  const modeLabel = this.state.supportedModes.find((m) => m.mode === newMode);
172082
- logger237.info(
172457
+ logger239.info(
172083
172458
  `changeToMode(${newMode}) "${modeLabel?.label ?? "unknown"}" for ${homeAssistant.entityId}`
172084
172459
  );
172085
172460
  this.pendingMode = newMode;
@@ -172087,7 +172462,7 @@ var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeS
172087
172462
  this.state.currentMode = newMode;
172088
172463
  const action = this.state.config.setCleanMode(newMode, this.agent);
172089
172464
  if (action) {
172090
- logger237.info(
172465
+ logger239.info(
172091
172466
  `changeToMode: dispatching action ${action.action} \u2192 ${action.target ?? homeAssistant.entityId}`
172092
172467
  );
172093
172468
  homeAssistant.callAction(action);
@@ -172120,7 +172495,7 @@ function RvcCleanModeServer2(config8, initialState) {
172120
172495
  }
172121
172496
 
172122
172497
  // src/matter/endpoints/legacy/vacuum/behaviors/vacuum-rvc-clean-mode-server.ts
172123
- var logger238 = Logger.get("VacuumRvcCleanModeServer");
172498
+ var logger240 = Logger.get("VacuumRvcCleanModeServer");
172124
172499
  var MODE_VACUUM = 0;
172125
172500
  var MODE_VACUUM_AND_MOP = 1;
172126
172501
  var MODE_MOP = 2;
@@ -172390,7 +172765,7 @@ function findMatchingCleanOption(ct, availableOptions) {
172390
172765
  const match = availableOptions.find((o) => classifyCleanOption(o) === type);
172391
172766
  if (match) return match;
172392
172767
  }
172393
- logger238.warn(
172768
+ logger240.warn(
172394
172769
  `No match for ${CLEAN_TYPE_LABELS[ct]} in [${availableOptions.join(", ")}]`
172395
172770
  );
172396
172771
  return availableOptions[0];
@@ -172399,7 +172774,7 @@ function buildCleaningModeAction(targetCleanType, agent) {
172399
172774
  const selectEntityId = getCleaningModeSelectEntity(agent);
172400
172775
  const { options } = readSelectEntity(selectEntityId, agent);
172401
172776
  const optionToUse = findMatchingCleanOption(targetCleanType, options);
172402
- logger238.info(
172777
+ logger240.info(
172403
172778
  `Switching cleaning mode to: ${optionToUse} via ${selectEntityId}`
172404
172779
  );
172405
172780
  return {
@@ -172488,7 +172863,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172488
172863
  }
172489
172864
  }
172490
172865
  if (speedMode !== void 0) {
172491
- logger238.debug(
172866
+ logger240.debug(
172492
172867
  `Current mode: Vacuum + fan_speed="${speedState}" -> mode ${speedMode}`
172493
172868
  );
172494
172869
  return speedMode;
@@ -172509,7 +172884,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172509
172884
  }
172510
172885
  }
172511
172886
  if (mopMode !== void 0) {
172512
- logger238.debug(
172887
+ logger240.debug(
172513
172888
  `Current mode: Mop + intensity="${state}" -> mode ${mopMode}`
172514
172889
  );
172515
172890
  return mopMode;
@@ -172527,14 +172902,14 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172527
172902
  const homeAssistant = agent.get(HomeAssistantEntityBehavior);
172528
172903
  const vacuumEntityId = homeAssistant.entityId;
172529
172904
  const mapping = homeAssistant.state.mapping;
172530
- logger238.info(
172905
+ logger240.info(
172531
172906
  `setCleanMode(${mode}) for ${vacuumEntityId}, suctionEntity=${mapping?.suctionLevelEntity ?? "none"}, mopEntity=${mapping?.mopIntensityEntity ?? "none"}, fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])}, customTags=${JSON.stringify(customFanSpeedTags ?? {})}`
172532
172907
  );
172533
172908
  if (mopIntensityList && mopIntensityList.length > 0 && isMopIntensityMode(mode)) {
172534
172909
  const mopIndex = mode - MOP_INTENSITY_MODE_BASE;
172535
172910
  const mopName = mopIntensityList[mopIndex];
172536
172911
  if (!mopName) {
172537
- logger238.warn(`Invalid mop intensity mode index: ${mopIndex}`);
172912
+ logger240.warn(`Invalid mop intensity mode index: ${mopIndex}`);
172538
172913
  return void 0;
172539
172914
  }
172540
172915
  if (hasCleanTypes) {
@@ -172547,18 +172922,18 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172547
172922
  mapping.mopIntensityEntity,
172548
172923
  agent
172549
172924
  );
172550
- logger238.info(
172925
+ logger240.info(
172551
172926
  `Mop intensity entity ${mapping.mopIntensityEntity}: current="${state}", options=${JSON.stringify(options ?? [])}`
172552
172927
  );
172553
172928
  let option = matchMopIntensityOption(mopName, options);
172554
172929
  if (!option && options && mopIndex < options.length) {
172555
172930
  option = options[mopIndex];
172556
- logger238.info(
172931
+ logger240.info(
172557
172932
  `Positional match for mop "${mopName}" -> "${option}" (index ${mopIndex})`
172558
172933
  );
172559
172934
  }
172560
172935
  if (option) {
172561
- logger238.info(
172936
+ logger240.info(
172562
172937
  `Setting mop intensity to: ${option} via ${mapping.mopIntensityEntity}`
172563
172938
  );
172564
172939
  return {
@@ -172567,11 +172942,11 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172567
172942
  target: mapping.mopIntensityEntity
172568
172943
  };
172569
172944
  }
172570
- logger238.warn(
172945
+ logger240.warn(
172571
172946
  `No match for mop intensity "${mopName}" in options: [${(options ?? []).join(", ")}]`
172572
172947
  );
172573
172948
  } else {
172574
- logger238.warn(
172949
+ logger240.warn(
172575
172950
  `Mop intensity mode ${mode} requested but no mopIntensityEntity configured`
172576
172951
  );
172577
172952
  }
@@ -172581,7 +172956,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172581
172956
  const fanSpeedIndex = mode - FAN_SPEED_MODE_BASE;
172582
172957
  const fanSpeedName = fanSpeedList[fanSpeedIndex];
172583
172958
  if (!fanSpeedName) {
172584
- logger238.warn(`Invalid fan speed mode index: ${fanSpeedIndex}`);
172959
+ logger240.warn(`Invalid fan speed mode index: ${fanSpeedIndex}`);
172585
172960
  return void 0;
172586
172961
  }
172587
172962
  if (mapping?.suctionLevelEntity) {
@@ -172594,7 +172969,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172594
172969
  mapping.suctionLevelEntity,
172595
172970
  agent
172596
172971
  );
172597
- logger238.info(
172972
+ logger240.info(
172598
172973
  `Suction entity ${mapping.suctionLevelEntity}: current="${state}", options=${JSON.stringify(options ?? [])}`
172599
172974
  );
172600
172975
  let option = matchFanSpeedOption(
@@ -172604,12 +172979,12 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172604
172979
  );
172605
172980
  if (!option && options && fanSpeedIndex < options.length) {
172606
172981
  option = options[fanSpeedIndex];
172607
- logger238.info(
172982
+ logger240.info(
172608
172983
  `Positional match for fan "${fanSpeedName}" -> "${option}" (index ${fanSpeedIndex})`
172609
172984
  );
172610
172985
  }
172611
172986
  if (option) {
172612
- logger238.info(
172987
+ logger240.info(
172613
172988
  `Setting suction to: ${option} via ${mapping.suctionLevelEntity}`
172614
172989
  );
172615
172990
  return {
@@ -172618,7 +172993,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172618
172993
  target: mapping.suctionLevelEntity
172619
172994
  };
172620
172995
  }
172621
- logger238.warn(
172996
+ logger240.warn(
172622
172997
  `No match for fan speed "${fanSpeedName}" in suction options: [${(options ?? []).join(", ")}]`
172623
172998
  );
172624
172999
  return void 0;
@@ -172628,7 +173003,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172628
173003
  buildCleaningModeAction(0 /* Sweeping */, agent)
172629
173004
  );
172630
173005
  }
172631
- logger238.info(
173006
+ logger240.info(
172632
173007
  `Setting fan speed to: ${fanSpeedName} via vacuum.set_fan_speed`
172633
173008
  );
172634
173009
  return {
@@ -172638,7 +173013,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172638
173013
  };
172639
173014
  }
172640
173015
  if (!hasCleanTypes) {
172641
- logger238.debug(
173016
+ logger240.debug(
172642
173017
  `Ignoring cleaning type change (mode=${mode}): no cleaning mode entity`
172643
173018
  );
172644
173019
  return void 0;
@@ -172650,7 +173025,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
172650
173025
  agent
172651
173026
  );
172652
173027
  const optionToUse = findMatchingCleanOption(cleanType, availableOptions);
172653
- logger238.info(
173028
+ logger240.info(
172654
173029
  `Setting cleaning mode to: ${optionToUse} (mode=${mode}) via ${selectEntityId}`
172655
173030
  );
172656
173031
  return {
@@ -172668,10 +173043,10 @@ function createVacuumRvcCleanModeServer(_attributes, fanSpeedList, mopIntensityL
172668
173043
  cleaningModeOptions,
172669
173044
  customFanSpeedTags
172670
173045
  );
172671
- logger238.info(
173046
+ logger240.info(
172672
173047
  `Creating VacuumRvcCleanModeServer with ${supportedModes2.length} modes (fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])}, cleaningModeOptions=${JSON.stringify(cleaningModeOptions ?? [])}, customTags=${JSON.stringify(customFanSpeedTags ?? {})})`
172673
173048
  );
172674
- logger238.info(
173049
+ logger240.info(
172675
173050
  `Modes: ${supportedModes2.map((m) => `${m.mode}:${m.label}[${m.modeTags.map((t) => t.value).join(",")}]`).join(", ")}`
172676
173051
  );
172677
173052
  const initialState = {
@@ -172734,7 +173109,7 @@ function resolveMopIntensityList(mopIntensityEntity) {
172734
173109
  init_dist();
172735
173110
  init_esm();
172736
173111
  init_home_assistant_entity_behavior();
172737
- var logger239 = Logger.get("VacuumRvcOperationalStateServer");
173112
+ var logger241 = Logger.get("VacuumRvcOperationalStateServer");
172738
173113
  function batteryFromAttributes(attrs) {
172739
173114
  const raw = attrs.battery_level ?? attrs.battery;
172740
173115
  if (raw == null) return null;
@@ -172785,16 +173160,16 @@ function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttribute
172785
173160
  operationalState = RvcOperationalState4.OperationalState.Error;
172786
173161
  } else {
172787
173162
  if (state.toLowerCase().includes("clean")) {
172788
- logger239.info(
173163
+ logger241.info(
172789
173164
  `Unknown vacuum state "${state}" contains 'clean', treating as Running`
172790
173165
  );
172791
173166
  operationalState = RvcOperationalState4.OperationalState.Running;
172792
173167
  } else {
172793
- logger239.info(`Unknown vacuum state "${state}", treating as Stopped`);
173168
+ logger241.info(`Unknown vacuum state "${state}", treating as Stopped`);
172794
173169
  operationalState = RvcOperationalState4.OperationalState.Stopped;
172795
173170
  }
172796
173171
  }
172797
- logger239.debug(
173172
+ logger241.debug(
172798
173173
  `Vacuum operationalState: "${state}" -> ${RvcOperationalState4.OperationalState[operationalState]}`
172799
173174
  );
172800
173175
  return operationalState;
@@ -172823,7 +173198,7 @@ var VacuumRvcOperationalStateServer = RvcOperationalStateServer2({
172823
173198
  });
172824
173199
 
172825
173200
  // src/matter/endpoints/legacy/vacuum/index.ts
172826
- var logger240 = Logger.get("VacuumDevice");
173201
+ var logger242 = Logger.get("VacuumDevice");
172827
173202
  var VacuumEndpointType = RoboticVacuumCleanerDevice.with(
172828
173203
  BasicInformationServer2,
172829
173204
  VacuumIdentifyServer,
@@ -172837,7 +173212,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
172837
173212
  const entityId = homeAssistantEntity.entity.entity_id;
172838
173213
  const attributes9 = homeAssistantEntity.entity.state.attributes;
172839
173214
  const customAreas = homeAssistantEntity.mapping?.customServiceAreas;
172840
- logger240.info(
173215
+ logger242.info(
172841
173216
  `Creating vacuum endpoint for ${entityId}, mapping: ${JSON.stringify(homeAssistantEntity.mapping ?? "none")}`
172842
173217
  );
172843
173218
  const cleanAreaRooms = homeAssistantEntity.mapping?.cleanAreaRooms;
@@ -172850,32 +173225,32 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
172850
173225
  )
172851
173226
  ).set({ homeAssistantEntity });
172852
173227
  if (includeOnOff) {
172853
- logger240.info(`${entityId}: Adding OnOff cluster (vacuumOnOff flag enabled)`);
173228
+ logger242.info(`${entityId}: Adding OnOff cluster (vacuumOnOff flag enabled)`);
172854
173229
  device = device.with(VacuumOnOffServer);
172855
173230
  }
172856
173231
  device = device.with(VacuumPowerSourceServer);
172857
173232
  const roomEntities = homeAssistantEntity.mapping?.roomEntities;
172858
173233
  const rooms = parseVacuumRooms(attributes9);
172859
- logger240.info(
173234
+ logger242.info(
172860
173235
  `${entityId}: customAreas=${customAreas?.length ?? 0}, roomEntities=${JSON.stringify(roomEntities ?? [])}, parsedRooms=${rooms.length}, cleanAreaRooms=${cleanAreaRooms?.length ?? 0}`
172861
173236
  );
172862
173237
  if (cleanAreaRooms && cleanAreaRooms.length > 0) {
172863
- logger240.info(
173238
+ logger242.info(
172864
173239
  `${entityId}: Adding ServiceArea (${cleanAreaRooms.length} HA areas via CLEAN_AREA)`
172865
173240
  );
172866
173241
  device = device.with(createCleanAreaServiceAreaServer(cleanAreaRooms));
172867
173242
  } else if (customAreas && customAreas.length > 0) {
172868
- logger240.info(
173243
+ logger242.info(
172869
173244
  `${entityId}: Adding ServiceArea (${customAreas.length} custom areas)`
172870
173245
  );
172871
173246
  device = device.with(createCustomServiceAreaServer(customAreas));
172872
173247
  } else if (rooms.length > 0 || roomEntities && roomEntities.length > 0) {
172873
- logger240.info(`${entityId}: Adding ServiceArea (${rooms.length} rooms)`);
173248
+ logger242.info(`${entityId}: Adding ServiceArea (${rooms.length} rooms)`);
172874
173249
  device = device.with(
172875
173250
  createVacuumServiceAreaServer(attributes9, roomEntities)
172876
173251
  );
172877
173252
  } else {
172878
- logger240.info(`${entityId}: Adding ServiceArea (default single-area)`);
173253
+ logger242.info(`${entityId}: Adding ServiceArea (default single-area)`);
172879
173254
  device = device.with(createDefaultServiceAreaServer());
172880
173255
  }
172881
173256
  const fanSpeedList = resolveFanSpeedList(
@@ -172886,7 +173261,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
172886
173261
  homeAssistantEntity.mapping?.mopIntensityEntity
172887
173262
  );
172888
173263
  if (cleaningModeOptions || fanSpeedList || mopIntensityList) {
172889
- logger240.info(
173264
+ logger242.info(
172890
173265
  `${entityId}: Adding RvcCleanMode (multi-mode, cleaningModeOptions=${JSON.stringify(cleaningModeOptions ?? [])}, fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])})`
172891
173266
  );
172892
173267
  device = device.with(
@@ -172899,7 +173274,7 @@ function VacuumDevice(homeAssistantEntity, includeOnOff = false, cleaningModeOpt
172899
173274
  )
172900
173275
  );
172901
173276
  } else {
172902
- logger240.info(`${entityId}: Adding RvcCleanMode (default single-mode)`);
173277
+ logger242.info(`${entityId}: Adding RvcCleanMode (default single-mode)`);
172903
173278
  device = device.with(createDefaultRvcCleanModeServer());
172904
173279
  }
172905
173280
  return device;
@@ -173065,7 +173440,7 @@ var WaterHeaterThermostatServer = ThermostatServer2(
173065
173440
  );
173066
173441
 
173067
173442
  // src/matter/endpoints/legacy/water-heater/index.ts
173068
- var logger241 = Logger.get("WaterHeaterDevice");
173443
+ var logger243 = Logger.get("WaterHeaterDevice");
173069
173444
  var WaterHeaterDeviceType = ThermostatDevice.with(
173070
173445
  BasicInformationServer2,
173071
173446
  IdentifyServer2,
@@ -173081,7 +173456,7 @@ function toMatterTemp2(value) {
173081
173456
  }
173082
173457
  function WaterHeaterDevice2(homeAssistantEntity) {
173083
173458
  const attributes9 = homeAssistantEntity.entity.state.attributes;
173084
- logger241.debug(
173459
+ logger243.debug(
173085
173460
  `Creating device for ${homeAssistantEntity.entity.entity_id}, min_temp=${attributes9.min_temp}, max_temp=${attributes9.max_temp}`
173086
173461
  );
173087
173462
  const minLimit = toMatterTemp2(attributes9.min_temp) ?? 0;
@@ -173167,6 +173542,7 @@ var WaterHeaterBoostMemoryBehavior = class extends Behavior {
173167
173542
  // src/matter/endpoints/legacy/water-heater/behaviors/water-heater-management-server.ts
173168
173543
  init_dist();
173169
173544
  init_esm();
173545
+ init_types2();
173170
173546
  init_home_assistant_entity_behavior();
173171
173547
 
173172
173548
  // src/matter/endpoints/legacy/water-heater/water-heater-modes.ts
@@ -173323,7 +173699,7 @@ function activeHeatSource(attributes9) {
173323
173699
  }
173324
173700
 
173325
173701
  // src/matter/endpoints/legacy/water-heater/behaviors/water-heater-management-server.ts
173326
- var logger242 = Logger.get("WaterHeaterManagementServer");
173702
+ var logger244 = Logger.get("WaterHeaterManagementServer");
173327
173703
  var MAX_TIMER_MS2 = 2147483647;
173328
173704
  var boostSessions = /* @__PURE__ */ new WeakMap();
173329
173705
  function clearBoostTimer(endpoint) {
@@ -173569,7 +173945,7 @@ var WaterHeaterManagementServerBase = class _WaterHeaterManagementServerBase ext
173569
173945
  armBoostTimer(durationSeconds) {
173570
173946
  const delay = durationSeconds * 1e3;
173571
173947
  if (!Number.isFinite(delay) || delay <= 0 || delay > MAX_TIMER_MS2) {
173572
- logger242.debug(
173948
+ logger244.debug(
173573
173949
  `Boost duration ${durationSeconds}s outside the timer range, boost stays active until CancelBoost`
173574
173950
  );
173575
173951
  return;
@@ -173583,7 +173959,7 @@ var WaterHeaterManagementServerBase = class _WaterHeaterManagementServerBase ext
173583
173959
  agent.get(_WaterHeaterManagementServerBase).endBoost({ restoreMode: true });
173584
173960
  });
173585
173961
  } catch (error) {
173586
- logger242.debug(
173962
+ logger244.debug(
173587
173963
  `Boost expiry failed (endpoint may be closing): ${error}`
173588
173964
  );
173589
173965
  }
@@ -173686,13 +174062,13 @@ function WaterHeaterModeServer2(mapping, initialMode) {
173686
174062
  }
173687
174063
 
173688
174064
  // src/matter/endpoints/legacy/water-heater/water-heater-management-device.ts
173689
- var logger243 = Logger.get("WaterHeaterManagementDevice");
174065
+ var logger245 = Logger.get("WaterHeaterManagementDevice");
173690
174066
  function WaterHeaterManagementDevice(homeAssistantEntity) {
173691
174067
  const entityState = homeAssistantEntity.entity.state;
173692
174068
  const attributes9 = entityState.attributes;
173693
174069
  const modeMapping = buildModeMapping(attributes9);
173694
174070
  const initialMode = currentMode(modeMapping, entityState.state, attributes9);
173695
- logger243.debug(
174071
+ logger245.debug(
173696
174072
  `Creating Matter 1.4 water heater for ${homeAssistantEntity.entity.entity_id}, modes=[${modeMapping.supportedModes.map((m) => `${m.mode}:${m.label}`).join(", ")}], boostMode=${modeMapping.boostOperationMode ?? "none"}`
173697
174073
  );
173698
174074
  const minLimit = toMatterTemp2(attributes9.min_temp) ?? 0;
@@ -173963,7 +174339,7 @@ var matterDeviceTypeFactories = {
173963
174339
  };
173964
174340
 
173965
174341
  // src/matter/endpoints/composed/user-composed-endpoint.ts
173966
- var logger244 = Logger.get("UserComposedEndpoint");
174342
+ var logger246 = Logger.get("UserComposedEndpoint");
173967
174343
  function stripBasicInformation(type) {
173968
174344
  const behaviors = { ...type.behaviors };
173969
174345
  delete behaviors.bridgedDeviceBasicInformation;
@@ -174039,7 +174415,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
174039
174415
  { vacuumOnOff: registry3.isVacuumOnOffEnabled() }
174040
174416
  );
174041
174417
  if (!primaryType) {
174042
- logger244.warn(
174418
+ logger246.warn(
174043
174419
  `Cannot create endpoint type for primary entity ${primaryEntityId}`
174044
174420
  );
174045
174421
  return void 0;
@@ -174054,7 +174430,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
174054
174430
  if (!sub.entityId) continue;
174055
174431
  const subPayload = buildEntityPayload4(registry3, sub.entityId);
174056
174432
  if (!subPayload) {
174057
- logger244.warn(
174433
+ logger246.warn(
174058
174434
  `Cannot find state for composed sub-entity ${sub.entityId}, it does not exist in Home Assistant (removed or renamed?)`
174059
174435
  );
174060
174436
  continue;
@@ -174069,7 +174445,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
174069
174445
  config8.areaName
174070
174446
  );
174071
174447
  if (!subType) {
174072
- logger244.warn(
174448
+ logger246.warn(
174073
174449
  `Cannot create endpoint type for composed sub-entity ${sub.entityId}`
174074
174450
  );
174075
174451
  continue;
@@ -174082,7 +174458,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
174082
174458
  mappedIds.push(sub.entityId);
174083
174459
  }
174084
174460
  if (parts.length < 2) {
174085
- logger244.warn(
174461
+ logger246.warn(
174086
174462
  `User composed device ${primaryEntityId}: only ${parts.length} sub-endpoint(s), need at least 2 (primary + one sub-entity). Falling back to standalone.`
174087
174463
  );
174088
174464
  return void 0;
@@ -174106,7 +174482,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
174106
174482
  const labels = parts.map(
174107
174483
  (_, i) => i === 0 ? primaryEntityId.split(".")[0] : composedEntities[i - 1]?.entityId?.split(".")[0] ?? "?"
174108
174484
  ).join("+");
174109
- logger244.info(
174485
+ logger246.info(
174110
174486
  `Created user composed device ${primaryEntityId}: ${parts.length} sub-endpoint(s) [${labels}]`
174111
174487
  );
174112
174488
  return endpoint;
@@ -174212,7 +174588,7 @@ function asStandaloneEndpointType(type) {
174212
174588
  }
174213
174589
 
174214
174590
  // src/matter/endpoints/legacy/legacy-endpoint.ts
174215
- var logger245 = Logger.get("LegacyEndpoint");
174591
+ var logger247 = Logger.get("LegacyEndpoint");
174216
174592
  var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174217
174593
  constructor(type, entityId, customName, mappedEntityIds, throttleMs, endpointId, vacuumEffective) {
174218
174594
  super(type, entityId, customName, mappedEntityIds, endpointId);
@@ -174228,25 +174604,25 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174228
174604
  return;
174229
174605
  }
174230
174606
  if (registry3.isAutoBatteryMappingEnabled() && registry3.isBatteryEntityUsed(entityId)) {
174231
- logger245.debug(
174607
+ logger247.debug(
174232
174608
  `Skipping ${entityId} - already auto-assigned as battery to another device`
174233
174609
  );
174234
174610
  return;
174235
174611
  }
174236
174612
  if (registry3.isAutoHumidityMappingEnabled() && registry3.isHumidityEntityUsed(entityId)) {
174237
- logger245.debug(
174613
+ logger247.debug(
174238
174614
  `Skipping ${entityId} - already auto-assigned as humidity to a temperature sensor`
174239
174615
  );
174240
174616
  return;
174241
174617
  }
174242
174618
  if (registry3.isAutoPressureMappingEnabled() && registry3.isPressureEntityUsed(entityId)) {
174243
- logger245.debug(
174619
+ logger247.debug(
174244
174620
  `Skipping ${entityId} - already auto-assigned as pressure to a temperature sensor`
174245
174621
  );
174246
174622
  return;
174247
174623
  }
174248
174624
  if (registry3.isAutoComposedDevicesEnabled() && registry3.isComposedSubEntityUsed(entityId)) {
174249
- logger245.debug(
174625
+ logger247.debug(
174250
174626
  `Skipping ${entityId} - already consumed by a composed device`
174251
174627
  );
174252
174628
  return;
@@ -174266,7 +174642,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174266
174642
  humidityEntity: humidityEntityId
174267
174643
  };
174268
174644
  registry3.markHumidityEntityUsed(humidityEntityId);
174269
- logger245.debug(
174645
+ logger247.debug(
174270
174646
  `Auto-assigned humidity ${humidityEntityId} to ${entityId}`
174271
174647
  );
174272
174648
  }
@@ -174285,7 +174661,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174285
174661
  pressureEntity: pressureEntityId
174286
174662
  };
174287
174663
  registry3.markPressureEntityUsed(pressureEntityId);
174288
- logger245.debug(
174664
+ logger247.debug(
174289
174665
  `Auto-assigned pressure ${pressureEntityId} to ${entityId}`
174290
174666
  );
174291
174667
  }
@@ -174303,7 +174679,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174303
174679
  batteryEntity: batteryEntityId
174304
174680
  };
174305
174681
  registry3.markBatteryEntityUsed(batteryEntityId);
174306
- logger245.debug(
174682
+ logger247.debug(
174307
174683
  `Auto-assigned battery ${batteryEntityId} to ${entityId}`
174308
174684
  );
174309
174685
  }
@@ -174320,7 +174696,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174320
174696
  entityId: effectiveMapping?.entityId ?? entityId,
174321
174697
  faultEntity: faultEntityId
174322
174698
  };
174323
- logger245.debug(`Auto-assigned fault ${faultEntityId} to ${entityId}`);
174699
+ logger247.debug(`Auto-assigned fault ${faultEntityId} to ${entityId}`);
174324
174700
  }
174325
174701
  }
174326
174702
  if (!mapping?.powerEntity) {
@@ -174336,7 +174712,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174336
174712
  powerEntity: powerEntityId
174337
174713
  };
174338
174714
  registry3.markPowerEntityUsed(powerEntityId);
174339
- logger245.debug(`Auto-assigned power ${powerEntityId} to ${entityId}`);
174715
+ logger247.debug(`Auto-assigned power ${powerEntityId} to ${entityId}`);
174340
174716
  }
174341
174717
  }
174342
174718
  }
@@ -174353,7 +174729,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174353
174729
  energyEntity: energyEntityId
174354
174730
  };
174355
174731
  registry3.markEnergyEntityUsed(energyEntityId);
174356
- logger245.debug(
174732
+ logger247.debug(
174357
174733
  `Auto-assigned energy ${energyEntityId} to ${entityId}`
174358
174734
  );
174359
174735
  }
@@ -174369,7 +174745,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174369
174745
  entityId: effectiveMapping?.entityId ?? entityId,
174370
174746
  cleaningModeEntity: vacuumEntities.cleaningModeEntity
174371
174747
  };
174372
- logger245.info(
174748
+ logger247.info(
174373
174749
  `Auto-assigned cleaningMode ${vacuumEntities.cleaningModeEntity} to ${entityId}`
174374
174750
  );
174375
174751
  }
@@ -174379,7 +174755,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174379
174755
  entityId: effectiveMapping?.entityId ?? entityId,
174380
174756
  suctionLevelEntity: vacuumEntities.suctionLevelEntity
174381
174757
  };
174382
- logger245.info(
174758
+ logger247.info(
174383
174759
  `Auto-assigned suctionLevel ${vacuumEntities.suctionLevelEntity} to ${entityId}`
174384
174760
  );
174385
174761
  }
@@ -174389,7 +174765,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174389
174765
  entityId: effectiveMapping?.entityId ?? entityId,
174390
174766
  mopIntensityEntity: vacuumEntities.mopIntensityEntity
174391
174767
  };
174392
- logger245.info(
174768
+ logger247.info(
174393
174769
  `Auto-assigned mopIntensity ${vacuumEntities.mopIntensityEntity} to ${entityId}`
174394
174770
  );
174395
174771
  }
@@ -174399,7 +174775,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174399
174775
  entityId: effectiveMapping?.entityId ?? entityId,
174400
174776
  currentRoomEntity: vacuumEntities.currentRoomEntity
174401
174777
  };
174402
- logger245.info(
174778
+ logger247.info(
174403
174779
  `Auto-assigned currentRoom ${vacuumEntities.currentRoomEntity} to ${entityId}`
174404
174780
  );
174405
174781
  }
@@ -174414,7 +174790,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174414
174790
  entityId: effectiveMapping?.entityId ?? entityId,
174415
174791
  cleanAreaRooms
174416
174792
  };
174417
- logger245.info(
174793
+ logger247.info(
174418
174794
  `Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA for ${entityId}`
174419
174795
  );
174420
174796
  }
@@ -174435,7 +174811,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174435
174811
  rooms: roomsObj
174436
174812
  }
174437
174813
  };
174438
- logger245.debug(
174814
+ logger247.debug(
174439
174815
  `Auto-detected ${valetudoRooms.length} Valetudo segments for ${entityId}`
174440
174816
  );
174441
174817
  } else {
@@ -174452,7 +174828,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174452
174828
  rooms: roomsObj
174453
174829
  }
174454
174830
  };
174455
- logger245.debug(
174831
+ logger247.debug(
174456
174832
  `Auto-detected ${roborockRooms.length} Roborock rooms for ${entityId}`
174457
174833
  );
174458
174834
  }
@@ -174461,7 +174837,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174461
174837
  }
174462
174838
  }
174463
174839
  if (standalone && ((effectiveMapping?.composedEntities?.length ?? 0) > 0 || effectiveMapping?.climateExposeFan === true)) {
174464
- logger245.warn(
174840
+ logger247.warn(
174465
174841
  `Composed mappings are not supported in server mode, exposing ${entityId} as a flat standalone endpoint`
174466
174842
  );
174467
174843
  }
@@ -174480,7 +174856,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174480
174856
  if (composed) {
174481
174857
  return composed;
174482
174858
  }
174483
- logger245.warn(
174859
+ logger247.warn(
174484
174860
  `User composed device creation failed for ${entityId}, falling back to standalone`
174485
174861
  );
174486
174862
  }
@@ -174545,7 +174921,7 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174545
174921
  if (composed) {
174546
174922
  return composed;
174547
174923
  }
174548
- logger245.warn(
174924
+ logger247.warn(
174549
174925
  `Companion fan creation failed for ${entityId}, falling back to standalone`
174550
174926
  );
174551
174927
  }
@@ -174623,11 +174999,11 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
174623
174999
  }
174624
175000
  if (mappedChanged) {
174625
175001
  this.pendingMappedChange = true;
174626
- logger245.debug(
175002
+ logger247.debug(
174627
175003
  `Mapped entity change detected for ${this.entityId}, forcing update`
174628
175004
  );
174629
175005
  }
174630
- logger245.debug(
175006
+ logger247.debug(
174631
175007
  `State update received for ${this.entityId}: state=${state.state}`
174632
175008
  );
174633
175009
  this.lastState = state;
@@ -174848,12 +175224,14 @@ var CameraAvStreamServer = class extends Base4 {
174848
175224
  // src/plugins/builtin/camera/webrtc-provider-server.ts
174849
175225
  init_esm();
174850
175226
  init_esm7();
175227
+ init_types2();
174851
175228
  init_esm3();
174852
175229
 
174853
175230
  // src/plugins/builtin/camera/requestor-client.ts
174854
175231
  init_esm();
175232
+ init_types2();
174855
175233
  init_esm4();
174856
- var logger246 = Logger.get("CameraWebRtcRequestor");
175234
+ var logger248 = Logger.get("CameraWebRtcRequestor");
174857
175235
  var registry2 = /* @__PURE__ */ new Map();
174858
175236
  var pendingDeliveries = /* @__PURE__ */ new Map();
174859
175237
  var invokeTransport = defaultInvoke;
@@ -174887,19 +175265,19 @@ function deliverAnswerDeferred(sessionId, sdp, onGiveUp) {
174887
175265
  return;
174888
175266
  }
174889
175267
  if (await sendAnswer(sessionId, sdp)) {
174890
- logger246.info(`answer delivered for session ${sessionId}`);
175268
+ logger248.info(`answer delivered for session ${sessionId}`);
174891
175269
  return;
174892
175270
  }
174893
175271
  }
174894
175272
  if (registry2.get(sessionId) !== owner || pendingDeliveries.get(sessionId) !== timer) {
174895
175273
  return;
174896
175274
  }
174897
- logger246.info(
175275
+ logger248.info(
174898
175276
  `answer delivery failed for session ${sessionId}, giving up`
174899
175277
  );
174900
175278
  await onGiveUp();
174901
175279
  } catch (err) {
174902
- logger246.info(
175280
+ logger248.info(
174903
175281
  `answer delivery for session ${sessionId} threw: ${errText(err)}`
174904
175282
  );
174905
175283
  } finally {
@@ -174924,11 +175302,11 @@ function sendAnswer(sessionId, sdp) {
174924
175302
  async function invokeRequestor(sessionId, command, fields) {
174925
175303
  const registration = registry2.get(sessionId);
174926
175304
  if (!registration) {
174927
- logger246.info(`requestor ${command}: no session ${sessionId} registered`);
175305
+ logger248.info(`requestor ${command}: no session ${sessionId} registered`);
174928
175306
  return false;
174929
175307
  }
174930
175308
  if (registration.session.isClosed) {
174931
- logger246.info(`requestor ${command}: session ${sessionId} already closed`);
175309
+ logger248.info(`requestor ${command}: session ${sessionId} already closed`);
174932
175310
  return false;
174933
175311
  }
174934
175312
  const request = {
@@ -174940,7 +175318,7 @@ async function invokeRequestor(sessionId, command, fields) {
174940
175318
  try {
174941
175319
  return await invokeTransport({ registration, request });
174942
175320
  } catch (err) {
174943
- logger246.info(
175321
+ logger248.info(
174944
175322
  `requestor ${command} failed for session ${sessionId}: ${errText(err)}`
174945
175323
  );
174946
175324
  return false;
@@ -174975,7 +175353,7 @@ async function defaultInvoke({
174975
175353
  for (const entry of chunk) {
174976
175354
  if (entry.kind === "cmd-status") {
174977
175355
  if (entry.status !== Status2.Success) {
174978
- logger246.info(
175356
+ logger248.info(
174979
175357
  `requestor ${request.command} status ${entry.status} for session ${String(request.fields.webRtcSessionId)}`
174980
175358
  );
174981
175359
  return false;
@@ -174995,7 +175373,7 @@ function errText(err) {
174995
175373
  }
174996
175374
 
174997
175375
  // src/plugins/builtin/camera/webrtc-provider-server.ts
174998
- var logger247 = Logger.get("CameraWebRtc");
175376
+ var logger249 = Logger.get("CameraWebRtc");
174999
175377
  var nextGlobalSessionId = 0;
175000
175378
  function mintSessionId() {
175001
175379
  for (let i = 0; i <= 65534; i++) {
@@ -175009,14 +175387,14 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
175009
175387
  solicitOffer(request) {
175010
175388
  const id = mintSessionId();
175011
175389
  this.trackSession(id, request.streamUsage, request.originatingEndpointId);
175012
- logger247.info(
175390
+ logger249.info(
175013
175391
  `solicitOffer session=${id} (${this.state.entityId}), deferred offer`
175014
175392
  );
175015
175393
  void this.state.bridge.startSession(id, this.state.entityId, {
175016
175394
  iceServers: request.iceServers,
175017
175395
  iceTransportPolicy: request.iceTransportPolicy
175018
175396
  }).catch(
175019
- (err) => logger247.info(
175397
+ (err) => logger249.info(
175020
175398
  `solicitOffer startSession failed for ${this.state.entityId}: ${errText2(err)}`
175021
175399
  )
175022
175400
  );
@@ -175024,7 +175402,7 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
175024
175402
  }
175025
175403
  async provideOffer(request) {
175026
175404
  const id = request.webRtcSessionId ?? mintSessionId();
175027
- logger247.info(
175405
+ logger249.info(
175028
175406
  `provideOffer entry: entityId=${this.state.entityId} session=${id} (sdp ${request.sdp.length} chars)`
175029
175407
  );
175030
175408
  if (request.webRtcSessionId == null) {
@@ -175058,7 +175436,7 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
175058
175436
  );
175059
175437
  } catch (err) {
175060
175438
  const message = errText2(err);
175061
- logger247.info(
175439
+ logger249.info(
175062
175440
  `provideOffer failed for ${this.state.entityId} session=${id}: ${message}`
175063
175441
  );
175064
175442
  unregisterRequestor(id);
@@ -175070,7 +175448,7 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
175070
175448
  StatusCode.Failure
175071
175449
  );
175072
175450
  }
175073
- logger247.info(
175451
+ logger249.info(
175074
175452
  `provideOffer answer computed for ${this.state.entityId} session=${id} (${answerSdp.length} chars); delivering via requestor`
175075
175453
  );
175076
175454
  const bridge = this.state.bridge;
@@ -175089,7 +175467,7 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
175089
175467
  return { webRtcSessionId: id };
175090
175468
  }
175091
175469
  provideAnswer(request) {
175092
- logger247.info(
175470
+ logger249.info(
175093
175471
  `provideAnswer session=${request.webRtcSessionId} (sdp ${request.sdp.length} chars, ${this.state.entityId})`
175094
175472
  );
175095
175473
  return this.state.bridge.acceptControllerAnswer(
@@ -175098,7 +175476,7 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
175098
175476
  );
175099
175477
  }
175100
175478
  async provideIceCandidates(request) {
175101
- logger247.info(
175479
+ logger249.info(
175102
175480
  `provideIceCandidates session=${request.webRtcSessionId}: ${request.iceCandidates.length} candidate(s) (${this.state.entityId})`
175103
175481
  );
175104
175482
  for (const c of request.iceCandidates) {
@@ -175111,7 +175489,7 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
175111
175489
  }
175112
175490
  }
175113
175491
  async endSession(request) {
175114
- logger247.info(
175492
+ logger249.info(
175115
175493
  `endSession session=${request.webRtcSessionId} (${this.state.entityId})`
175116
175494
  );
175117
175495
  await this.state.bridge.endSession(request.webRtcSessionId);
@@ -175232,7 +175610,7 @@ import {
175232
175610
  createLongLivedTokenAuth as createLongLivedTokenAuth2
175233
175611
  } from "home-assistant-js-websocket";
175234
175612
  import { RTCPeerConnection } from "werift";
175235
- var logger248 = Logger.get("CameraWebRtc");
175613
+ var logger250 = Logger.get("CameraWebRtc");
175236
175614
  var DEFAULT_HA_WEBRTC_TIMEOUT_MS = 15e3;
175237
175615
  var haWebRtcTimeoutMs = DEFAULT_HA_WEBRTC_TIMEOUT_MS;
175238
175616
  var WebRtcBridge = class {
@@ -175279,7 +175657,7 @@ var WebRtcBridge = class {
175279
175657
  const haOffer = await haPeer.createOffer();
175280
175658
  await haPeer.setLocalDescription(haOffer);
175281
175659
  const haOfferSdp = this.localSdp(haPeer);
175282
- logger248.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
175660
+ logger250.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
175283
175661
  const { answer, sessionId, unsubscribe } = await this.requestHaWebRtc(
175284
175662
  entityId,
175285
175663
  haOfferSdp,
@@ -175302,12 +175680,12 @@ var WebRtcBridge = class {
175302
175680
  const controllerOffer = await controllerPeer.createOffer();
175303
175681
  await controllerPeer.setLocalDescription(controllerOffer);
175304
175682
  const sdp = this.localSdp(controllerPeer);
175305
- logger248.info(
175683
+ logger250.info(
175306
175684
  `controller offer created for ${entityId} (${sdp.length} chars)`
175307
175685
  );
175308
175686
  return { sdp, iceCandidates };
175309
175687
  } catch (err) {
175310
- logger248.info(`startSession failed for ${entityId}: ${errText3(err)}`);
175688
+ logger250.info(`startSession failed for ${entityId}: ${errText3(err)}`);
175311
175689
  await this.endSession(matterSessionId);
175312
175690
  throw err;
175313
175691
  }
@@ -175337,7 +175715,7 @@ var WebRtcBridge = class {
175337
175715
  const haOffer = await haPeer.createOffer();
175338
175716
  await haPeer.setLocalDescription(haOffer);
175339
175717
  const haOfferSdp = this.localSdp(haPeer);
175340
- logger248.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
175718
+ logger250.info(`HA offer sent for ${entityId} (${haOfferSdp.length} chars)`);
175341
175719
  const { answer, sessionId, unsubscribe } = await this.requestHaWebRtc(
175342
175720
  entityId,
175343
175721
  haOfferSdp,
@@ -175356,12 +175734,12 @@ var WebRtcBridge = class {
175356
175734
  const controllerAnswer = await controllerPeer.createAnswer();
175357
175735
  await controllerPeer.setLocalDescription(controllerAnswer);
175358
175736
  const answerSdp = this.localSdp(controllerPeer);
175359
- logger248.info(
175737
+ logger250.info(
175360
175738
  `controller answer created for ${entityId} (${answerSdp.length} chars)`
175361
175739
  );
175362
175740
  return answerSdp;
175363
175741
  } catch (err) {
175364
- logger248.info(
175742
+ logger250.info(
175365
175743
  `acceptControllerOffer failed for ${entityId}: ${errText3(err)}`
175366
175744
  );
175367
175745
  await this.endSession(matterSessionId);
@@ -175372,14 +175750,14 @@ var WebRtcBridge = class {
175372
175750
  async acceptControllerAnswer(matterSessionId, sdp) {
175373
175751
  const session = this.sessions.get(matterSessionId);
175374
175752
  if (!session) return;
175375
- logger248.debug(`controller answer applied for ${session.entityId}`);
175753
+ logger250.debug(`controller answer applied for ${session.entityId}`);
175376
175754
  await session.controllerPeer.setRemoteDescription({ type: "answer", sdp });
175377
175755
  }
175378
175756
  /** Add a remote ICE candidate from the Matter controller. */
175379
175757
  async addControllerIceCandidate(matterSessionId, candidate, sdpMid, sdpMLineIndex) {
175380
175758
  const session = this.sessions.get(matterSessionId);
175381
175759
  if (!session) return;
175382
- logger248.debug(
175760
+ logger250.debug(
175383
175761
  `controller ICE candidate for ${session.entityId}: ${candidate}`
175384
175762
  );
175385
175763
  await session.controllerPeer.addIceCandidate({
@@ -175393,12 +175771,12 @@ var WebRtcBridge = class {
175393
175771
  async closeExistingPeers(matterSessionId) {
175394
175772
  const prior = this.sessions.get(matterSessionId);
175395
175773
  if (!prior) return;
175396
- logger248.info(
175774
+ logger250.info(
175397
175775
  `replacing session ${matterSessionId} (${prior.entityId}), closing prior peers`
175398
175776
  );
175399
175777
  if (prior.haUnsubscribe) {
175400
175778
  await Promise.resolve(prior.haUnsubscribe()).catch(
175401
- (err) => logger248.debug(`HA unsubscribe failed: ${errText3(err)}`)
175779
+ (err) => logger250.debug(`HA unsubscribe failed: ${errText3(err)}`)
175402
175780
  );
175403
175781
  }
175404
175782
  await prior.haPeer.close().catch(() => {
@@ -175411,11 +175789,11 @@ var WebRtcBridge = class {
175411
175789
  async endSession(matterSessionId) {
175412
175790
  const session = this.sessions.get(matterSessionId);
175413
175791
  if (!session) return;
175414
- logger248.info(`ending session ${matterSessionId} (${session.entityId})`);
175792
+ logger250.info(`ending session ${matterSessionId} (${session.entityId})`);
175415
175793
  this.sessions.delete(matterSessionId);
175416
175794
  if (session.haUnsubscribe) {
175417
175795
  await Promise.resolve(session.haUnsubscribe()).catch(
175418
- (err) => logger248.debug(`HA unsubscribe failed: ${errText3(err)}`)
175796
+ (err) => logger250.debug(`HA unsubscribe failed: ${errText3(err)}`)
175419
175797
  );
175420
175798
  }
175421
175799
  await session.haPeer.close().catch(() => {
@@ -175445,11 +175823,11 @@ var WebRtcBridge = class {
175445
175823
  headers: { Authorization: `Bearer ${this.config.haToken}` }
175446
175824
  });
175447
175825
  } catch (err) {
175448
- logger248.info(`snapshot fetch failed for ${entityId}: ${errText3(err)}`);
175826
+ logger250.info(`snapshot fetch failed for ${entityId}: ${errText3(err)}`);
175449
175827
  throw err;
175450
175828
  }
175451
175829
  if (!res.ok) {
175452
- logger248.info(
175830
+ logger250.info(
175453
175831
  `snapshot fetch failed for ${entityId}: camera_proxy ${res.status}`
175454
175832
  );
175455
175833
  throw new Error(`HA camera_proxy ${entityId}: ${res.status}`);
@@ -175466,10 +175844,10 @@ var WebRtcBridge = class {
175466
175844
  // Forward every track HA sends into the matching pre-added controller sender.
175467
175845
  forwardHaTracks(haPeer, senders, entityId) {
175468
175846
  haPeer.onTrack.subscribe((track) => {
175469
- logger248.info(`onTrack ${track.kind} from HA (${entityId})`);
175847
+ logger250.info(`onTrack ${track.kind} from HA (${entityId})`);
175470
175848
  const transceiver = senders.get(track.kind);
175471
175849
  if (!transceiver) {
175472
- logger248.info(
175850
+ logger250.info(
175473
175851
  `no controller transceiver for ${track.kind} (${entityId})`
175474
175852
  );
175475
175853
  return;
@@ -175478,7 +175856,7 @@ var WebRtcBridge = class {
175478
175856
  track.onReceiveRtp.subscribe((rtp) => {
175479
175857
  if (!firstLogged) {
175480
175858
  firstLogged = true;
175481
- logger248.info(`first RTP forwarded for ${track.kind} (${entityId})`);
175859
+ logger250.info(`first RTP forwarded for ${track.kind} (${entityId})`);
175482
175860
  }
175483
175861
  transceiver.sender.sendRtp(rtp);
175484
175862
  });
@@ -175487,10 +175865,10 @@ var WebRtcBridge = class {
175487
175865
  // Log connection/ice state transitions (these events fire on change only).
175488
175866
  wireStateLogging(peer, label, entityId) {
175489
175867
  peer.connectionStateChange.subscribe(
175490
- (s) => logger248.info(`${label} connectionState=${s} (${entityId})`)
175868
+ (s) => logger250.info(`${label} connectionState=${s} (${entityId})`)
175491
175869
  );
175492
175870
  peer.iceConnectionStateChange.subscribe(
175493
- (s) => logger248.info(`${label} iceConnectionState=${s} (${entityId})`)
175871
+ (s) => logger250.info(`${label} iceConnectionState=${s} (${entityId})`)
175494
175872
  );
175495
175873
  }
175496
175874
  // Map the Matter ICE struct onto werift's config. Only the controller peer
@@ -175504,11 +175882,11 @@ var WebRtcBridge = class {
175504
175882
  }))
175505
175883
  );
175506
175884
  if (iceServers.length === 0) {
175507
- logger248.info(`controller peer using default ICE servers (${entityId})`);
175885
+ logger250.info(`controller peer using default ICE servers (${entityId})`);
175508
175886
  return void 0;
175509
175887
  }
175510
175888
  const policy = ice?.iceTransportPolicy === "relay" || ice?.iceTransportPolicy === "all" ? ice.iceTransportPolicy : void 0;
175511
- logger248.info(
175889
+ logger250.info(
175512
175890
  `controller peer using ${iceServers.length} controller ICE server(s)${policy ? ` policy=${policy}` : ""} (${entityId})`
175513
175891
  );
175514
175892
  return policy ? { iceServers, iceTransportPolicy: policy } : { iceServers };
@@ -175522,13 +175900,13 @@ var WebRtcBridge = class {
175522
175900
  feedHaCandidate(entityId, haPeer, candidate) {
175523
175901
  const value = candidate?.candidate;
175524
175902
  if (!value) return;
175525
- logger248.debug(`HA ICE candidate for ${entityId}: ${value}`);
175903
+ logger250.debug(`HA ICE candidate for ${entityId}: ${value}`);
175526
175904
  void haPeer.addIceCandidate({
175527
175905
  candidate: value,
175528
175906
  sdpMid: candidate?.sdpMid ?? void 0,
175529
175907
  sdpMLineIndex: candidate?.sdpMLineIndex ?? void 0
175530
175908
  }).catch(
175531
- (e) => logger248.debug(
175909
+ (e) => logger250.debug(
175532
175910
  `HA addIceCandidate failed for ${entityId}: ${errText3(e)}`
175533
175911
  )
175534
175912
  );
@@ -175557,7 +175935,7 @@ var WebRtcBridge = class {
175557
175935
  reject(err);
175558
175936
  });
175559
175937
  timer = setTimeout(() => {
175560
- logger248.info(
175938
+ logger250.info(
175561
175939
  `HA WebRTC timed out after ${haWebRtcTimeoutMs}ms for ${entityId}`
175562
175940
  );
175563
175941
  fail(
@@ -175575,7 +175953,7 @@ var WebRtcBridge = class {
175575
175953
  if (msg.session_id) sessionId = msg.session_id;
175576
175954
  const ans = msg.answer;
175577
175955
  settle(() => {
175578
- logger248.info(
175956
+ logger250.info(
175579
175957
  `HA answer received for ${entityId} (${ans.length} chars)`
175580
175958
  );
175581
175959
  resolve11(ans);
@@ -175584,7 +175962,7 @@ var WebRtcBridge = class {
175584
175962
  break;
175585
175963
  case "error": {
175586
175964
  const detail = msg.message ?? msg.code ?? "unknown error";
175587
- logger248.info(`HA WebRTC error for ${entityId}: ${detail}`);
175965
+ logger250.info(`HA WebRTC error for ${entityId}: ${detail}`);
175588
175966
  fail(new Error(`HA WebRTC error: ${detail}`));
175589
175967
  break;
175590
175968
  }
@@ -175758,7 +176136,7 @@ var CameraPlugin = class {
175758
176136
  // src/plugins/builtin/security/security-plugin.ts
175759
176137
  init_esm();
175760
176138
  import {
175761
- callService as callService2,
176139
+ callService,
175762
176140
  createConnection as createConnection4,
175763
176141
  createLongLivedTokenAuth as createLongLivedTokenAuth3
175764
176142
  } from "home-assistant-js-websocket";
@@ -176610,7 +176988,7 @@ var SecurityPlugin = class {
176610
176988
  let timer;
176611
176989
  try {
176612
176990
  await Promise.race([
176613
- callService2(connection, domain, service, void 0, {
176991
+ callService(connection, domain, service, void 0, {
176614
176992
  entity_id: entityId
176615
176993
  }),
176616
176994
  new Promise((_, reject) => {
@@ -176741,7 +177119,7 @@ import {
176741
177119
  getCollection
176742
177120
  } from "home-assistant-js-websocket";
176743
177121
  import { atLeastHaVersion } from "home-assistant-js-websocket/dist/util.js";
176744
- var logger249 = Logger.get("SubscribeEntities");
177122
+ var logger251 = Logger.get("SubscribeEntities");
176745
177123
  function processEvent(store, updates) {
176746
177124
  const state = { ...store.state };
176747
177125
  if (updates.a) {
@@ -176767,7 +177145,7 @@ function processEvent(store, updates) {
176767
177145
  for (const entityId in updates.c) {
176768
177146
  let entityState = state[entityId];
176769
177147
  if (!entityState) {
176770
- logger249.warn("Received state update for unknown entity", entityId);
177148
+ logger251.warn("Received state update for unknown entity", entityId);
176771
177149
  continue;
176772
177150
  }
176773
177151
  entityState = { ...entityState };
@@ -176856,7 +177234,7 @@ var subscribeEntities = (conn, onChange, entityIds) => {
176856
177234
  // src/services/bridges/entity-isolation-service.ts
176857
177235
  init_esm();
176858
177236
  init_diagnostic_event_bus();
176859
- var logger250 = Logger.get("EntityIsolation");
177237
+ var logger252 = Logger.get("EntityIsolation");
176860
177238
  var EntityIsolationServiceImpl = class {
176861
177239
  isolatedEntities = /* @__PURE__ */ new Map();
176862
177240
  isolationCallbacks = /* @__PURE__ */ new Map();
@@ -176924,13 +177302,13 @@ var EntityIsolationServiceImpl = class {
176924
177302
  }
176925
177303
  const parsed = this.parseEndpointPath(msg);
176926
177304
  if (!parsed) {
176927
- logger250.warn("Could not parse entity from error:", msg);
177305
+ logger252.warn("Could not parse entity from error:", msg);
176928
177306
  return false;
176929
177307
  }
176930
177308
  const { bridgeId, entityName } = parsed;
176931
177309
  const callback = this.isolationCallbacks.get(bridgeId);
176932
177310
  if (!callback) {
176933
- logger250.warn(
177311
+ logger252.warn(
176934
177312
  `No isolation callback registered for bridge ${bridgeId}, entity: ${entityName}`
176935
177313
  );
176936
177314
  return false;
@@ -176945,7 +177323,7 @@ var EntityIsolationServiceImpl = class {
176945
177323
  reason,
176946
177324
  failedAt: (/* @__PURE__ */ new Date()).toISOString()
176947
177325
  });
176948
- logger250.warn(
177326
+ logger252.warn(
176949
177327
  `Isolating entity "${entityName}" from bridge ${bridgeId} due to: ${reason}`
176950
177328
  );
176951
177329
  diagnosticEventBus.emit("entity_error", `Entity isolated: ${entityName}`, {
@@ -176957,7 +177335,7 @@ var EntityIsolationServiceImpl = class {
176957
177335
  await callback(entityName);
176958
177336
  return true;
176959
177337
  } catch (e) {
176960
- logger250.error(`Failed to isolate entity ${entityName}:`, e);
177338
+ logger252.error(`Failed to isolate entity ${entityName}:`, e);
176961
177339
  return false;
176962
177340
  }
176963
177341
  }
@@ -176988,7 +177366,13 @@ var EntityIsolationService = new EntityIsolationServiceImpl();
176988
177366
 
176989
177367
  // src/services/bridges/bridge-endpoint-manager.ts
176990
177368
  var MAX_ENTITY_ID_LENGTH = 150;
176991
- var ENDPOINT_REMOVAL_GRACE_MS = 6e4;
177369
+ var ENDPOINT_REMOVAL_GRACE_MS = 3e5;
177370
+ function isEntityPart(p) {
177371
+ return typeof p.updateStates === "function";
177372
+ }
177373
+ function hasEntityIdentity(p) {
177374
+ return typeof p.entityId === "string";
177375
+ }
176992
177376
  var BridgeEndpointManager = class extends Service {
176993
177377
  constructor(client, registry3, mappingStorage, identityStorage, bridgeId, log, pluginManager, pluginRegistry, pluginInstaller) {
176994
177378
  super("BridgeEndpointManager");
@@ -177026,11 +177410,15 @@ var BridgeEndpointManager = class extends Service {
177026
177410
  root;
177027
177411
  entityIds = [];
177028
177412
  unsubscribe;
177413
+ observingRequested = false;
177029
177414
  _failedEntities = [];
177030
177415
  mappingFingerprints = /* @__PURE__ */ new Map();
177031
- // entityId -> first time it went missing from the registry (grace window)
177416
+ // entityId -> first absence stamp (grace window)
177032
177417
  pendingRemovals = /* @__PURE__ */ new Map();
177033
177418
  removalRecheckTimer = null;
177419
+ // Bumped on every stop, so a refresh that was already running cannot arm a
177420
+ // timer on a bridge that has since stopped (#438).
177421
+ lifecycle = 0;
177034
177422
  pluginEndpoints = /* @__PURE__ */ new Map();
177035
177423
  pluginStateUpdating = /* @__PURE__ */ new Set();
177036
177424
  pluginListeners = /* @__PURE__ */ new Map();
@@ -177063,11 +177451,36 @@ var BridgeEndpointManager = class extends Service {
177063
177451
  );
177064
177452
  return;
177065
177453
  }
177454
+ const supplied = device.endpointType;
177066
177455
  const initialState = {};
177067
177456
  for (const cluster2 of device.clusters) {
177457
+ if (cluster2.clusterId === "pluginDevice" && supplied.behaviors?.pluginDevice == null && supplied.behaviors?.bridgedDeviceBasicInformation == null) {
177458
+ this.log.warn(
177459
+ `Plugin "${pluginName}": device "${device.id}" declares a "pluginDevice" cluster without owning that behavior, ignoring it`
177460
+ );
177461
+ continue;
177462
+ }
177068
177463
  initialState[cluster2.clusterId] = cluster2.attributes;
177069
177464
  }
177070
- const base = device.endpointType;
177465
+ const hasOwnIdentity = supplied.behaviors?.bridgedDeviceBasicInformation != null;
177466
+ const ownsPluginDevice = supplied.behaviors?.pluginDevice != null;
177467
+ const mutable = typeof supplied.with === "function" && typeof supplied.set === "function";
177468
+ if (!mutable && Object.keys(initialState).length > 0) {
177469
+ this.log.warn(
177470
+ `Plugin "${pluginName}": endpointType for device "${device.id}" cannot take its cluster config, skipping it`
177471
+ );
177472
+ return;
177473
+ }
177474
+ if (!hasOwnIdentity && (!mutable || ownsPluginDevice)) {
177475
+ this.log.warn(
177476
+ `Plugin "${pluginName}": device "${device.id}" mounts without BridgedDeviceBasicInformation, controllers may not show it`
177477
+ );
177478
+ }
177479
+ let base = device.endpointType;
177480
+ if (mutable && !hasOwnIdentity && !ownsPluginDevice) {
177481
+ base = base.with(PluginBasicInformationServer, PluginDeviceBehavior);
177482
+ initialState.pluginDevice = { device, pluginName };
177483
+ }
177071
177484
  endpoint = new Endpoint(
177072
177485
  Object.keys(initialState).length > 0 ? base.set(initialState) : base,
177073
177486
  { id: `plugin_${device.id}` }
@@ -177236,9 +177649,9 @@ var BridgeEndpointManager = class extends Service {
177236
177649
  await this.pluginManager.shutdownAll("Bridge stopping");
177237
177650
  for (const [id, endpoint] of this.pluginEndpoints) {
177238
177651
  try {
177239
- await endpoint.delete();
177652
+ await endpoint.close();
177240
177653
  } catch (e) {
177241
- this.log.warn(`Failed to delete plugin endpoint ${id}:`, e);
177654
+ this.log.warn(`Failed to close plugin endpoint ${id}:`, e);
177242
177655
  }
177243
177656
  }
177244
177657
  this.pluginEndpoints.clear();
@@ -177278,7 +177691,7 @@ var BridgeEndpointManager = class extends Service {
177278
177691
  * Called by EntityIsolationService when a runtime error is detected.
177279
177692
  */
177280
177693
  async isolateEntity(entityName) {
177281
- const endpoints = this.root.parts.map((p) => p);
177694
+ const endpoints = [...this.root.parts].filter(hasEntityIdentity);
177282
177695
  const endpoint = endpoints.find(
177283
177696
  (e) => !(e instanceof VacuumAreaSwitchEndpoint) && (e.id === entityName || e.entityId === entityName)
177284
177697
  ) ?? endpoints.find((e) => e.id === entityName);
@@ -177287,9 +177700,9 @@ var BridgeEndpointManager = class extends Service {
177287
177700
  `Isolating entity ${endpoint.entityId} due to runtime error`
177288
177701
  );
177289
177702
  try {
177290
- await endpoint.delete();
177703
+ await endpoint.close();
177291
177704
  } catch (e) {
177292
- this.log.error(`Failed to delete isolated endpoint:`, e);
177705
+ this.log.error(`Failed to close isolated endpoint:`, e);
177293
177706
  }
177294
177707
  this.pendingRemovals.delete(endpoint.entityId);
177295
177708
  this.mappingFingerprints.delete(endpoint.entityId);
@@ -177298,7 +177711,7 @@ var BridgeEndpointManager = class extends Service {
177298
177711
  if (!(sw instanceof VacuumAreaSwitchEndpoint)) continue;
177299
177712
  if (sw.vacuumEndpointId !== endpoint.id) continue;
177300
177713
  try {
177301
- await sw.delete();
177714
+ await sw.close();
177302
177715
  } catch (e) {
177303
177716
  this.log.warn(`Failed to remove area switch ${sw.id}:`, e);
177304
177717
  }
@@ -177315,11 +177728,13 @@ var BridgeEndpointManager = class extends Service {
177315
177728
  this.removalRecheckTimer = null;
177316
177729
  }
177317
177730
  if (this.pendingRemovals.size === 0) return;
177731
+ const lifecycle = this.lifecycle;
177318
177732
  this.removalRecheckTimer = setTimeout(() => {
177319
177733
  this.removalRecheckTimer = null;
177320
- this.refreshDevices().catch(
177321
- (e) => this.log.warn("Endpoint removal recheck failed:", e)
177322
- );
177734
+ this.refreshDevices().catch((e) => {
177735
+ this.log.warn("Endpoint removal recheck failed:", e);
177736
+ if (lifecycle === this.lifecycle) this.scheduleRemovalRecheck();
177737
+ });
177323
177738
  }, ENDPOINT_REMOVAL_GRACE_MS + 5e3);
177324
177739
  }
177325
177740
  getPluginDomainMappings() {
@@ -177358,7 +177773,8 @@ var BridgeEndpointManager = class extends Service {
177358
177773
  }
177359
177774
  }
177360
177775
  async startObserving() {
177361
- this.stopObserving();
177776
+ this.clearSubscription();
177777
+ this.observingRequested = true;
177362
177778
  if (!this.entityIds.length) {
177363
177779
  return;
177364
177780
  }
@@ -177382,14 +177798,32 @@ var BridgeEndpointManager = class extends Service {
177382
177798
  }
177383
177799
  return [...ids];
177384
177800
  }
177385
- stopObserving() {
177801
+ clearSubscription() {
177386
177802
  this.unsubscribe?.();
177387
177803
  this.unsubscribe = void 0;
177388
177804
  }
177805
+ stopObserving() {
177806
+ this.observingRequested = false;
177807
+ this.lifecycle++;
177808
+ this.clearSubscription();
177809
+ if (this.removalRecheckTimer) {
177810
+ clearTimeout(this.removalRecheckTimer);
177811
+ this.removalRecheckTimer = null;
177812
+ }
177813
+ }
177389
177814
  async refreshDevices() {
177390
177815
  this.registry.refresh();
177816
+ const lifecycle = this.lifecycle;
177817
+ const endpoints = [...this.root.parts].filter(hasEntityIdentity).filter((p) => !(p instanceof VacuumAreaSwitchEndpoint));
177818
+ const fullEntities = this.registry.fullEntities;
177819
+ if (!this.client.haRunning || Object.keys(fullEntities).length === 0) {
177820
+ this.pendingRemovals.clear();
177821
+ this.log.warn(
177822
+ `HA not running or registry empty, deferring reconcile of ${endpoints.length} endpoints`
177823
+ );
177824
+ return;
177825
+ }
177391
177826
  this._failedEntities = [];
177392
- const endpoints = this.root.parts.map((p) => p).filter((p) => !(p instanceof VacuumAreaSwitchEndpoint));
177393
177827
  this.entityIds = this.registry.entityIds;
177394
177828
  if (this.registry.isAutoComposedDevicesEnabled()) {
177395
177829
  for (const eid of this.entityIds) {
@@ -177441,7 +177875,6 @@ var BridgeEndpointManager = class extends Service {
177441
177875
  this.mappingFingerprints.set(entityId, fp);
177442
177876
  }
177443
177877
  }
177444
- const fullEntities = this.registry.fullEntities;
177445
177878
  stampIdentityPresence(
177446
177879
  this.identityStorage,
177447
177880
  this.bridgeId,
@@ -177490,13 +177923,16 @@ var BridgeEndpointManager = class extends Service {
177490
177923
  this.pendingRemovals.delete(endpoint.entityId);
177491
177924
  }
177492
177925
  if (!present) {
177493
- const since = this.pendingRemovals.get(endpoint.entityId);
177494
- if (since == null) {
177495
- this.pendingRemovals.set(endpoint.entityId, now);
177926
+ const entry = this.pendingRemovals.get(endpoint.entityId);
177927
+ if (entry == null) {
177928
+ this.pendingRemovals.set(endpoint.entityId, {
177929
+ since: now,
177930
+ generation: this.registry.snapshotGeneration
177931
+ });
177496
177932
  existingEndpoints.push(endpoint);
177497
177933
  continue;
177498
177934
  }
177499
- if (now - since < ENDPOINT_REMOVAL_GRACE_MS) {
177935
+ if (now - entry.since < ENDPOINT_REMOVAL_GRACE_MS || now - this.client.runningSince < ENDPOINT_REMOVAL_GRACE_MS || this.registry.snapshotGeneration <= entry.generation) {
177500
177936
  existingEndpoints.push(endpoint);
177501
177937
  continue;
177502
177938
  }
@@ -177512,13 +177948,13 @@ var BridgeEndpointManager = class extends Service {
177512
177948
  this.pendingRemovals.delete(endpoint.entityId);
177513
177949
  } else if (this.registry.isAutoComposedDevicesEnabled() && this.registry.isComposedSubEntityUsed(endpoint.entityId)) {
177514
177950
  this.log.info(
177515
- `Deleting standalone endpoint ${endpoint.entityId}, consumed by composed device`
177951
+ `Removing standalone endpoint ${endpoint.entityId}, consumed by composed device`
177516
177952
  );
177517
177953
  try {
177518
- await endpoint.delete();
177954
+ await endpoint.close();
177519
177955
  } catch (e) {
177520
177956
  this.log.warn(
177521
- `Failed to delete composed sub-entity endpoint ${endpoint.entityId}:`,
177957
+ `Failed to remove composed sub-entity endpoint ${endpoint.entityId}:`,
177522
177958
  e
177523
177959
  );
177524
177960
  }
@@ -177551,7 +177987,9 @@ var BridgeEndpointManager = class extends Service {
177551
177987
  }
177552
177988
  }
177553
177989
  }
177554
- this.scheduleRemovalRecheck();
177990
+ if (lifecycle === this.lifecycle) {
177991
+ this.scheduleRemovalRecheck();
177992
+ }
177555
177993
  let memoryLimitReached = false;
177556
177994
  for (const entityId of this.entityIds) {
177557
177995
  if (!memoryLimitReached && isHeapUnderPressure()) {
@@ -177625,7 +178063,7 @@ var BridgeEndpointManager = class extends Service {
177625
178063
  }
177626
178064
  }
177627
178065
  await this.reconcileAreaSwitches();
177628
- if (this.unsubscribe) {
178066
+ if (this.observingRequested) {
177629
178067
  this.startObserving();
177630
178068
  }
177631
178069
  }
@@ -177757,10 +178195,10 @@ var BridgeEndpointManager = class extends Service {
177757
178195
  this.pendingStates = void 0;
177758
178196
  this.pendingChanged = void 0;
177759
178197
  if (queued) {
177760
- void this.updateStates(
178198
+ this.updateStates(
177761
178199
  queued,
177762
178200
  queuedChanged === void 0 ? null : queuedChanged
177763
- );
178201
+ ).catch((e) => this.log.warn("Queued state update failed:", e));
177764
178202
  }
177765
178203
  });
177766
178204
  return this.updateInFlight;
@@ -177768,9 +178206,9 @@ var BridgeEndpointManager = class extends Service {
177768
178206
  async runUpdateStates(states, changed) {
177769
178207
  const startMs = performance.now();
177770
178208
  this.registry.mergeExternalStates(states);
177771
- const allEndpoints = this.root.parts.map((p) => p);
178209
+ const allEndpoints = [...this.root.parts].filter(isEntityPart);
177772
178210
  const endpoints = changed === null ? allEndpoints : allEndpoints.filter(
177773
- (e) => changed.has(e.entityId) || e.mappedEntityIds.some((id) => changed.has(id))
178211
+ (e) => changed.has(e.entityId) || (e.mappedEntityIds ?? []).some((id) => changed.has(id))
177774
178212
  );
177775
178213
  if (endpoints.length === 0) return;
177776
178214
  const results = await Promise.allSettled(
@@ -177850,7 +178288,7 @@ var BridgeEndpointManager = class extends Service {
177850
178288
  init_dist();
177851
178289
  init_esm();
177852
178290
  init_send_ha_message();
177853
- import { callService as callService3 } from "home-assistant-js-websocket";
178291
+ import { callService as callService2 } from "home-assistant-js-websocket";
177854
178292
  import { keys as keys2, pickBy, values as values3 } from "lodash-es";
177855
178293
  var BridgeRegistry = class _BridgeRegistry {
177856
178294
  constructor(registry3, dataProvider, client) {
@@ -177899,6 +178337,10 @@ var BridgeRegistry = class _BridgeRegistry {
177899
178337
  get fullEntities() {
177900
178338
  return this.registry.entities;
177901
178339
  }
178340
+ // Successful-reload counter, see HomeAssistantRegistry (#438).
178341
+ get snapshotGeneration() {
178342
+ return this.registry.snapshotGeneration;
178343
+ }
177902
178344
  // composed sub-entities may sit outside the bridge filter (#408), so these
177903
178345
  // fall back to the full HA registry. keep them separate from the strict
177904
178346
  // accessors above, every other caller must stay filtered.
@@ -178236,7 +178678,7 @@ var BridgeRegistry = class _BridgeRegistry {
178236
178678
  async resolveRoborockRooms(entityId) {
178237
178679
  if (!this.client) return [];
178238
178680
  try {
178239
- const raw = await callService3(
178681
+ const raw = await callService2(
178240
178682
  this.client.connection,
178241
178683
  "roborock",
178242
178684
  "get_maps",
@@ -178615,6 +179057,7 @@ function hashAreaId(areaId) {
178615
179057
  init_dist();
178616
179058
  init_esm7();
178617
179059
  import * as os10 from "node:os";
179060
+ init_protocol3();
178618
179061
  init_diagnostic_event_bus();
178619
179062
  var imWrapMarker2 = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
178620
179063
  var commandMessageTypes2 = [
@@ -178630,11 +179073,11 @@ function makeWarmStartState(state, now = (/* @__PURE__ */ new Date()).toISOStrin
178630
179073
  return { ...state, last_updated: now };
178631
179074
  }
178632
179075
  var ServerModeBridge = class {
178633
- constructor(logger253, dataProvider, endpointManager, server) {
179076
+ constructor(logger255, dataProvider, endpointManager, server) {
178634
179077
  this.dataProvider = dataProvider;
178635
179078
  this.endpointManager = endpointManager;
178636
179079
  this.server = server;
178637
- this.log = logger253.get(`ServerModeBridge / ${dataProvider.id}`);
179080
+ this.log = logger255.get(`ServerModeBridge / ${dataProvider.id}`);
178638
179081
  }
178639
179082
  dataProvider;
178640
179083
  endpointManager;
@@ -179014,12 +179457,18 @@ ${e?.toString()}`);
179014
179457
  for (const s of [...sessionManager.sessions]) {
179015
179458
  if (s !== newSession && !s.isClosing && s.peerNodeId === newSession.peerNodeId && s.fabric?.fabricIndex === newSession.fabric?.fabricIndex && s.subscriptions.size === 0) {
179016
179459
  this.log.info(
179017
- `Closing stale session ${s.id} (peer ${s.peerNodeId}, 0 subs), replaced by session ${newSession.id}`
179460
+ `Session ${s.id} (peer ${s.peerNodeId}, 0 subs) replaced by session ${newSession.id}, closing it once it goes quiet`
179461
+ );
179462
+ const staleId = s.id;
179463
+ const armed = this.staleSessionTimers.get(staleId);
179464
+ if (armed) clearTimeout(armed);
179465
+ this.staleSessionTimers.set(
179466
+ staleId,
179467
+ setTimeout(() => {
179468
+ this.staleSessionTimers.delete(staleId);
179469
+ this.closeStaleSession(staleId, PRIMING_GRACE_MS);
179470
+ }, replacedSessionTimeoutMs(this.dataProvider.featureFlags))
179018
179471
  );
179019
- s.initiateForceClose({
179020
- cause: new Error("stale session replaced by new session")
179021
- }).catch(() => {
179022
- });
179023
179472
  }
179024
179473
  }
179025
179474
  const quietWindowMs = staleSessionQuietWindowMs(
@@ -179055,6 +179504,11 @@ ${e?.toString()}`);
179055
179504
  };
179056
179505
  this.sessionDeletedHandler = (session) => {
179057
179506
  this.sessionStartedAt.delete(session.id);
179507
+ const armed = this.staleSessionTimers.get(session.id);
179508
+ if (armed) {
179509
+ clearTimeout(armed);
179510
+ this.staleSessionTimers.delete(session.id);
179511
+ }
179058
179512
  const sessions = [...sessionManager.sessions];
179059
179513
  this.log.warn(
179060
179514
  `Session closed: id=${session.id} peer=${session.peerNodeId} | remaining sessions=${sessions.length}`
@@ -179163,7 +179617,7 @@ ${e?.toString()}`);
179163
179617
  );
179164
179618
  });
179165
179619
  }
179166
- closeStaleSession(sessionId) {
179620
+ closeStaleSession(sessionId, minQuietMs = 0) {
179167
179621
  try {
179168
179622
  const sessionManager = this.server.env.get(SessionManager);
179169
179623
  for (const s of [...sessionManager.sessions]) {
@@ -179173,7 +179627,10 @@ ${e?.toString()}`);
179173
179627
  const idleSec = Math.round((Date.now() - s.timestamp) / 1e3);
179174
179628
  if (!staleSessionShouldClose(
179175
179629
  s,
179176
- staleSessionQuietWindowMs(this.dataProvider.featureFlags)
179630
+ Math.max(
179631
+ staleSessionQuietWindowMs(this.dataProvider.featureFlags),
179632
+ minQuietMs
179633
+ )
179177
179634
  )) {
179178
179635
  this.log.info(
179179
179636
  `Keeping session ${s.id} (peer ${s.peerNodeId}, 0 subs, last traffic ${idleSec}s ago)`
@@ -179182,7 +179639,7 @@ ${e?.toString()}`);
179182
179639
  sessionId,
179183
179640
  setTimeout(() => {
179184
179641
  this.staleSessionTimers.delete(sessionId);
179185
- this.closeStaleSession(sessionId);
179642
+ this.closeStaleSession(sessionId, minQuietMs);
179186
179643
  }, deadSessionTimeoutMs(this.dataProvider.featureFlags))
179187
179644
  );
179188
179645
  break;
@@ -179211,6 +179668,9 @@ ${e?.toString()}`);
179211
179668
  if (s.isClosing || s.subscriptions.size > 0) {
179212
179669
  continue;
179213
179670
  }
179671
+ if (this.staleSessionTimers.has(s.id)) {
179672
+ continue;
179673
+ }
179214
179674
  const idleSec = Math.round((Date.now() - s.timestamp) / 1e3);
179215
179675
  if (!staleSessionShouldClose(
179216
179676
  s,
@@ -179776,7 +180236,7 @@ function ServerModeVacuumDevice(homeAssistantEntity, includeOnOff = false, clean
179776
180236
  }
179777
180237
 
179778
180238
  // src/matter/endpoints/server-mode-vacuum-endpoint.ts
179779
- var logger251 = Logger.get("ServerModeVacuumEndpoint");
180239
+ var logger253 = Logger.get("ServerModeVacuumEndpoint");
179780
180240
  var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEndpoint {
179781
180241
  static async create(registry3, entityId, mapping, endpointId) {
179782
180242
  const deviceRegistry = registry3.deviceOf(entityId);
@@ -179789,7 +180249,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
179789
180249
  if (mapping?.disableBatteryMapping && mapping.batteryEntity) {
179790
180250
  effectiveMapping = { ...mapping, batteryEntity: void 0 };
179791
180251
  }
179792
- logger251.info(
180252
+ logger253.info(
179793
180253
  `${entityId}: device_id=${entity.device_id}, manualBattery=${mapping?.batteryEntity ?? "none"}`
179794
180254
  );
179795
180255
  if (entity.device_id) {
@@ -179804,15 +180264,15 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
179804
180264
  batteryEntity: batteryEntityId
179805
180265
  };
179806
180266
  registry3.markBatteryEntityUsed(batteryEntityId);
179807
- logger251.info(`${entityId}: Auto-assigned battery ${batteryEntityId}`);
180267
+ logger253.info(`${entityId}: Auto-assigned battery ${batteryEntityId}`);
179808
180268
  } else {
179809
180269
  const attrs = state.attributes;
179810
180270
  if (attrs.battery_level != null || attrs.battery != null) {
179811
- logger251.info(
180271
+ logger253.info(
179812
180272
  `${entityId}: No battery entity found, using battery attribute from vacuum state`
179813
180273
  );
179814
180274
  } else {
179815
- logger251.warn(
180275
+ logger253.warn(
179816
180276
  `${entityId}: No battery entity found for device ${entity.device_id}`
179817
180277
  );
179818
180278
  }
@@ -179827,7 +180287,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
179827
180287
  entityId: effectiveMapping?.entityId ?? entityId,
179828
180288
  cleaningModeEntity: vacuumEntities.cleaningModeEntity
179829
180289
  };
179830
- logger251.info(
180290
+ logger253.info(
179831
180291
  `${entityId}: Auto-assigned cleaningMode ${vacuumEntities.cleaningModeEntity}`
179832
180292
  );
179833
180293
  }
@@ -179837,7 +180297,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
179837
180297
  entityId: effectiveMapping?.entityId ?? entityId,
179838
180298
  suctionLevelEntity: vacuumEntities.suctionLevelEntity
179839
180299
  };
179840
- logger251.info(
180300
+ logger253.info(
179841
180301
  `${entityId}: Auto-assigned suctionLevel ${vacuumEntities.suctionLevelEntity}`
179842
180302
  );
179843
180303
  }
@@ -179847,7 +180307,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
179847
180307
  entityId: effectiveMapping?.entityId ?? entityId,
179848
180308
  mopIntensityEntity: vacuumEntities.mopIntensityEntity
179849
180309
  };
179850
- logger251.info(
180310
+ logger253.info(
179851
180311
  `${entityId}: Auto-assigned mopIntensity ${vacuumEntities.mopIntensityEntity}`
179852
180312
  );
179853
180313
  }
@@ -179857,7 +180317,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
179857
180317
  entityId: effectiveMapping?.entityId ?? entityId,
179858
180318
  currentRoomEntity: vacuumEntities.currentRoomEntity
179859
180319
  };
179860
- logger251.info(
180320
+ logger253.info(
179861
180321
  `${entityId}: Auto-assigned currentRoom ${vacuumEntities.currentRoomEntity}`
179862
180322
  );
179863
180323
  }
@@ -179872,7 +180332,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
179872
180332
  entityId: effectiveMapping?.entityId ?? entityId,
179873
180333
  cleanAreaRooms
179874
180334
  };
179875
- logger251.info(
180335
+ logger253.info(
179876
180336
  `${entityId}: Using ${cleanAreaRooms.length} HA areas via CLEAN_AREA`
179877
180337
  );
179878
180338
  }
@@ -179893,7 +180353,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
179893
180353
  rooms: roomsObj
179894
180354
  }
179895
180355
  };
179896
- logger251.info(
180356
+ logger253.info(
179897
180357
  `${entityId}: Auto-detected ${valetudoRooms.length} Valetudo segments`
179898
180358
  );
179899
180359
  } else {
@@ -179910,14 +180370,14 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
179910
180370
  rooms: roomsObj
179911
180371
  }
179912
180372
  };
179913
- logger251.info(
180373
+ logger253.info(
179914
180374
  `${entityId}: Auto-detected ${roborockRooms.length} Roborock rooms`
179915
180375
  );
179916
180376
  }
179917
180377
  }
179918
180378
  }
179919
180379
  } else {
179920
- logger251.warn(`${entityId}: No device_id, cannot auto-assign battery`);
180380
+ logger253.warn(`${entityId}: No device_id, cannot auto-assign battery`);
179921
180381
  }
179922
180382
  const payload = {
179923
180383
  entity_id: entityId,
@@ -179990,11 +180450,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
179990
180450
  }
179991
180451
  if (mappedChanged) {
179992
180452
  this.pendingMappedChange = true;
179993
- logger251.debug(
180453
+ logger253.debug(
179994
180454
  `Mapped entity change detected for ${this.entityId}, forcing update`
179995
180455
  );
179996
180456
  }
179997
- logger251.debug(
180457
+ logger253.debug(
179998
180458
  `State update received for ${this.entityId}: state=${state.state}`
179999
180459
  );
180000
180460
  this.lastState = state;
@@ -180057,8 +180517,20 @@ var ServerModeEndpointManager = class extends Service {
180057
180517
  log;
180058
180518
  entityIds = [];
180059
180519
  unsubscribe;
180520
+ observingRequested = false;
180060
180521
  _failedEntities = [];
180061
180522
  endpoints = /* @__PURE__ */ new Map();
180523
+ // Same grace as the aggregator manager: server mode deleted on the FIRST
180524
+ // refresh an entity was absent, so one partial HA snapshot re-minted the
180525
+ // device (#438).
180526
+ pendingRemovals = /* @__PURE__ */ new Map();
180527
+ removalRecheckTimer = null;
180528
+ // endpoint id -> entity that closed but may come back (disable). Its number
180529
+ // is still parked, so nobody else may mount on that id (#438).
180530
+ parkedEndpointIds = /* @__PURE__ */ new Map();
180531
+ // Bumped on every stop, so a refresh that was already running cannot arm a
180532
+ // timer on a bridge that has since stopped.
180533
+ lifecycle = 0;
180062
180534
  get failedEntities() {
180063
180535
  return this._failedEntities;
180064
180536
  }
@@ -180076,6 +180548,11 @@ var ServerModeEndpointManager = class extends Service {
180076
180548
  }
180077
180549
  async dispose() {
180078
180550
  this.stopObserving();
180551
+ if (this.removalRecheckTimer) {
180552
+ clearTimeout(this.removalRecheckTimer);
180553
+ this.removalRecheckTimer = null;
180554
+ }
180555
+ this.pendingRemovals.clear();
180079
180556
  for (const [entityId, entry] of this.endpoints) {
180080
180557
  try {
180081
180558
  await entry.endpoint.close();
@@ -180089,7 +180566,8 @@ var ServerModeEndpointManager = class extends Service {
180089
180566
  this.endpoints.clear();
180090
180567
  }
180091
180568
  async startObserving() {
180092
- this.stopObserving();
180569
+ this.clearSubscription();
180570
+ this.observingRequested = true;
180093
180571
  if (!this.entityIds.length) {
180094
180572
  return;
180095
180573
  }
@@ -180109,10 +180587,19 @@ var ServerModeEndpointManager = class extends Service {
180109
180587
  }
180110
180588
  return [...ids];
180111
180589
  }
180112
- stopObserving() {
180590
+ clearSubscription() {
180113
180591
  this.unsubscribe?.();
180114
180592
  this.unsubscribe = void 0;
180115
180593
  }
180594
+ stopObserving() {
180595
+ this.observingRequested = false;
180596
+ this.lifecycle++;
180597
+ this.clearSubscription();
180598
+ if (this.removalRecheckTimer) {
180599
+ clearTimeout(this.removalRecheckTimer);
180600
+ this.removalRecheckTimer = null;
180601
+ }
180602
+ }
180116
180603
  /** Primary first (the entity the first include matcher tests true for). */
180117
180604
  orderEntityIds(ids) {
180118
180605
  const firstMatcher = this.dataProvider.filter?.include?.[0];
@@ -180135,6 +180622,44 @@ var ServerModeEndpointManager = class extends Service {
180135
180622
  this.endpoints.delete(entityId);
180136
180623
  }
180137
180624
  }
180625
+ // Absence-driven removals wait out the grace window AND a fresh successful
180626
+ // HA reload, so restarts and stale snapshots never erase numbers (#438).
180627
+ // Returns the ids whose absence is confirmed, ready for removeEndpoints.
180628
+ removableAfterGrace(entityIds) {
180629
+ const now = Date.now();
180630
+ const generation = this.registry.snapshotGeneration;
180631
+ const ready = [];
180632
+ for (const entityId of entityIds) {
180633
+ const entry = this.pendingRemovals.get(entityId);
180634
+ if (entry == null) {
180635
+ this.pendingRemovals.set(entityId, { since: now, generation });
180636
+ continue;
180637
+ }
180638
+ if (now - entry.since < ENDPOINT_REMOVAL_GRACE_MS || now - this.client.runningSince < ENDPOINT_REMOVAL_GRACE_MS || generation <= entry.generation) {
180639
+ continue;
180640
+ }
180641
+ ready.push(entityId);
180642
+ this.pendingRemovals.delete(entityId);
180643
+ }
180644
+ return ready;
180645
+ }
180646
+ // refreshDevices only runs on registry-fingerprint changes, which may not
180647
+ // recur, so drive held removals to completion ourselves after the grace.
180648
+ scheduleRemovalRecheck() {
180649
+ if (this.removalRecheckTimer) {
180650
+ clearTimeout(this.removalRecheckTimer);
180651
+ this.removalRecheckTimer = null;
180652
+ }
180653
+ if (this.pendingRemovals.size === 0) return;
180654
+ const lifecycle = this.lifecycle;
180655
+ this.removalRecheckTimer = setTimeout(() => {
180656
+ this.removalRecheckTimer = null;
180657
+ this.refreshDevices().catch((e) => {
180658
+ this.log.warn("Endpoint removal recheck failed:", e);
180659
+ if (lifecycle === this.lifecycle) this.scheduleRemovalRecheck();
180660
+ });
180661
+ }, ENDPOINT_REMOVAL_GRACE_MS + 5e3);
180662
+ }
180138
180663
  // Like removeEndpoints but close() keeps the persisted number pre-allocated,
180139
180664
  // so a same-id recreate reuses it. Used on rename and same-id recreates (#404).
180140
180665
  async closeEndpoint(entityId) {
@@ -180144,29 +180669,40 @@ var ServerModeEndpointManager = class extends Service {
180144
180669
  await entry.endpoint.close();
180145
180670
  } catch (e) {
180146
180671
  this.log.warn(`Failed to close endpoint ${entityId}:`, e);
180672
+ return;
180147
180673
  }
180148
180674
  this.serverNode.forgetDevice(entry.endpoint);
180149
180675
  this.endpoints.delete(entityId);
180150
180676
  }
180151
180677
  async refreshDevices() {
180152
180678
  this.registry.refresh();
180153
- this._failedEntities = [];
180154
- this.entityIds = this.registry.entityIds;
180679
+ const lifecycle = this.lifecycle;
180155
180680
  const fullEntities = this.registry.fullEntities;
180156
- stampIdentityPresence(
180157
- this.identityStorage,
180158
- this.dataProvider.id,
180159
- buildPresentIdentityKeys(fullEntities)
180160
- );
180161
- stampMappingPresence(
180162
- this.mappingStorage,
180163
- this.dataProvider.id,
180164
- buildPresentEntityIds(fullEntities)
180165
- );
180166
180681
  try {
180682
+ if (!this.client.haRunning || Object.keys(fullEntities).length === 0) {
180683
+ this.pendingRemovals.clear();
180684
+ this.log.warn(
180685
+ "HA not running or registry empty, deferring server mode reconcile"
180686
+ );
180687
+ return;
180688
+ }
180689
+ this._failedEntities = [];
180690
+ this.entityIds = this.registry.entityIds;
180691
+ stampIdentityPresence(
180692
+ this.identityStorage,
180693
+ this.dataProvider.id,
180694
+ buildPresentIdentityKeys(fullEntities)
180695
+ );
180696
+ stampMappingPresence(
180697
+ this.mappingStorage,
180698
+ this.dataProvider.id,
180699
+ buildPresentEntityIds(fullEntities)
180700
+ );
180167
180701
  if (this.entityIds.length === 0) {
180168
180702
  this.log.warn("Server mode bridge has no entities configured");
180169
- await this.removeEndpoints([...this.endpoints.keys()]);
180703
+ await this.removeEndpoints(
180704
+ this.removableAfterGrace([...this.endpoints.keys()])
180705
+ );
180170
180706
  this._failedEntities.push({
180171
180707
  entityId: this.dataProvider.filter?.include?.[0]?.value ?? "(no entity configured)",
180172
180708
  reason: "No Home Assistant entity matched this bridge's filter. Check for typos or renamed/removed entities."
@@ -180212,19 +180748,35 @@ var ServerModeEndpointManager = class extends Service {
180212
180748
  endpointIdToEntity.set(resolved.endpointId, entityId);
180213
180749
  }
180214
180750
  const keep = new Set(orderedIds);
180751
+ for (const id of keep) {
180752
+ this.pendingRemovals.delete(id);
180753
+ }
180215
180754
  const removed = [...this.endpoints.keys()].filter((id) => !keep.has(id));
180216
- let structureChanged = removed.length > 0;
180217
180755
  const genuinelyRemoved = [];
180218
180756
  for (const oldId of removed) {
180219
180757
  const entry = this.endpoints.get(oldId);
180220
180758
  const claimant = entry ? endpointIdToEntity.get(entry.endpoint.id) : void 0;
180221
180759
  if (entry && claimant != null && claimant !== oldId) {
180222
180760
  await this.closeEndpoint(oldId);
180761
+ this.pendingRemovals.delete(oldId);
180223
180762
  } else {
180224
180763
  genuinelyRemoved.push(oldId);
180225
180764
  }
180226
180765
  }
180227
- await this.removeEndpoints(genuinelyRemoved);
180766
+ const confirmedRemoved = this.removableAfterGrace(genuinelyRemoved);
180767
+ let structureChanged = removed.length > genuinelyRemoved.length || confirmedRemoved.length > 0;
180768
+ await this.removeEndpoints(confirmedRemoved);
180769
+ for (const entityId of orderedIds) {
180770
+ const mapping = this.getEntityMapping(entityId);
180771
+ if (!mapping?.disabled) continue;
180772
+ for (const [id, owner] of this.parkedEndpointIds) {
180773
+ if (owner === entityId) this.parkedEndpointIds.delete(id);
180774
+ }
180775
+ this.parkedEndpointIds.set(
180776
+ this.endpoints.get(entityId)?.endpoint.id ?? resolvedByEntity.get(entityId)?.endpointId ?? createEndpointId(entityId, mapping.customName),
180777
+ entityId
180778
+ );
180779
+ }
180228
180780
  for (const entityId of orderedIds) {
180229
180781
  const mapping = this.getEntityMapping(entityId);
180230
180782
  if (mapping?.disabled) {
@@ -180232,7 +180784,7 @@ var ServerModeEndpointManager = class extends Service {
180232
180784
  `Entity in server mode bridge is disabled: ${entityId}`
180233
180785
  );
180234
180786
  if (this.endpoints.has(entityId)) {
180235
- await this.removeEndpoints([entityId]);
180787
+ await this.closeEndpoint(entityId);
180236
180788
  structureChanged = true;
180237
180789
  }
180238
180790
  this._failedEntities.push({
@@ -180271,10 +180823,11 @@ var ServerModeEndpointManager = class extends Service {
180271
180823
  const collision = [...this.endpoints.entries()].find(
180272
180824
  ([, entry]) => entry.endpoint.id === endpointId
180273
180825
  );
180274
- if (collision) {
180826
+ const parkedBy = this.parkedEndpointIds.get(endpointId);
180827
+ if (collision || parkedBy != null && parkedBy !== entityId) {
180275
180828
  this._failedEntities.push({
180276
180829
  entityId,
180277
- reason: `Endpoint id collides with ${collision[0]}. Set distinct custom names.`
180830
+ reason: `Endpoint id collides with ${collision?.[0] ?? parkedBy}. Set distinct custom names.`
180278
180831
  });
180279
180832
  continue;
180280
180833
  }
@@ -180312,6 +180865,11 @@ var ServerModeEndpointManager = class extends Service {
180312
180865
  }
180313
180866
  await this.serverNode.addDevice(endpoint);
180314
180867
  this.endpoints.set(entityId, { endpoint, fingerprint });
180868
+ for (const [id, owner] of this.parkedEndpointIds) {
180869
+ if (id === endpointId || owner === entityId) {
180870
+ this.parkedEndpointIds.delete(id);
180871
+ }
180872
+ }
180315
180873
  structureChanged = true;
180316
180874
  this.log.info(`Server mode: Added device ${entityId}`);
180317
180875
  } catch (e) {
@@ -180331,7 +180889,10 @@ var ServerModeEndpointManager = class extends Service {
180331
180889
  }
180332
180890
  }
180333
180891
  } finally {
180334
- if (this.unsubscribe) {
180892
+ if (lifecycle === this.lifecycle) {
180893
+ this.scheduleRemovalRecheck();
180894
+ }
180895
+ if (this.observingRequested) {
180335
180896
  this.startObserving();
180336
180897
  }
180337
180898
  }
@@ -180572,10 +181133,10 @@ var BridgeEnvironmentFactory = class extends BridgeFactory {
180572
181133
  // src/core/ioc/app-environment.ts
180573
181134
  var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
180574
181135
  constructor(rootEnv, options) {
180575
- const logger253 = rootEnv.get(LoggerService);
181136
+ const logger255 = rootEnv.get(LoggerService);
180576
181137
  super({
180577
181138
  id: "App",
180578
- log: logger253.get("AppContainer"),
181139
+ log: logger255.get("AppContainer"),
180579
181140
  parent: rootEnv
180580
181141
  });
180581
181142
  this.options = options;
@@ -180589,8 +181150,8 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
180589
181150
  }
180590
181151
  construction;
180591
181152
  async init() {
180592
- const logger253 = this.get(LoggerService);
180593
- this.set(LoggerService, logger253);
181153
+ const logger255 = this.get(LoggerService);
181154
+ this.set(LoggerService, logger255);
180594
181155
  this.set(AppStorage, new AppStorage(await this.load(StorageService)));
180595
181156
  this.set(BridgeStorage, new BridgeStorage(await this.load(AppStorage)));
180596
181157
  this.set(
@@ -180611,7 +181172,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
180611
181172
  );
180612
181173
  this.set(
180613
181174
  HomeAssistantClient,
180614
- new HomeAssistantClient(logger253, this.options.homeAssistant)
181175
+ new HomeAssistantClient(logger255, this.options.homeAssistant)
180615
181176
  );
180616
181177
  this.set(
180617
181178
  HomeAssistantConfig,
@@ -180619,7 +181180,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
180619
181180
  );
180620
181181
  this.set(
180621
181182
  HomeAssistantActions,
180622
- new HomeAssistantActions(logger253, await this.load(HomeAssistantClient))
181183
+ new HomeAssistantActions(logger255, await this.load(HomeAssistantClient))
180623
181184
  );
180624
181185
  this.set(
180625
181186
  HomeAssistantRegistry,
@@ -180655,7 +181216,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
180655
181216
  this.set(
180656
181217
  WebApi,
180657
181218
  new WebApi(
180658
- logger253,
181219
+ logger255,
180659
181220
  await this.load(BridgeService),
180660
181221
  await this.load(HomeAssistantClient),
180661
181222
  await this.load(HomeAssistantRegistry),
@@ -180676,7 +181237,7 @@ var AppEnvironment = class _AppEnvironment extends EnvironmentBase {
180676
181237
 
180677
181238
  // src/matter/patches/patch-level-control-tlv.ts
180678
181239
  init_esm();
180679
- var logger252 = Logger.get("PatchLevelControlTlv");
181240
+ var logger254 = Logger.get("PatchLevelControlTlv");
180680
181241
  var optionalFieldModels = /* @__PURE__ */ new WeakSet();
180681
181242
  var fieldModelMandatoryGetterPatched = false;
180682
181243
  function patchLevelControlTlv() {
@@ -180732,11 +181293,11 @@ function patchLevelControlTlv() {
180732
181293
  patched++;
180733
181294
  }
180734
181295
  if (patched > 0) {
180735
- logger252.info(
181296
+ logger254.info(
180736
181297
  `Patched ${patched} LevelControl TLV schema(s): transitionTime is now optional (Google Home compatibility)`
180737
181298
  );
180738
181299
  } else {
180739
- logger252.warn(
181300
+ logger254.warn(
180740
181301
  "Failed to patch LevelControl TLV schemas, field definitions not found. Google Home brightness adjustment may not work.",
180741
181302
  {
180742
181303
  moveToLevel: describeFields(
@@ -182478,6 +183039,7 @@ export {
182478
183039
  @matter/main/dist/esm/version.js:
182479
183040
  @matter/main/dist/esm/index.js:
182480
183041
  @matter/main/dist/esm/protocol.js:
183042
+ @matter/main/dist/esm/types.js:
182481
183043
  @matter/types/dist/esm/clusters/fan-control.js:
182482
183044
  @matter/node/dist/esm/behaviors/fan-control/FanControlBehavior.js:
182483
183045
  @matter/node/dist/esm/behaviors/fan-control/FanControlServer.js:
@@ -182908,7 +183470,6 @@ export {
182908
183470
  @matter/node/dist/esm/devices/index.js:
182909
183471
  @matter/main/dist/esm/devices.js:
182910
183472
  @matter/main/dist/esm/node.js:
182911
- @matter/main/dist/esm/types.js:
182912
183473
  @matter/node/dist/esm/behaviors/account-login/index.js:
182913
183474
  @matter/node/dist/esm/behaviors/actions/ActionsClient.js:
182914
183475
  @matter/node/dist/esm/behaviors/actions/index.js:
@@ -183133,6 +183694,13 @@ export {
183133
183694
  @matter/node/dist/esm/behaviors/zone-management/index.js:
183134
183695
  @matter/node/dist/esm/behaviors/index.js:
183135
183696
  @matter/main/dist/esm/behaviors.js:
183697
+ @matter/types/dist/esm/clusters/alarm-base.js:
183698
+ @matter/types/dist/esm/clusters/concentration-measurement.js:
183699
+ @matter/types/dist/esm/clusters/label.js:
183700
+ @matter/types/dist/esm/clusters/resource-monitoring.js:
183701
+ @matter/types/dist/esm/clusters/web-rtc-transport-definitions.js:
183702
+ @matter/types/dist/esm/clusters/index.js:
183703
+ @matter/main/dist/esm/clusters.js:
183136
183704
  @matter/node/dist/esm/endpoints/base.js:
183137
183705
  @matter/node/dist/esm/endpoints/bridged-node.js:
183138
183706
  @matter/node/dist/esm/endpoints/device-energy-management.js:
@@ -183144,13 +183712,6 @@ export {
183144
183712
  @matter/node/dist/esm/endpoints/secondary-network-interface.js:
183145
183713
  @matter/node/dist/esm/endpoints/index.js:
183146
183714
  @matter/main/dist/esm/endpoints.js:
183147
- @matter/types/dist/esm/clusters/alarm-base.js:
183148
- @matter/types/dist/esm/clusters/concentration-measurement.js:
183149
- @matter/types/dist/esm/clusters/label.js:
183150
- @matter/types/dist/esm/clusters/resource-monitoring.js:
183151
- @matter/types/dist/esm/clusters/web-rtc-transport-definitions.js:
183152
- @matter/types/dist/esm/clusters/index.js:
183153
- @matter/main/dist/esm/clusters.js:
183154
183715
  @matter/main/dist/esm/model.js:
183155
183716
  @matter/main/dist/esm/forwards/clusters/level-control.js:
183156
183717
  @matter/main/dist/esm/forwards/behaviors/boolean-state.js: