@riddix/hamh 2.1.0-alpha.806 → 2.1.0-alpha.808

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.48 | Production-ready, recommended for most users |
40
+ | **Stable** | `main` | v2.0.49 | 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,24 @@ 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.48)</strong> - Click to expand</summary>
64
+ <summary><strong>📦 Stable Features (v2.0.49)</strong> - Click to expand</summary>
65
65
 
66
- **New in v2.0.48:**
66
+ **New in v2.0.49:**
67
+
68
+ - 🌀 **Fan speed restore**: speed is remembered across transactions and restarts, localized wind presets map through, Auto is gated on a real auto preset, and an opt-in restores speed on power-on ([#387](https://github.com/RiDDiX/home-assistant-matter-hub/issues/387))
69
+ - 📡 **Session recovery hardening**: subscription jitter dropped, superseded sessions of a reconnecting peer swept, stale sessions closed only after a real quiet period, plus opt-in fast recovery ([#386](https://github.com/RiDDiX/home-assistant-matter-hub/issues/386), [#400](https://github.com/RiDDiX/home-assistant-matter-hub/issues/400), [#398](https://github.com/RiDDiX/home-assistant-matter-hub/issues/398), [#287](https://github.com/RiDDiX/home-assistant-matter-hub/issues/287))
70
+ - 🩺 **Per-fabric controller health card** on the bridge page
71
+ - 🔢 **Endpoint number kept on mapping change** so Alexa no longer re-adds the device ([#404](https://github.com/RiDDiX/home-assistant-matter-hub/issues/404)), plus a **unique-id suffix** to shed stale controller records ([#385](https://github.com/RiDDiX/home-assistant-matter-hub/issues/385))
72
+ - 🔘 **`select` as an On/Off Switch** ([#356](https://github.com/RiDDiX/home-assistant-matter-hub/issues/356)), plus an **experimental mounted On/Off** control type ([#380](https://github.com/RiDDiX/home-assistant-matter-hub/issues/380))
73
+ - 🪟 **Tilt for `set_tilt_position`-only covers** ([#405](https://github.com/RiDDiX/home-assistant-matter-hub/issues/405)), and **tilt-only cover-as-light** uses the tilt channel ([#350](https://github.com/RiDDiX/home-assistant-matter-hub/issues/350))
74
+ - 🚨 **Warns on a non-5540 Alexa bridge** ([#401](https://github.com/RiDDiX/home-assistant-matter-hub/issues/401)) and on an **OTBR Thread mDNS interface** ([#388](https://github.com/RiDDiX/home-assistant-matter-hub/issues/388))
75
+ - 📷 **Camera plugin config scoped per bridge** ([#373](https://github.com/RiDDiX/home-assistant-matter-hub/issues/373))
76
+ - 🔌 **Clashing bridge port reassigned on load** ([#378](https://github.com/RiDDiX/home-assistant-matter-hub/issues/378))
77
+ - 🧩 **Composed sub-entities compose even outside the bridge filter** ([#408](https://github.com/RiDDiX/home-assistant-matter-hub/issues/408)), and **composed sensors list all device types** ([#214](https://github.com/RiDDiX/home-assistant-matter-hub/issues/214))
78
+ - 🔒 **Unbolt maps to `lock.unlock`** not open ([#397](https://github.com/RiDDiX/home-assistant-matter-hub/issues/397)), **level-to-brightness uses one scale** ([#402](https://github.com/RiDDiX/home-assistant-matter-hub/issues/402)), **button drops the Lighting feature** ([#182](https://github.com/RiDDiX/home-assistant-matter-hub/issues/182)), and **stale cooling/auto state is cleared** ([#384](https://github.com/RiDDiX/home-assistant-matter-hub/issues/384))
79
+ - 🌍 **Russian translations** added ([#409](https://github.com/RiDDiX/home-assistant-matter-hub/issues/409)) and **zh-TW completed**
80
+
81
+ **Previously in v2.0.48:**
67
82
 
68
83
  - 🌡️ Cooling-only thermostats no longer drop and re-pair after a switch to cool; a leftover Heat setpoint limit was crashing init ([#381](https://github.com/RiDDiX/home-assistant-matter-hub/issues/381))
69
84
  - 📝 The Settings update box now shows the full release notes instead of cutting them off
@@ -220,7 +235,7 @@ Re-assign the affected devices to their rooms after they reconnect. See the [doc
220
235
  <details>
221
236
  <summary><strong>🧪 Alpha Features (v2.1.0-alpha.x)</strong> - Click to expand</summary>
222
237
 
223
- **Alpha is currently level with Stable (v2.0.46).** All alpha work up to the latest pre-release has been promoted into v2.0.46. New alpha work continues from the next pre-release tag onward and will appear here as development progresses.
238
+ **Alpha is currently level with Stable (v2.0.49).** All alpha work up to the latest pre-release has been promoted into v2.0.49. New alpha work continues from the next pre-release tag onward and will appear here as development progresses.
224
239
 
225
240
  </details>
226
241
 
@@ -498,7 +513,7 @@ Matter relies heavily on **mDNS (multicast DNS)** for device discovery and reach
498
513
  - **Keep the path simple**: Avoid placing access points or managed switches between your Matter bridge (Home Assistant) and your Home Hub (HomePod/Apple TV)
499
514
  - **Use wired connections** where possible for Home Hubs and the Home Assistant host
500
515
  - **Same subnet**: All Matter devices, controllers, and the bridge must be on the same Layer 2 network / subnet
501
- - **IPv6**: Matter requires IPv6, do not disable it. For VLAN setups, configure **ULA addresses** (`fd00::/8`), not just link-local (`fe80::`). See [Troubleshooting](https://home-assistant-matter-hub.riddix.dev/guides/connectivity-issues#ipv6) and [Discussion #39](https://github.com/RiDDiX/home-assistant-matter-hub/discussions/39)
516
+ - **IPv6**: Matter requires IPv6, do not disable it. For VLAN setups, configure **ULA addresses** (`fd00::/8`), not just link-local (`fe80::`). See [Troubleshooting](https://riddix.github.io/home-assistant-matter-hub/guides/connectivity-issues#ipv6) and [Discussion #39](https://github.com/RiDDiX/home-assistant-matter-hub/discussions/39)
502
517
 
503
518
  </details>
504
519
 
@@ -162514,6 +162514,9 @@ function createLegacyEndpointType(entity, mapping, areaName, options) {
162514
162514
  const overrideFactory = matterDeviceTypeFactories[mapping.matterDeviceType];
162515
162515
  if (overrideFactory) {
162516
162516
  type = overrideFactory({ entity, customName, mapping });
162517
+ if (type && mapping.batteryEntity && !hasPowerSource(type)) {
162518
+ type = addPowerSource(type);
162519
+ }
162517
162520
  }
162518
162521
  }
162519
162522
  if (!type) {
@@ -162564,6 +162567,16 @@ function addFixedLabel(type, areaName) {
162564
162567
  behaviors: { ...type.behaviors, fixedLabel }
162565
162568
  };
162566
162569
  }
162570
+ function hasPowerSource(type) {
162571
+ return "powerSource" in type.behaviors;
162572
+ }
162573
+ function addPowerSource(type) {
162574
+ const mutable = type;
162575
+ if (typeof mutable.with === "function") {
162576
+ return mutable.with(DefaultPowerSourceServer);
162577
+ }
162578
+ return type;
162579
+ }
162567
162580
  var deviceCtrs = {
162568
162581
  light: LightDevice,
162569
162582
  switch: SwitchDevice,
@@ -162680,6 +162693,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
162680
162693
  mappedEntityIds;
162681
162694
  subEndpoints = /* @__PURE__ */ new Map();
162682
162695
  lastStates = /* @__PURE__ */ new Map();
162696
+ lastMappedStates = /* @__PURE__ */ new Map();
162683
162697
  debouncedUpdates = /* @__PURE__ */ new Map();
162684
162698
  static async create(config11) {
162685
162699
  const { registry: registry2, primaryEntityId, composedEntities } = config11;
@@ -162698,14 +162712,32 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
162698
162712
  })
162699
162713
  );
162700
162714
  }
162715
+ if (config11.mapping?.batteryEntity) {
162716
+ parentType = parentType.with(DefaultPowerSourceServer);
162717
+ }
162701
162718
  const endpointId = createEndpointId5(primaryEntityId, config11.customName);
162702
162719
  const parts = [];
162703
162720
  const subEndpointMap = /* @__PURE__ */ new Map();
162704
162721
  const mappedIds = [];
162722
+ if (config11.mapping?.batteryEntity) {
162723
+ mappedIds.push(config11.mapping.batteryEntity);
162724
+ }
162725
+ let primaryMapping = config11.mapping;
162726
+ let primarySubPayload = primaryPayload;
162727
+ if (config11.mapping?.batteryEntity) {
162728
+ primaryMapping = { ...config11.mapping, batteryEntity: void 0 };
162729
+ const attributes9 = { ...primaryPayload.state.attributes };
162730
+ delete attributes9.battery;
162731
+ delete attributes9.battery_level;
162732
+ primarySubPayload = {
162733
+ ...primaryPayload,
162734
+ state: { ...primaryPayload.state, attributes: attributes9 }
162735
+ };
162736
+ }
162705
162737
  const primaryType = createLegacyEndpointType(
162706
- primaryPayload,
162707
- config11.mapping,
162708
- void 0,
162738
+ primarySubPayload,
162739
+ primaryMapping,
162740
+ config11.areaName,
162709
162741
  { vacuumOnOff: registry2.isVacuumOnOffEnabled() }
162710
162742
  );
162711
162743
  if (!primaryType) {
@@ -162733,7 +162765,11 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
162733
162765
  entityId: sub.entityId,
162734
162766
  matterDeviceType: sub.matterDeviceType
162735
162767
  };
162736
- const subType = createLegacyEndpointType(subPayload, subMapping);
162768
+ const subType = createLegacyEndpointType(
162769
+ subPayload,
162770
+ subMapping,
162771
+ config11.areaName
162772
+ );
162737
162773
  if (!subType) {
162738
162774
  logger229.warn(
162739
162775
  `Cannot create endpoint type for composed sub-entity ${sub.entityId}`
@@ -162782,12 +162818,29 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
162782
162818
  this.mappedEntityIds = mappedEntityIds;
162783
162819
  }
162784
162820
  async updateStates(states) {
162785
- this.scheduleUpdate(this, this.entityId, states);
162821
+ const mappedChanged = this.hasMappedNonSubEntityChanged(states);
162822
+ this.scheduleUpdate(this, this.entityId, states, mappedChanged);
162786
162823
  for (const [entityId, sub] of this.subEndpoints) {
162787
162824
  this.scheduleUpdate(sub, entityId, states);
162788
162825
  }
162789
162826
  }
162790
- scheduleUpdate(endpoint, entityId, states) {
162827
+ // A mapped entity without its own sub-endpoint (the battery) drives the parent
162828
+ // PowerSource, so track its state and report when it moves. Subs flush
162829
+ // themselves, so skip them here.
162830
+ hasMappedNonSubEntityChanged(states) {
162831
+ let changed = false;
162832
+ for (const mappedId of this.mappedEntityIds) {
162833
+ if (this.subEndpoints.has(mappedId)) continue;
162834
+ const mappedState = states[mappedId];
162835
+ if (!mappedState) continue;
162836
+ if (mappedState.state !== this.lastMappedStates.get(mappedId)) {
162837
+ this.lastMappedStates.set(mappedId, mappedState.state);
162838
+ changed = true;
162839
+ }
162840
+ }
162841
+ return changed;
162842
+ }
162843
+ scheduleUpdate(endpoint, entityId, states, force = false) {
162791
162844
  const state = states[entityId];
162792
162845
  if (!state) return;
162793
162846
  const key = endpoint === this ? `_parent_:${entityId}` : entityId;
@@ -162795,8 +162848,9 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
162795
162848
  s: state.state,
162796
162849
  a: state.attributes
162797
162850
  });
162798
- if (this.lastStates.get(key) === stateJson) return;
162851
+ if (!force && this.lastStates.get(key) === stateJson) return;
162799
162852
  this.lastStates.set(key, stateJson);
162853
+ const effectiveState = force ? { ...state, last_updated: (/* @__PURE__ */ new Date()).toISOString() } : state;
162800
162854
  let debouncedFn = this.debouncedUpdates.get(key);
162801
162855
  if (!debouncedFn) {
162802
162856
  debouncedFn = debounce5(
@@ -162805,7 +162859,7 @@ var UserComposedEndpoint = class _UserComposedEndpoint extends Endpoint {
162805
162859
  );
162806
162860
  this.debouncedUpdates.set(key, debouncedFn);
162807
162861
  }
162808
- debouncedFn(endpoint, state);
162862
+ debouncedFn(endpoint, effectiveState);
162809
162863
  }
162810
162864
  async flushUpdate(endpoint, state) {
162811
162865
  try {