@riddix/hamh 2.1.0-alpha.843 → 2.1.0-alpha.845

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,7 @@ of port forwarding etc.
37
37
 
38
38
  | Channel | Branch | Current Version | Description |
39
39
  |---------|--------|-----------------|-------------|
40
- | **Stable** | `main` | v2.0.51 | Production-ready, recommended for most users |
40
+ | **Stable** | `main` | v2.0.52 | Production-ready, recommended for most users |
41
41
  | **Alpha** | `alpha` | v2.1.0-alpha.x (next) | Currently level with Stable; next pre-release lands here first |
42
42
  | **Testing** | `testing` | v4.1.0-testing.x | ⚠️ **Highly unstable!** Experimental features, may break |
43
43
 
@@ -61,9 +61,13 @@ Re-assign the affected devices to their rooms after they reconnect. See the [doc
61
61
  ## 🎉 What's New
62
62
 
63
63
  <details>
64
- <summary><strong>📦 Stable Features (v2.0.51)</strong> - Click to expand</summary>
64
+ <summary><strong>📦 Stable Features (v2.0.52)</strong> - Click to expand</summary>
65
65
 
66
- **New in v2.0.51:**
66
+ **New in v2.0.52 (hotfix release):**
67
+
68
+ - 🔌 The plugins page no longer fails with a 500 when a server mode bridge exists ([#430](https://github.com/RiDDiX/home-assistant-matter-hub/issues/430))
69
+
70
+ **Previously in v2.0.51:**
67
71
 
68
72
  - 🐕 **Opt-in wedge watchdog**: rotates a controller session that keeps acking but stops talking at the interaction layer, the automated "Play Sound" fix for stuck Apple devices ([#287](https://github.com/RiDDiX/home-assistant-matter-hub/issues/287), [#428](https://github.com/RiDDiX/home-assistant-matter-hub/issues/428))
69
73
  - 🧹 **Clean up orphaned records**: bridge menu action with a dry-run preview that removes identity and mapping leftovers of entities deleted from HA (7 day tombstone guards against false positives)
@@ -266,7 +270,7 @@ Re-assign the affected devices to their rooms after they reconnect. See the [doc
266
270
  <details>
267
271
  <summary><strong>🧪 Alpha Features (v2.1.0-alpha.x)</strong> - Click to expand</summary>
268
272
 
269
- **Alpha is currently level with Stable (v2.0.51).** All alpha work up to the latest pre-release has been promoted into v2.0.51. New alpha work continues from the next pre-release tag onward and will appear here as development progresses.
273
+ **Alpha is currently level with Stable (v2.0.52).** All alpha work up to the latest pre-release has been promoted into v2.0.52. New alpha work continues from the next pre-release tag onward and will appear here as development progresses.
270
274
 
271
275
  </details>
272
276
 
@@ -133183,7 +133183,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133183
133183
  fanWindPresets: config11.fanWindPresets,
133184
133184
  fanRestoreSpeedOnPowerOn: config11.fanRestoreSpeedOnPowerOn,
133185
133185
  composedEntities: config11.composedEntities,
133186
- disableMomentaryFlip: config11.disableMomentaryFlip
133186
+ disableMomentaryFlip: config11.disableMomentaryFlip,
133187
+ vacuumAscendingRoomOrder: config11.vacuumAscendingRoomOrder
133187
133188
  });
133188
133189
  mappingsRestored++;
133189
133190
  }
@@ -133346,7 +133347,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
133346
133347
  customFanSpeedTags: config11.customFanSpeedTags,
133347
133348
  fanWindPresets: config11.fanWindPresets,
133348
133349
  composedEntities: config11.composedEntities,
133349
- disableMomentaryFlip: config11.disableMomentaryFlip
133350
+ disableMomentaryFlip: config11.disableMomentaryFlip,
133351
+ vacuumAscendingRoomOrder: config11.vacuumAscendingRoomOrder
133350
133352
  });
133351
133353
  mappingsRestored++;
133352
133354
  }
@@ -134195,6 +134197,7 @@ function entityMappingApi(mappingStorage, identityStorage) {
134195
134197
  fanRestoreSpeedOnPowerOn: body.fanRestoreSpeedOnPowerOn,
134196
134198
  currentRoomEntity: body.currentRoomEntity,
134197
134199
  cleanedAreaEntity: body.cleanedAreaEntity,
134200
+ vacuumAscendingRoomOrder: body.vacuumAscendingRoomOrder,
134198
134201
  disableCustomAreaRoomModes: body.disableCustomAreaRoomModes,
134199
134202
  valetudoIdentifier: body.valetudoIdentifier,
134200
134203
  coverSwapOpenClose: body.coverSwapOpenClose,
@@ -134704,7 +134707,8 @@ function configToProfileEntry(config11) {
134704
134707
  climateKeepModeOnIdle: config11.climateKeepModeOnIdle,
134705
134708
  climateExposeFan: config11.climateExposeFan,
134706
134709
  climateAutoMode: config11.climateAutoMode,
134707
- disableMomentaryFlip: config11.disableMomentaryFlip
134710
+ disableMomentaryFlip: config11.disableMomentaryFlip,
134711
+ vacuumAscendingRoomOrder: config11.vacuumAscendingRoomOrder
134708
134712
  };
134709
134713
  }
134710
134714
  function mappingProfileApi(mappingStorage) {
@@ -134845,7 +134849,8 @@ function mappingProfileApi(mappingStorage) {
134845
134849
  climateKeepModeOnIdle: entry.climateKeepModeOnIdle,
134846
134850
  climateExposeFan: entry.climateExposeFan,
134847
134851
  climateAutoMode: entry.climateAutoMode,
134848
- disableMomentaryFlip: entry.disableMomentaryFlip
134852
+ disableMomentaryFlip: entry.disableMomentaryFlip,
134853
+ vacuumAscendingRoomOrder: entry.vacuumAscendingRoomOrder
134849
134854
  });
134850
134855
  applied++;
134851
134856
  } catch (e) {
@@ -136243,6 +136248,9 @@ function runDiagnostics(mdnsInterface, mdnsIpv4) {
136243
136248
  import * as nodePath from "node:path";
136244
136249
  import express14 from "express";
136245
136250
 
136251
+ // src/plugins/builtin/names.ts
136252
+ var BUILTIN_PLUGIN_NAMES = ["camera"];
136253
+
136246
136254
  // src/plugins/plugin-installer.ts
136247
136255
  init_esm();
136248
136256
  import { execFile } from "node:child_process";
@@ -136614,6 +136622,17 @@ var BLOCKED_PREFIXES = [
136614
136622
  "/boot",
136615
136623
  "/root"
136616
136624
  ];
136625
+ function packageBaseName(spec) {
136626
+ const name = spec.trim();
136627
+ const at = name.startsWith("@") ? name.lastIndexOf("@") : name.indexOf("@");
136628
+ return at > 0 ? name.slice(0, at) : name;
136629
+ }
136630
+ function builtinPluginName(requested) {
136631
+ const wanted = requested.trim().toLowerCase();
136632
+ return BUILTIN_PLUGIN_NAMES.find(
136633
+ (name) => name.trim().toLowerCase() === wanted
136634
+ );
136635
+ }
136617
136636
  function pluginApi(bridgeService, storageLocation) {
136618
136637
  const router = express14.Router();
136619
136638
  const installer = new PluginInstaller(storageLocation);
@@ -136721,6 +136740,14 @@ function pluginApi(bridgeService, storageLocation) {
136721
136740
  res.status(400).json({ error: "packageName is required" });
136722
136741
  return;
136723
136742
  }
136743
+ const baseName = packageBaseName(packageName);
136744
+ const builtin = builtinPluginName(baseName);
136745
+ if (builtin) {
136746
+ res.status(400).json({
136747
+ error: `"${builtin}" is built in and needs no installation. Enable and configure it on the plugins page.`
136748
+ });
136749
+ return;
136750
+ }
136724
136751
  try {
136725
136752
  const result = await installer.install(packageName);
136726
136753
  if (!result.success) {
@@ -136730,10 +136757,10 @@ function pluginApi(bridgeService, storageLocation) {
136730
136757
  });
136731
136758
  return;
136732
136759
  }
136733
- registry3.add(packageName, config11 ?? {});
136760
+ registry3.add(baseName, config11 ?? {});
136734
136761
  res.json({
136735
136762
  success: true,
136736
- packageName,
136763
+ packageName: baseName,
136737
136764
  version: result.version,
136738
136765
  message: "Plugin installed. Restart the bridge to load it."
136739
136766
  });
@@ -136798,6 +136825,13 @@ function pluginApi(bridgeService, storageLocation) {
136798
136825
  });
136799
136826
  return;
136800
136827
  }
136828
+ const reservedUpload = builtinPluginName(result.packageName);
136829
+ if (reservedUpload) {
136830
+ res.status(400).json({
136831
+ error: `"${reservedUpload}" is built in and needs no installation. Enable and configure it on the plugins page.`
136832
+ });
136833
+ return;
136834
+ }
136801
136835
  registry3.add(result.packageName, {});
136802
136836
  res.json({
136803
136837
  success: true,
@@ -136831,6 +136865,13 @@ function pluginApi(bridgeService, storageLocation) {
136831
136865
  });
136832
136866
  return;
136833
136867
  }
136868
+ const reservedLocal = builtinPluginName(result.packageName);
136869
+ if (reservedLocal) {
136870
+ res.status(400).json({
136871
+ error: `"${reservedLocal}" is built in and needs no installation. Enable and configure it on the plugins page.`
136872
+ });
136873
+ return;
136874
+ }
136834
136875
  registry3.add(result.packageName, {});
136835
136876
  res.json({
136836
136877
  success: true,
@@ -139564,6 +139605,7 @@ var EntityMappingStorage = class extends Service {
139564
139605
  fanRestoreSpeedOnPowerOn: request.fanRestoreSpeedOnPowerOn || void 0,
139565
139606
  currentRoomEntity: request.currentRoomEntity?.trim() || void 0,
139566
139607
  cleanedAreaEntity: request.cleanedAreaEntity?.trim() || void 0,
139608
+ vacuumAscendingRoomOrder: request.vacuumAscendingRoomOrder || void 0,
139567
139609
  disableCustomAreaRoomModes: request.disableCustomAreaRoomModes || void 0,
139568
139610
  valetudoIdentifier: request.valetudoIdentifier?.trim() || void 0,
139569
139611
  coverSwapOpenClose: request.coverSwapOpenClose || void 0,
@@ -139581,7 +139623,7 @@ var EntityMappingStorage = class extends Service {
139581
139623
  composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0,
139582
139624
  disableMomentaryFlip: request.disableMomentaryFlip || void 0
139583
139625
  };
139584
- if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.disableBatteryMapping && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.lockUsercodeService && config11.lockUsercodeSlot === void 0 && config11.lockPinMinLength === void 0 && config11.lockPinMaxLength === void 0 && !config11.powerEntity && !config11.energyEntity && !config11.voltageEntity && !config11.currentEntity && !config11.batteryPowerEntity && !config11.batteryEnergyEntity && !config11.chargingSwitchEntity && !config11.currentLimitEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && (!config11.fanWindPresets || (config11.fanWindPresets.natural?.length ?? 0) === 0 && (config11.fanWindPresets.sleep?.length ?? 0) === 0) && !config11.fanRestoreSpeedOnPowerOn && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.selectExposeAsSwitch && !config11.selectSwitchOnOption && !config11.selectSwitchOffOption && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0) && !config11.disableMomentaryFlip) {
139626
+ if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.disableBatteryMapping && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.lockUsercodeService && config11.lockUsercodeSlot === void 0 && config11.lockPinMinLength === void 0 && config11.lockPinMaxLength === void 0 && !config11.powerEntity && !config11.energyEntity && !config11.voltageEntity && !config11.currentEntity && !config11.batteryPowerEntity && !config11.batteryEnergyEntity && !config11.chargingSwitchEntity && !config11.currentLimitEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && (!config11.fanWindPresets || (config11.fanWindPresets.natural?.length ?? 0) === 0 && (config11.fanWindPresets.sleep?.length ?? 0) === 0) && !config11.fanRestoreSpeedOnPowerOn && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.vacuumAscendingRoomOrder && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.selectExposeAsSwitch && !config11.selectSwitchOnOption && !config11.selectSwitchOffOption && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0) && !config11.disableMomentaryFlip) {
139585
139627
  bridgeMap.delete(request.entityId);
139586
139628
  } else {
139587
139629
  bridgeMap.set(request.entityId, config11);
@@ -159131,7 +159173,7 @@ var DefaultPowerSourceServer = PowerSourceServer2(defaultBatteryConfig);
159131
159173
 
159132
159174
  // src/matter/behaviors/power-topology-server.ts
159133
159175
  var HaPowerTopologyServer = PowerTopologyServer.with(
159134
- PowerTopology3.Feature.NodeTopology
159176
+ PowerTopology3.Feature.TreeTopology
159135
159177
  );
159136
159178
 
159137
159179
  // src/matter/behaviors/temperature-measurement-server.ts
@@ -165085,6 +165127,23 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
165085
165127
  }
165086
165128
  }
165087
165129
  }
165130
+ /**
165131
+ * Snapshot the controller selection in the order the vacuum will actually
165132
+ * clean it. Roborock-class devices clean a batch of segments in ascending
165133
+ * id order and ignore the order they were dispatched in, so tracking has
165134
+ * to follow that instead of the tap order (#368).
165135
+ */
165136
+ orderSelectedAreas(selectedAreas) {
165137
+ const areas = [...selectedAreas];
165138
+ try {
165139
+ const mapping = this.agent.get(HomeAssistantEntityBehavior).state.mapping;
165140
+ if (mapping?.vacuumAscendingRoomOrder) {
165141
+ areas.sort((a, b) => a - b);
165142
+ }
165143
+ } catch {
165144
+ }
165145
+ return areas;
165146
+ }
165088
165147
  /** Read the cumulative cleaned-area sensor (m2), or null if not configured. */
165089
165148
  readCleanedAreaSqm() {
165090
165149
  try {
@@ -165274,7 +165333,10 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
165274
165333
  try {
165275
165334
  const serviceArea = this.agent.get(ServiceAreaBehavior);
165276
165335
  if (serviceArea.state.selectedAreas?.length > 0) {
165277
- s.activeAreas = [...serviceArea.state.selectedAreas];
165336
+ s.activeAreas = this.orderSelectedAreas(
165337
+ serviceArea.state.selectedAreas
165338
+ );
165339
+ serviceArea.state.selectedAreas = [...s.activeAreas];
165278
165340
  s.completedAreas.clear();
165279
165341
  s.lastCurrentArea = null;
165280
165342
  s.loggedShortCircuits.clear();
@@ -165313,7 +165375,10 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
165313
165375
  try {
165314
165376
  const serviceArea = this.agent.get(ServiceAreaBehavior);
165315
165377
  if (serviceArea.state.selectedAreas?.length > 0) {
165316
- s.activeAreas = [...serviceArea.state.selectedAreas];
165378
+ s.activeAreas = this.orderSelectedAreas(
165379
+ serviceArea.state.selectedAreas
165380
+ );
165381
+ serviceArea.state.selectedAreas = [...s.activeAreas];
165317
165382
  s.completedAreas.clear();
165318
165383
  s.lastCurrentArea = null;
165319
165384
  s.loggedShortCircuits.clear();
@@ -167989,7 +168054,8 @@ var ElectricalMeterType = ElectricalMeterDevice.with(
167989
168054
  IdentifyServer2,
167990
168055
  HomeAssistantEntityBehavior,
167991
168056
  PowerServer,
167992
- EnergyServer
168057
+ EnergyServer,
168058
+ HaPowerTopologyServer
167993
168059
  );
167994
168060
 
167995
168061
  // src/matter/endpoints/legacy/sensor/devices/energy-evse.ts
@@ -168257,6 +168323,8 @@ var EnergyEvseBase = EnergyEvseDevice.with(
168257
168323
  // ElectricalSensor device type.
168258
168324
  PowerServer,
168259
168325
  EnergyServer,
168326
+ // ElectricalSensor requires PowerTopology; matter.js does not enforce it.
168327
+ HaPowerTopologyServer,
168260
168328
  DescriptorServer
168261
168329
  ).set({
168262
168330
  descriptor: {
@@ -169992,7 +170060,16 @@ init_esm();
169992
170060
 
169993
170061
  // src/matter/behaviors/service-area-server.ts
169994
170062
  init_esm();
170063
+ init_home_assistant_entity_behavior();
169995
170064
  var logger234 = Logger.get("ServiceAreaServer");
170065
+ function orderSelection(areas, agent) {
170066
+ try {
170067
+ const ascending = agent?.get(HomeAssistantEntityBehavior).state.mapping?.vacuumAscendingRoomOrder === true;
170068
+ return ascending ? [...areas].sort((a, b) => a - b) : areas;
170069
+ } catch {
170070
+ return areas;
170071
+ }
170072
+ }
169996
170073
  var ServiceAreaWithProgress = ServiceAreaBehavior.with(
169997
170074
  ServiceArea3.Feature.ProgressReporting
169998
170075
  );
@@ -170002,7 +170079,7 @@ var ServiceAreaServerBase = class extends ServiceAreaWithProgress {
170002
170079
  logger234.info(
170003
170080
  `ServiceArea selectAreas called with: ${JSON.stringify(newAreas)}`
170004
170081
  );
170005
- const uniqueAreas = [...new Set(newAreas)];
170082
+ const uniqueAreas = orderSelection([...new Set(newAreas)], this.agent);
170006
170083
  const supportedAreaIds = this.state.supportedAreas.map((a) => a.areaId);
170007
170084
  const invalidAreas = uniqueAreas.filter(
170008
170085
  (id) => !supportedAreaIds.includes(id)
@@ -170061,7 +170138,7 @@ var ServiceAreaServerWithMapsBase = class extends ServiceAreaWithMapsAndProgress
170061
170138
  logger234.info(
170062
170139
  `ServiceArea selectAreas called with: ${JSON.stringify(newAreas)}`
170063
170140
  );
170064
- const uniqueAreas = [...new Set(newAreas)];
170141
+ const uniqueAreas = orderSelection([...new Set(newAreas)], this.agent);
170065
170142
  const supportedAreaIds = this.state.supportedAreas.map((a) => a.areaId);
170066
170143
  const invalidAreas = uniqueAreas.filter(
170067
170144
  (id) => !supportedAreaIds.includes(id)
@@ -173885,6 +173962,11 @@ var CameraPlugin = class {
173885
173962
  }
173886
173963
  };
173887
173964
 
173965
+ // src/plugins/builtin/index.ts
173966
+ var BUILTIN_PLUGINS = [
173967
+ CameraPlugin
173968
+ ];
173969
+
173888
173970
  // src/services/bridges/bridge-endpoint-manager.ts
173889
173971
  init_diagnostic_event_bus();
173890
173972
 
@@ -174341,10 +174423,16 @@ var BridgeEndpointManager = class extends Service {
174341
174423
  // matter.js instance and can hand over live EndpointTypes.
174342
174424
  async registerBuiltInPlugins() {
174343
174425
  if (!this.pluginManager) return;
174344
- try {
174345
- await this.pluginManager.registerBuiltIn(new CameraPlugin());
174346
- } catch (e) {
174347
- this.log.warn("Failed to register built-in camera plugin:", e);
174426
+ for (const Plugin of BUILTIN_PLUGINS) {
174427
+ const plugin = new Plugin();
174428
+ try {
174429
+ await this.pluginManager.registerBuiltIn(plugin);
174430
+ } catch (e) {
174431
+ this.log.warn(
174432
+ `Failed to register built-in plugin "${plugin.name}":`,
174433
+ e
174434
+ );
174435
+ }
174348
174436
  }
174349
174437
  }
174350
174438
  async loadRegisteredPlugins() {