@riddix/hamh 2.1.0-alpha.841 → 2.1.0-alpha.843
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.
|
|
40
|
+
| **Stable** | `main` | v2.0.51 | 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,20 @@ 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.
|
|
64
|
+
<summary><strong>📦 Stable Features (v2.0.51)</strong> - Click to expand</summary>
|
|
65
65
|
|
|
66
|
-
**New in v2.0.
|
|
66
|
+
**New in v2.0.51:**
|
|
67
|
+
|
|
68
|
+
- 🐕 **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
|
+
- 🧹 **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)
|
|
70
|
+
- 🚗 **EV charger support**: new EVSE device-type override with charging switch and current limit mapping, EnableCharging/Disable from the controller; Aqara and Home Assistant render it, keep it off Alexa bridges
|
|
71
|
+
- 🩺 **Subscription scope on the health card**: see whether each controller holds a whole-node wildcard or watches specific endpoints
|
|
72
|
+
- 📷 **Camera live view signaling completed**: the WebRTC answer now reaches the controller over the requestor cluster, plus full media path logging, HA signaling timeouts, trickle ICE and ICE server support (still experimental until verified on real hardware)
|
|
73
|
+
- 🪟 **Cover stop detection in every position space**: the v2.0.50 cover fix now also works with the Alexa percentage flag and the per-cover swap toggle, and swapped covers report the right direction ([#429](https://github.com/RiDDiX/home-assistant-matter-hub/issues/429))
|
|
74
|
+
- 🤖 **Vacuum on/off crash fixed**: state updates no longer throw on vacuums with the on/off toggle ([#428](https://github.com/RiDDiX/home-assistant-matter-hub/issues/428))
|
|
75
|
+
- 🌍 **Brazilian Portuguese completed**: 602 of 615 strings ([#420](https://github.com/RiDDiX/home-assistant-matter-hub/issues/420))
|
|
76
|
+
|
|
77
|
+
**Previously in v2.0.50:**
|
|
67
78
|
|
|
68
79
|
- 🆔 **Stable device identity (opt-in)**: endpoints keep their identity when an entity is renamed or re-registered, keyed on the HA registry unique id; enable the `stableIdentity` feature flag per bridge ([#404](https://github.com/RiDDiX/home-assistant-matter-hub/issues/404), [#407](https://github.com/RiDDiX/home-assistant-matter-hub/issues/407))
|
|
69
80
|
- ⚡ **Energy suite stage 1**: consumption sensors expose an **Electrical Meter** that Google Home and SmartThings render, home batteries become a **Battery Storage** device with signed charge/discharge power and lifetime energy, and voltage/current/energy sensors group onto one endpoint via new mapping fields
|
|
@@ -255,7 +266,7 @@ Re-assign the affected devices to their rooms after they reconnect. See the [doc
|
|
|
255
266
|
<details>
|
|
256
267
|
<summary><strong>🧪 Alpha Features (v2.1.0-alpha.x)</strong> - Click to expand</summary>
|
|
257
268
|
|
|
258
|
-
**Alpha is currently level with Stable (v2.0.
|
|
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.
|
|
259
270
|
|
|
260
271
|
</details>
|
|
261
272
|
|
package/dist/backend/cli.js
CHANGED
|
@@ -136622,6 +136622,7 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136622
136622
|
const result = [];
|
|
136623
136623
|
for (const bridge of bridgeService.bridges) {
|
|
136624
136624
|
const info = bridge.pluginInfo;
|
|
136625
|
+
if (!info) continue;
|
|
136625
136626
|
const plugins = info.metadata.map((meta) => ({
|
|
136626
136627
|
name: meta.name,
|
|
136627
136628
|
version: meta.version,
|
|
@@ -136644,41 +136645,41 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136644
136645
|
}
|
|
136645
136646
|
res.json(result);
|
|
136646
136647
|
});
|
|
136647
|
-
|
|
136648
|
-
const bridge = bridgeService.get(
|
|
136648
|
+
function pluginBridge(bridgeId, res) {
|
|
136649
|
+
const bridge = bridgeService.get(bridgeId);
|
|
136649
136650
|
if (!bridge) {
|
|
136650
136651
|
res.status(404).json({ error: "Bridge not found" });
|
|
136651
|
-
return;
|
|
136652
|
+
return void 0;
|
|
136652
136653
|
}
|
|
136654
|
+
if (typeof bridge.enablePlugin !== "function") {
|
|
136655
|
+
res.status(400).json({ error: "Bridge does not support plugins" });
|
|
136656
|
+
return void 0;
|
|
136657
|
+
}
|
|
136658
|
+
return bridge;
|
|
136659
|
+
}
|
|
136660
|
+
router.post("/:bridgeId/:pluginName/enable", (req, res) => {
|
|
136661
|
+
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
136662
|
+
if (!bridge) return;
|
|
136653
136663
|
const { pluginName } = req.params;
|
|
136654
136664
|
bridge.enablePlugin(pluginName);
|
|
136655
136665
|
res.json({ success: true, pluginName, enabled: true });
|
|
136656
136666
|
});
|
|
136657
136667
|
router.post("/:bridgeId/:pluginName/disable", (req, res) => {
|
|
136658
|
-
const bridge =
|
|
136659
|
-
if (!bridge)
|
|
136660
|
-
res.status(404).json({ error: "Bridge not found" });
|
|
136661
|
-
return;
|
|
136662
|
-
}
|
|
136668
|
+
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
136669
|
+
if (!bridge) return;
|
|
136663
136670
|
const { pluginName } = req.params;
|
|
136664
136671
|
bridge.disablePlugin(pluginName);
|
|
136665
136672
|
res.json({ success: true, pluginName, enabled: false });
|
|
136666
136673
|
});
|
|
136667
136674
|
router.get("/:bridgeId/:pluginName/config-schema", (req, res) => {
|
|
136668
|
-
const bridge =
|
|
136669
|
-
if (!bridge)
|
|
136670
|
-
res.status(404).json({ error: "Bridge not found" });
|
|
136671
|
-
return;
|
|
136672
|
-
}
|
|
136675
|
+
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
136676
|
+
if (!bridge) return;
|
|
136673
136677
|
const schema6 = bridge.getPluginConfigSchema(req.params.pluginName);
|
|
136674
136678
|
res.json({ pluginName: req.params.pluginName, schema: schema6 ?? null });
|
|
136675
136679
|
});
|
|
136676
136680
|
router.post("/:bridgeId/:pluginName/config", async (req, res) => {
|
|
136677
|
-
const bridge =
|
|
136678
|
-
if (!bridge)
|
|
136679
|
-
res.status(404).json({ error: "Bridge not found" });
|
|
136680
|
-
return;
|
|
136681
|
-
}
|
|
136681
|
+
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
136682
|
+
if (!bridge) return;
|
|
136682
136683
|
const { config: config11 } = req.body;
|
|
136683
136684
|
if (!config11 || typeof config11 !== "object") {
|
|
136684
136685
|
res.status(400).json({ error: "config object is required" });
|
|
@@ -136692,11 +136693,8 @@ function pluginApi(bridgeService, storageLocation) {
|
|
|
136692
136693
|
res.json({ success: true, pluginName: req.params.pluginName });
|
|
136693
136694
|
});
|
|
136694
136695
|
router.post("/:bridgeId/:pluginName/reset", (req, res) => {
|
|
136695
|
-
const bridge =
|
|
136696
|
-
if (!bridge)
|
|
136697
|
-
res.status(404).json({ error: "Bridge not found" });
|
|
136698
|
-
return;
|
|
136699
|
-
}
|
|
136696
|
+
const bridge = pluginBridge(req.params.bridgeId, res);
|
|
136697
|
+
if (!bridge) return;
|
|
136700
136698
|
const { pluginName } = req.params;
|
|
136701
136699
|
bridge.resetPlugin(pluginName);
|
|
136702
136700
|
res.json({ success: true, pluginName, reset: true });
|
|
@@ -163276,6 +163274,15 @@ function clearPendingTilt(st) {
|
|
|
163276
163274
|
st.pendingTilt = null;
|
|
163277
163275
|
}
|
|
163278
163276
|
var coverOptimistic = /* @__PURE__ */ new WeakMap();
|
|
163277
|
+
function haRestEnd(action) {
|
|
163278
|
+
if (action.action.includes("open_cover")) {
|
|
163279
|
+
return "open";
|
|
163280
|
+
}
|
|
163281
|
+
if (action.action.includes("close_cover")) {
|
|
163282
|
+
return "close";
|
|
163283
|
+
}
|
|
163284
|
+
return null;
|
|
163285
|
+
}
|
|
163279
163286
|
function getCoverOptimistic(endpoint) {
|
|
163280
163287
|
let st = coverOptimistic.get(endpoint);
|
|
163281
163288
|
if (!st) {
|
|
@@ -163390,7 +163397,10 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163390
163397
|
if (!entry) {
|
|
163391
163398
|
return MovementStatus.Stopped;
|
|
163392
163399
|
}
|
|
163393
|
-
const
|
|
163400
|
+
const getExpected = config11.getExpectedRestPosition;
|
|
163401
|
+
const expectedPercent = entry.expectedRestEnd != null && getExpected != null ? getExpected(entry.expectedRestEnd, this.agent) : null;
|
|
163402
|
+
const goal = expectedPercent != null ? expectedPercent * 100 : existingTarget;
|
|
163403
|
+
const reachedTarget = positionAware && current100ths != null && goal != null && Math.abs(current100ths - goal) < 100;
|
|
163394
163404
|
const expired = Date.now() - entry.startedAt >= optimisticMovementTimeoutMs;
|
|
163395
163405
|
if (reachedTarget || expired) {
|
|
163396
163406
|
clearOptimisticAxis(optimistic, axis);
|
|
@@ -163495,7 +163505,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163495
163505
|
}
|
|
163496
163506
|
// Record the optimistic direction and emit it now so a controller gets the
|
|
163497
163507
|
// moving->stopped edge even when HA never reports a transitional state (#429).
|
|
163498
|
-
startOptimisticMovement(axis, status3) {
|
|
163508
|
+
startOptimisticMovement(axis, status3, expectedRestEnd) {
|
|
163499
163509
|
const optimistic = getCoverOptimistic(this.endpoint);
|
|
163500
163510
|
clearOptimisticAxis(optimistic, axis);
|
|
163501
163511
|
const endpoint = this.endpoint;
|
|
@@ -163540,7 +163550,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163540
163550
|
})();
|
|
163541
163551
|
}, optimisticMovementTimeoutMs);
|
|
163542
163552
|
timer.unref?.();
|
|
163543
|
-
optimistic[axis] = { status: status3, startedAt, timer };
|
|
163553
|
+
optimistic[axis] = { status: status3, startedAt, timer, expectedRestEnd };
|
|
163544
163554
|
this.writeOperationalStatus({ [axis]: status3 });
|
|
163545
163555
|
}
|
|
163546
163556
|
async handleMovement(type, _, direction, targetPercent100ths) {
|
|
@@ -163599,17 +163609,25 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163599
163609
|
}
|
|
163600
163610
|
handleLiftOpen() {
|
|
163601
163611
|
clearPendingLift(getCoverDebounce(this.endpoint));
|
|
163602
|
-
this.startOptimisticMovement("lift", MovementStatus.Opening);
|
|
163603
163612
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163604
163613
|
const action = this.state.config.openCoverLift(void 0, this.agent);
|
|
163614
|
+
this.startOptimisticMovement(
|
|
163615
|
+
"lift",
|
|
163616
|
+
MovementStatus.Opening,
|
|
163617
|
+
haRestEnd(action)
|
|
163618
|
+
);
|
|
163605
163619
|
logger218.info(`handleLiftOpen: calling action=${action.action}`);
|
|
163606
163620
|
homeAssistant.callAction(action);
|
|
163607
163621
|
}
|
|
163608
163622
|
handleLiftClose() {
|
|
163609
163623
|
clearPendingLift(getCoverDebounce(this.endpoint));
|
|
163610
|
-
this.startOptimisticMovement("lift", MovementStatus.Closing);
|
|
163611
163624
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163612
163625
|
const action = this.state.config.closeCoverLift(void 0, this.agent);
|
|
163626
|
+
this.startOptimisticMovement(
|
|
163627
|
+
"lift",
|
|
163628
|
+
MovementStatus.Closing,
|
|
163629
|
+
haRestEnd(action)
|
|
163630
|
+
);
|
|
163613
163631
|
logger218.info(`handleLiftClose: calling action=${action.action}`);
|
|
163614
163632
|
homeAssistant.callAction(action);
|
|
163615
163633
|
}
|
|
@@ -163620,14 +163638,15 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163620
163638
|
return;
|
|
163621
163639
|
}
|
|
163622
163640
|
this.state.targetPositionLiftPercent100ths = targetPercent100ths;
|
|
163623
|
-
this.startOptimisticMovement(
|
|
163624
|
-
"lift",
|
|
163625
|
-
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing
|
|
163626
|
-
);
|
|
163627
163641
|
const targetPosition = targetPercent100ths / 100;
|
|
163628
163642
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163629
163643
|
const action = config11.setLiftPosition(targetPosition, this.agent);
|
|
163630
163644
|
const entityId = homeAssistant.entityId;
|
|
163645
|
+
this.startOptimisticMovement(
|
|
163646
|
+
"lift",
|
|
163647
|
+
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing,
|
|
163648
|
+
haRestEnd(action)
|
|
163649
|
+
);
|
|
163631
163650
|
const actions = this.env.get(HomeAssistantActions);
|
|
163632
163651
|
const st = getCoverDebounce(this.endpoint);
|
|
163633
163652
|
st.pendingLift = { action, entityId, actions };
|
|
@@ -163660,21 +163679,27 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163660
163679
|
const st = getCoverDebounce(this.endpoint);
|
|
163661
163680
|
clearPendingTilt(st);
|
|
163662
163681
|
if (st.pendingLift?.action.action.includes("tilt")) clearPendingLift(st);
|
|
163663
|
-
this.
|
|
163664
|
-
|
|
163665
|
-
|
|
163666
|
-
|
|
163682
|
+
const action = this.state.config.openCoverTilt(void 0, this.agent);
|
|
163683
|
+
this.startOptimisticMovement(
|
|
163684
|
+
"tilt",
|
|
163685
|
+
MovementStatus.Opening,
|
|
163686
|
+
haRestEnd(action)
|
|
163667
163687
|
);
|
|
163688
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163689
|
+
homeAssistant.callAction(action);
|
|
163668
163690
|
}
|
|
163669
163691
|
handleTiltClose() {
|
|
163670
163692
|
const st = getCoverDebounce(this.endpoint);
|
|
163671
163693
|
clearPendingTilt(st);
|
|
163672
163694
|
if (st.pendingLift?.action.action.includes("tilt")) clearPendingLift(st);
|
|
163673
|
-
this.
|
|
163674
|
-
|
|
163675
|
-
|
|
163676
|
-
|
|
163695
|
+
const action = this.state.config.closeCoverTilt(void 0, this.agent);
|
|
163696
|
+
this.startOptimisticMovement(
|
|
163697
|
+
"tilt",
|
|
163698
|
+
MovementStatus.Closing,
|
|
163699
|
+
haRestEnd(action)
|
|
163677
163700
|
);
|
|
163701
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163702
|
+
homeAssistant.callAction(action);
|
|
163678
163703
|
}
|
|
163679
163704
|
handleGoToTiltPosition(targetPercent100ths) {
|
|
163680
163705
|
const config11 = this.state.config;
|
|
@@ -163683,14 +163708,15 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
163683
163708
|
return;
|
|
163684
163709
|
}
|
|
163685
163710
|
this.state.targetPositionTiltPercent100ths = targetPercent100ths;
|
|
163686
|
-
this.startOptimisticMovement(
|
|
163687
|
-
"tilt",
|
|
163688
|
-
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing
|
|
163689
|
-
);
|
|
163690
163711
|
const targetPosition = targetPercent100ths / 100;
|
|
163691
163712
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
163692
163713
|
const action = config11.setTiltPosition(targetPosition, this.agent);
|
|
163693
163714
|
const entityId = homeAssistant.entityId;
|
|
163715
|
+
this.startOptimisticMovement(
|
|
163716
|
+
"tilt",
|
|
163717
|
+
currentPositionMatter != null && targetPercent100ths < currentPositionMatter ? MovementStatus.Opening : MovementStatus.Closing,
|
|
163718
|
+
haRestEnd(action)
|
|
163719
|
+
);
|
|
163694
163720
|
const actions = this.env.get(HomeAssistantActions);
|
|
163695
163721
|
const st = getCoverDebounce(this.endpoint);
|
|
163696
163722
|
st.pendingTilt = { action, entityId, actions };
|
|
@@ -163832,6 +163858,7 @@ var adjustPositionForWriting2 = (position, agent) => {
|
|
|
163832
163858
|
const matterSem = usesMatterSemantics(agent);
|
|
163833
163859
|
return adjustPositionForWriting(position, featureFlags, matterSem);
|
|
163834
163860
|
};
|
|
163861
|
+
var expectedRestPosition = (end, agent) => adjustPositionForReading2(end === "open" ? 100 : 0, agent);
|
|
163835
163862
|
var shouldSwapOpenClose = (agent) => {
|
|
163836
163863
|
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
163837
163864
|
const entitySwap = homeAssistant.state.mapping?.coverSwapOpenClose;
|
|
@@ -163919,11 +163946,11 @@ var config6 = {
|
|
|
163919
163946
|
}
|
|
163920
163947
|
return position == null ? null : adjustPositionForReading2(position, agent);
|
|
163921
163948
|
},
|
|
163949
|
+
getExpectedRestPosition: expectedRestPosition,
|
|
163922
163950
|
getCoverType: (entity) => deviceClassMapping(entity)?.type,
|
|
163923
163951
|
getEndProductType: (entity) => deviceClassMapping(entity)?.endProductType,
|
|
163924
163952
|
getMovementStatus: (entity, agent) => {
|
|
163925
|
-
const
|
|
163926
|
-
const swapped = featureFlags?.coverSwapOpenClose === true;
|
|
163953
|
+
const swapped = shouldSwapOpenClose(agent);
|
|
163927
163954
|
const coverState = entity.state;
|
|
163928
163955
|
if (coverState === CoverDeviceState.opening) {
|
|
163929
163956
|
return swapped ? WindowCovering3.MovementStatus.Closing : WindowCovering3.MovementStatus.Opening;
|