@riddix/hamh 2.1.0-alpha.753 → 2.1.0-alpha.755

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.
@@ -127033,6 +127033,7 @@ function entityMappingApi(mappingStorage) {
127033
127033
  humidityEntity: body.humidityEntity,
127034
127034
  pressureEntity: body.pressureEntity,
127035
127035
  batteryEntity: body.batteryEntity,
127036
+ chargingStateEntity: body.chargingStateEntity,
127036
127037
  roomEntities: body.roomEntities,
127037
127038
  disableLockPin: body.disableLockPin,
127038
127039
  powerEntity: body.powerEntity,
@@ -131671,6 +131672,7 @@ var EntityMappingStorage = class extends Service {
131671
131672
  temperatureEntity: request.temperatureEntity?.trim() || void 0,
131672
131673
  humidityEntity: request.humidityEntity?.trim() || void 0,
131673
131674
  batteryEntity: request.batteryEntity?.trim() || void 0,
131675
+ chargingStateEntity: request.chargingStateEntity?.trim() || void 0,
131674
131676
  roomEntities: roomEntities.length > 0 ? roomEntities : void 0,
131675
131677
  disableLockPin: request.disableLockPin || void 0,
131676
131678
  powerEntity: request.powerEntity?.trim() || void 0,
@@ -131697,7 +131699,7 @@ var EntityMappingStorage = class extends Service {
131697
131699
  climateAutoMode: request.climateAutoMode || void 0,
131698
131700
  composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0
131699
131701
  };
131700
- if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.roomEntities && !config11.disableLockPin && !config11.powerEntity && !config11.energyEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0)) {
131702
+ if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.powerEntity && !config11.energyEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0)) {
131701
131703
  bridgeMap.delete(request.entityId);
131702
131704
  } else {
131703
131705
  bridgeMap.set(request.entityId, config11);
@@ -148434,6 +148436,7 @@ function seedExistingSessionStarts(startedAt, sessions, now = Date.now()) {
148434
148436
  // src/services/bridges/bridge.ts
148435
148437
  var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
148436
148438
  var DEAD_SESSION_TIMEOUT_MS = 6e4;
148439
+ var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS = 2500;
148437
148440
  var Bridge = class {
148438
148441
  constructor(env, logger234, dataProvider, endpointManager) {
148439
148442
  this.dataProvider = dataProvider;
@@ -148683,6 +148686,7 @@ ${e?.toString()}`);
148683
148686
  async runStop(code, reason) {
148684
148687
  this.unwireSessionDiagnostics();
148685
148688
  this.stopAutoForceSync();
148689
+ await this.closeActiveSessions();
148686
148690
  await this.endpointManager.stopPlugins();
148687
148691
  this.endpointManager.stopObserving();
148688
148692
  try {
@@ -148713,6 +148717,7 @@ ${e?.toString()}`);
148713
148717
  this.log.info(`Force sync: every ${AUTO_FORCE_SYNC_INTERVAL_MS / 1e3}s`);
148714
148718
  }
148715
148719
  wireSessionDiagnostics() {
148720
+ this.unwireSessionDiagnostics();
148716
148721
  try {
148717
148722
  const sessionManager = this.server.env.get(SessionManager);
148718
148723
  seedExistingSessionStarts(this.sessionStartedAt, sessionManager.sessions);
@@ -148868,6 +148873,42 @@ ${e?.toString()}`);
148868
148873
  } catch {
148869
148874
  }
148870
148875
  }
148876
+ // Close every active session on shutdown so each controller is told to drop
148877
+ // its CASE session instead of being left with a stale one. Mirrors the
148878
+ // dead-session close, but covers all sessions and waits (capped) so the
148879
+ // close actually reaches the peer before the server is canceled.
148880
+ async closeActiveSessions() {
148881
+ try {
148882
+ const sessionManager = this.server.env.get(SessionManager);
148883
+ const closes = [];
148884
+ for (const s of [...sessionManager.sessions]) {
148885
+ if (s.isClosing) {
148886
+ continue;
148887
+ }
148888
+ closes.push(
148889
+ s.initiateClose().catch(() => {
148890
+ return s.initiateForceClose({
148891
+ cause: new Error("graceful close failed, forcing")
148892
+ });
148893
+ })
148894
+ );
148895
+ }
148896
+ if (closes.length === 0) {
148897
+ return;
148898
+ }
148899
+ this.log.info(`Closing ${closes.length} active session(s) on shutdown`);
148900
+ let timer;
148901
+ const timeout = new Promise((resolve11) => {
148902
+ timer = setTimeout(resolve11, SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS);
148903
+ });
148904
+ try {
148905
+ await Promise.race([Promise.allSettled(closes), timeout]);
148906
+ } finally {
148907
+ clearTimeout(timer);
148908
+ }
148909
+ } catch {
148910
+ }
148911
+ }
148871
148912
  /**
148872
148913
  * Force a fresh mDNS operational advertisement after session cleanup.
148873
148914
  * matter.js DeviceAdvertiser only re-announces when a subscription is
@@ -149217,6 +149258,7 @@ function getMappedEntityIds(mapping) {
149217
149258
  if (!mapping) return [];
149218
149259
  const ids = [];
149219
149260
  if (mapping.batteryEntity) ids.push(mapping.batteryEntity);
149261
+ if (mapping.chargingStateEntity) ids.push(mapping.chargingStateEntity);
149220
149262
  if (mapping.temperatureEntity) ids.push(mapping.temperatureEntity);
149221
149263
  if (mapping.humidityEntity) ids.push(mapping.humidityEntity);
149222
149264
  if (mapping.pressureEntity) ids.push(mapping.pressureEntity);
@@ -149749,6 +149791,13 @@ var PowerSourceServerBase = class extends FeaturedBase2 {
149749
149791
  } else if (isCharging2 === false) {
149750
149792
  batChargeState = PowerSource3.BatChargeState.IsNotCharging;
149751
149793
  }
149794
+ const explicitChargeState = config11.getChargeState?.(
149795
+ entity.state,
149796
+ this.agent
149797
+ );
149798
+ if (explicitChargeState != null) {
149799
+ batChargeState = explicitChargeState;
149800
+ }
149752
149801
  applyPatchState(this.state, {
149753
149802
  status: PowerSource3.PowerSourceStatus.Active,
149754
149803
  batPercentRemaining,
@@ -160330,6 +160379,28 @@ function getVacuumBatteryPercent(entity, agent) {
160330
160379
  const parsed = Number.parseFloat(String(raw));
160331
160380
  return Number.isNaN(parsed) ? null : parsed;
160332
160381
  }
160382
+ var CHARGING_STRINGS = {
160383
+ charging: "charging",
160384
+ go_charging: "charging",
160385
+ on: "charging",
160386
+ true: "charging",
160387
+ full: "full",
160388
+ not_charging: "not_charging",
160389
+ not_chargeable: "not_charging",
160390
+ discharging: "not_charging",
160391
+ off: "not_charging",
160392
+ false: "not_charging"
160393
+ };
160394
+ function mapChargingString(raw) {
160395
+ return CHARGING_STRINGS[raw.trim().toLowerCase()] ?? null;
160396
+ }
160397
+ function getVacuumChargingState(agent) {
160398
+ const id = agent.get(HomeAssistantEntityBehavior).state.mapping?.chargingStateEntity;
160399
+ if (!id) return null;
160400
+ const state = agent.env.get(EntityStateProvider).getState(id);
160401
+ if (!state) return null;
160402
+ return mapChargingString(state.state);
160403
+ }
160333
160404
 
160334
160405
  // src/matter/endpoints/legacy/vacuum/behaviors/vacuum-power-source-server.ts
160335
160406
  var VacuumPowerSourceServer = PowerSourceServer2({
@@ -160337,6 +160408,14 @@ var VacuumPowerSourceServer = PowerSourceServer2({
160337
160408
  isCharging(entity) {
160338
160409
  const state = entity.state;
160339
160410
  return state === VacuumState.docked;
160411
+ },
160412
+ getChargeState(_, agent) {
160413
+ const signal = getVacuumChargingState(agent);
160414
+ if (signal === "charging") return PowerSource3.BatChargeState.IsCharging;
160415
+ if (signal === "full") return PowerSource3.BatChargeState.IsAtFullCharge;
160416
+ if (signal === "not_charging")
160417
+ return PowerSource3.BatChargeState.IsNotCharging;
160418
+ return null;
160340
160419
  }
160341
160420
  });
160342
160421
 
@@ -161086,7 +161165,7 @@ function isDockedCharging(entity, batteryPercent) {
161086
161165
  if (batteryPercent != null) return batteryPercent < 100;
161087
161166
  return isCharging(entity);
161088
161167
  }
161089
- function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttributes(entity.attributes)) {
161168
+ function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttributes(entity.attributes), chargingState = null) {
161090
161169
  const state = entity.state;
161091
161170
  const cleaningStates = [
161092
161171
  VacuumState.cleaning,
@@ -161097,11 +161176,8 @@ function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttribute
161097
161176
  ];
161098
161177
  let operationalState;
161099
161178
  if (state === VacuumState.docked) {
161100
- if (isDockedCharging(entity, batteryPercent)) {
161101
- operationalState = RvcOperationalState4.OperationalState.Charging;
161102
- } else {
161103
- operationalState = RvcOperationalState4.OperationalState.Docked;
161104
- }
161179
+ const charging = chargingState != null ? chargingState === "charging" : isDockedCharging(entity, batteryPercent);
161180
+ operationalState = charging ? RvcOperationalState4.OperationalState.Charging : RvcOperationalState4.OperationalState.Docked;
161105
161181
  } else if (state === VacuumState.returning) {
161106
161182
  operationalState = RvcOperationalState4.OperationalState.SeekingCharger;
161107
161183
  } else if (cleaningStates.includes(state)) {
@@ -161109,11 +161185,8 @@ function mapVacuumOperationalState(entity, batteryPercent = batteryFromAttribute
161109
161185
  } else if (state === VacuumState.paused) {
161110
161186
  operationalState = RvcOperationalState4.OperationalState.Paused;
161111
161187
  } else if (state === VacuumState.idle) {
161112
- if (isCharging(entity)) {
161113
- operationalState = RvcOperationalState4.OperationalState.Charging;
161114
- } else {
161115
- operationalState = RvcOperationalState4.OperationalState.Stopped;
161116
- }
161188
+ const charging = chargingState != null ? chargingState === "charging" : isCharging(entity);
161189
+ operationalState = charging ? RvcOperationalState4.OperationalState.Charging : RvcOperationalState4.OperationalState.Stopped;
161117
161190
  } else if (state === VacuumState.error || state === "unavailable") {
161118
161191
  operationalState = RvcOperationalState4.OperationalState.Error;
161119
161192
  } else {
@@ -161136,7 +161209,8 @@ var VacuumRvcOperationalStateServer = RvcOperationalStateServer2({
161136
161209
  getOperationalState(entity, agent) {
161137
161210
  return mapVacuumOperationalState(
161138
161211
  entity,
161139
- getVacuumBatteryPercent(entity, agent)
161212
+ getVacuumBatteryPercent(entity, agent),
161213
+ getVacuumChargingState(agent)
161140
161214
  );
161141
161215
  },
161142
161216
  pause: (_, agent) => {
@@ -164306,6 +164380,7 @@ init_dist();
164306
164380
  init_diagnostic_event_bus();
164307
164381
  var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
164308
164382
  var DEAD_SESSION_TIMEOUT_MS2 = 6e4;
164383
+ var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2 = 2500;
164309
164384
  function makeWarmStartState(state, now = (/* @__PURE__ */ new Date()).toISOString()) {
164310
164385
  return { ...state, last_updated: now };
164311
164386
  }
@@ -164469,6 +164544,7 @@ ${e?.toString()}`);
164469
164544
  this.unwireSessionDiagnostics();
164470
164545
  this.cancelWarmStart();
164471
164546
  this.stopAutoForceSync();
164547
+ await this.closeActiveSessions();
164472
164548
  this.endpointManager.stopObserving();
164473
164549
  try {
164474
164550
  await this.server.cancel();
@@ -164538,6 +164614,7 @@ ${e?.toString()}`);
164538
164614
  this.log.info(`Force sync: every ${AUTO_FORCE_SYNC_INTERVAL_MS2 / 1e3}s`);
164539
164615
  }
164540
164616
  wireSessionDiagnostics() {
164617
+ this.unwireSessionDiagnostics();
164541
164618
  try {
164542
164619
  const sessionManager = this.server.env.get(SessionManager);
164543
164620
  this.sessionDiagHandler = (session) => {
@@ -164658,6 +164735,42 @@ ${e?.toString()}`);
164658
164735
  } catch {
164659
164736
  }
164660
164737
  }
164738
+ // Close every active session on shutdown so each controller is told to drop
164739
+ // its CASE session instead of being left with a stale one. Mirrors the
164740
+ // dead-session close, but covers all sessions and waits (capped) so the
164741
+ // close actually reaches the peer before the server is canceled.
164742
+ async closeActiveSessions() {
164743
+ try {
164744
+ const sessionManager = this.server.env.get(SessionManager);
164745
+ const closes = [];
164746
+ for (const s of [...sessionManager.sessions]) {
164747
+ if (s.isClosing) {
164748
+ continue;
164749
+ }
164750
+ closes.push(
164751
+ s.initiateClose().catch(() => {
164752
+ return s.initiateForceClose({
164753
+ cause: new Error("graceful close failed, forcing")
164754
+ });
164755
+ })
164756
+ );
164757
+ }
164758
+ if (closes.length === 0) {
164759
+ return;
164760
+ }
164761
+ this.log.info(`Closing ${closes.length} active session(s) on shutdown`);
164762
+ let timer;
164763
+ const timeout = new Promise((resolve11) => {
164764
+ timer = setTimeout(resolve11, SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2);
164765
+ });
164766
+ try {
164767
+ await Promise.race([Promise.allSettled(closes), timeout]);
164768
+ } finally {
164769
+ clearTimeout(timer);
164770
+ }
164771
+ } catch {
164772
+ }
164773
+ }
164661
164774
  /**
164662
164775
  * Force a fresh mDNS operational advertisement after session cleanup.
164663
164776
  * matter.js DeviceAdvertiser only re-announces when a subscription is
@@ -165883,6 +165996,14 @@ async function startHandler(startOptions, webUiDist) {
165883
165996
  if (shuttingDown) return;
165884
165997
  shuttingDown = true;
165885
165998
  console.log(`Received ${signal}, shutting down gracefully...`);
165999
+ try {
166000
+ await Promise.race([
166001
+ bridgeService.stopAll(),
166002
+ new Promise((resolve11) => setTimeout(resolve11, 1e4))
166003
+ ]);
166004
+ } catch (e) {
166005
+ console.warn("Stopping bridges during shutdown failed:", e);
166006
+ }
165886
166007
  try {
165887
166008
  await Promise.race([
165888
166009
  backupService.createAutoBackup(),