@riddix/hamh 2.0.29 → 2.0.31
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 +19 -17
- package/dist/backend/cli.js +250 -72
- package/dist/backend/cli.js.map +4 -4
- package/dist/frontend/assets/{index-Dx3aKlw5.js → index-g6UDNEhe.js} +173 -173
- package/dist/frontend/index.html +1 -1
- package/package.json +4 -4
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.31 | Production-ready, recommended for most users |
|
|
41
41
|
| **Alpha** | `alpha` | v2.1.0-alpha.x | Pre-release with new features, for early adopters |
|
|
42
42
|
| **Testing** | `testing` | v4.1.0-testing.x | ⚠️ **Highly unstable!** Experimental features, may break |
|
|
43
43
|
|
|
@@ -52,36 +52,31 @@ of port forwarding etc.
|
|
|
52
52
|
## 🎉 What's New
|
|
53
53
|
|
|
54
54
|
<details>
|
|
55
|
-
<summary><strong>📦 Stable Features (v2.0.
|
|
55
|
+
<summary><strong>📦 Stable Features (v2.0.31)</strong> - Click to expand</summary>
|
|
56
56
|
|
|
57
|
-
**New in v2.0.
|
|
57
|
+
**New in v2.0.31:**
|
|
58
58
|
|
|
59
59
|
| Feature | Description |
|
|
60
60
|
|---------|-------------|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
61
|
+
| **🎮 Controller Profiles & Area Setup** | New bridge wizard with controller-specific profiles (Apple Home, Google Home, Alexa) and area-based bridge setup |
|
|
62
|
+
| **🌀 Fan Speed/Preset Fix** | Fix fan speed and preset control not working from Matter controllers ([#233](https://github.com/RiDDiX/home-assistant-matter-hub/issues/233)) |
|
|
63
|
+
| **💡 Optimistic State Fix** | Prevent stale HA state from reverting optimistic brightness/color updates ([#230](https://github.com/RiDDiX/home-assistant-matter-hub/issues/230)) |
|
|
64
|
+
| **🪟 Cover Target Fix** | Route boundary cover targets to open/close regardless of direction ([#240](https://github.com/RiDDiX/home-assistant-matter-hub/issues/240)) |
|
|
65
|
+
| **�️ Humidity Auto-Mapping Default** | Make autoHumidityMapping default-enabled like autoPressureMapping |
|
|
65
66
|
|
|
66
|
-
**Previously in v2.0.
|
|
67
|
+
**Previously in v2.0.30:**
|
|
67
68
|
|
|
68
69
|
| Feature | Description |
|
|
69
70
|
|---------|-------------|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
| **📺 TV Source Selection** | MediaInput cluster added to VideoPlayerDevice for input switching ([#231](https://github.com/RiDDiX/home-assistant-matter-hub/issues/231)) |
|
|
73
|
-
| **🔀 Reverse Proxy Base Path** | `--http-base-path` option for subfolder reverse proxy support ([#228](https://github.com/RiDDiX/home-assistant-matter-hub/issues/228)) |
|
|
74
|
-
| **🌀 On/Off-Only Fans** | Fans without speed control correctly use OnOffPlugInUnit ([#229](https://github.com/RiDDiX/home-assistant-matter-hub/issues/229)) |
|
|
75
|
-
| **💡 Light Brightness Fix** | Prevent brightness reset on turn-on by setting onLevel to null ([#225](https://github.com/RiDDiX/home-assistant-matter-hub/issues/225)) |
|
|
76
|
-
| **🌀 Fan Speed Fixes** | speedMax cap raised from 10 to 100 (Matter spec max), retain speed when off ([#225](https://github.com/RiDDiX/home-assistant-matter-hub/issues/225)) |
|
|
77
|
-
| **🌡️ Composed Air Purifier Fix** | Flatten to single endpoint for correct Apple Home primary tile ([#218](https://github.com/RiDDiX/home-assistant-matter-hub/issues/218)) |
|
|
71
|
+
| **🔗 Mapped Entity Propagation Fix** | Propagate mapped entity changes (battery, humidity, etc.) to Matter endpoints — fixes stale sensor readings |
|
|
72
|
+
| **🖥️ API Error Surfacing** | Surface API errors instead of silently swallowing them ([#232](https://github.com/RiDDiX/home-assistant-matter-hub/issues/232)) |
|
|
78
73
|
|
|
79
74
|
</details>
|
|
80
75
|
|
|
81
76
|
<details>
|
|
82
77
|
<summary><strong>🧪 Alpha Features (v2.1.0-alpha.x)</strong> - Click to expand</summary>
|
|
83
78
|
|
|
84
|
-
**Alpha is currently in sync with Stable (v2.0.
|
|
79
|
+
**Alpha is currently in sync with Stable (v2.0.31).** All alpha features have been promoted to stable. New alpha features will appear here as development continues.
|
|
85
80
|
|
|
86
81
|
</details>
|
|
87
82
|
|
|
@@ -107,6 +102,12 @@ of port forwarding etc.
|
|
|
107
102
|
<details>
|
|
108
103
|
<summary><strong>📜 Previous Stable Versions</strong> - Click to expand</summary>
|
|
109
104
|
|
|
105
|
+
### v2.0.30
|
|
106
|
+
Mapped Entity Propagation Fix, API Error Surfacing
|
|
107
|
+
|
|
108
|
+
### v2.0.29
|
|
109
|
+
Light currentLevel Fix, Bridge Config Save Fix, Fan Device Feature Fix, Humidity Auto-Mapping Fix
|
|
110
|
+
|
|
110
111
|
### v2.0.28
|
|
111
112
|
Device Image Support, Custom Fan Speed Mapping, TV Source Selection, Reverse Proxy Base Path, On/Off-Only Fans, Light Brightness Fix, Fan Speed Fixes, Composed Air Purifier Fix, Dreame Multi-Floor Fix, Optimistic State Updates, Frontend Improvements
|
|
112
113
|
|
|
@@ -445,6 +446,7 @@ Thank you to everyone who helps improve this project by reporting issues!
|
|
|
445
446
|
| [@JRCondat](https://github.com/JRCondat) | 💎 Thank you for your generous support! |
|
|
446
447
|
| Bonjon | 💎 Thank you for your generous support! |
|
|
447
448
|
| TobiR | 💎 Thank you for your generous support! |
|
|
449
|
+
| [@d3rby91](https://github.com/d3rby91) | 💎 Thank you for your generous support! |
|
|
448
450
|
| *Anonymous supporters* | 🙏 Thank you to those who prefer not to be named - your support is equally appreciated! |
|
|
449
451
|
|
|
450
452
|
### 🌟 Original Author
|
package/dist/backend/cli.js
CHANGED
|
@@ -146085,6 +146085,13 @@ var init_clusters2 = __esm({
|
|
|
146085
146085
|
}
|
|
146086
146086
|
});
|
|
146087
146087
|
|
|
146088
|
+
// ../common/dist/controller-profiles.js
|
|
146089
|
+
var init_controller_profiles = __esm({
|
|
146090
|
+
"../common/dist/controller-profiles.js"() {
|
|
146091
|
+
"use strict";
|
|
146092
|
+
}
|
|
146093
|
+
});
|
|
146094
|
+
|
|
146088
146095
|
// ../common/dist/diagnostic-event.js
|
|
146089
146096
|
var init_diagnostic_event = __esm({
|
|
146090
146097
|
"../common/dist/diagnostic-event.js"() {
|
|
@@ -146694,7 +146701,7 @@ var init_bridge_config_schema = __esm({
|
|
|
146694
146701
|
title: "Auto Humidity Mapping",
|
|
146695
146702
|
description: "Automatically combine humidity sensors with temperature sensors from the same Home Assistant device. When enabled, humidity sensors will be merged into temperature sensors to create combined TemperatureHumiditySensor devices.",
|
|
146696
146703
|
type: "boolean",
|
|
146697
|
-
default:
|
|
146704
|
+
default: true
|
|
146698
146705
|
},
|
|
146699
146706
|
autoPressureMapping: {
|
|
146700
146707
|
title: "Auto Pressure Mapping",
|
|
@@ -146999,6 +147006,7 @@ var init_dist = __esm({
|
|
|
146999
147006
|
init_bridge_export();
|
|
147000
147007
|
init_bridge_templates();
|
|
147001
147008
|
init_clusters2();
|
|
147009
|
+
init_controller_profiles();
|
|
147002
147010
|
init_diagnostic_event();
|
|
147003
147011
|
init_domains();
|
|
147004
147012
|
init_endpoint_data();
|
|
@@ -149030,7 +149038,8 @@ function matterApi(bridgeService, haRegistry) {
|
|
|
149030
149038
|
const body = req.body;
|
|
149031
149039
|
const isValid = ajv.validate(createBridgeRequestSchema, body);
|
|
149032
149040
|
if (!isValid) {
|
|
149033
|
-
|
|
149041
|
+
const details = ajv.errors?.map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ") ?? "Unknown";
|
|
149042
|
+
res.status(400).json({ error: `Validation failed: ${details}` });
|
|
149034
149043
|
} else {
|
|
149035
149044
|
try {
|
|
149036
149045
|
const bridge = await bridgeService.create(body);
|
|
@@ -149071,7 +149080,8 @@ function matterApi(bridgeService, haRegistry) {
|
|
|
149071
149080
|
const body = req.body;
|
|
149072
149081
|
const isValid = ajv.validate(updateBridgeRequestSchema, body);
|
|
149073
149082
|
if (!isValid) {
|
|
149074
|
-
|
|
149083
|
+
const details = ajv.errors?.map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ") ?? "Unknown";
|
|
149084
|
+
res.status(400).json({ error: `Validation failed: ${details}` });
|
|
149075
149085
|
} else if (bridgeId !== body.id) {
|
|
149076
149086
|
res.status(400).send("Path variable `bridgeId` does not match `body.id`");
|
|
149077
149087
|
} else {
|
|
@@ -149302,6 +149312,73 @@ function matterApi(bridgeService, haRegistry) {
|
|
|
149302
149312
|
areas.sort((a, b) => a.name.localeCompare(b.name));
|
|
149303
149313
|
res.status(200).json(areas);
|
|
149304
149314
|
});
|
|
149315
|
+
router.get("/areas/summary", async (_, res) => {
|
|
149316
|
+
if (!haRegistry) {
|
|
149317
|
+
res.status(503).json({ error: "Home Assistant registry not available" });
|
|
149318
|
+
return;
|
|
149319
|
+
}
|
|
149320
|
+
const supportedDomains = /* @__PURE__ */ new Set([
|
|
149321
|
+
"light",
|
|
149322
|
+
"switch",
|
|
149323
|
+
"sensor",
|
|
149324
|
+
"binary_sensor",
|
|
149325
|
+
"climate",
|
|
149326
|
+
"cover",
|
|
149327
|
+
"fan",
|
|
149328
|
+
"lock",
|
|
149329
|
+
"media_player",
|
|
149330
|
+
"vacuum",
|
|
149331
|
+
"valve",
|
|
149332
|
+
"humidifier",
|
|
149333
|
+
"water_heater",
|
|
149334
|
+
"select",
|
|
149335
|
+
"input_select",
|
|
149336
|
+
"input_boolean",
|
|
149337
|
+
"alarm_control_panel",
|
|
149338
|
+
"event",
|
|
149339
|
+
"automation",
|
|
149340
|
+
"script",
|
|
149341
|
+
"scene"
|
|
149342
|
+
]);
|
|
149343
|
+
const entities = Object.values(haRegistry.entities);
|
|
149344
|
+
const devices = haRegistry.devices;
|
|
149345
|
+
const states = haRegistry.states;
|
|
149346
|
+
const areaSummary = /* @__PURE__ */ new Map();
|
|
149347
|
+
for (const [areaId, areaName] of haRegistry.areas) {
|
|
149348
|
+
areaSummary.set(areaId, { name: areaName, entityCount: 0, domains: {} });
|
|
149349
|
+
}
|
|
149350
|
+
for (const entity of entities) {
|
|
149351
|
+
if (entity.disabled_by != null) continue;
|
|
149352
|
+
const domain = entity.entity_id.split(".")[0];
|
|
149353
|
+
if (!supportedDomains.has(domain)) continue;
|
|
149354
|
+
const state = states[entity.entity_id];
|
|
149355
|
+
if (!state || state.state === "unavailable") continue;
|
|
149356
|
+
let areaId;
|
|
149357
|
+
const entityAreaId = entity.area_id;
|
|
149358
|
+
if (entityAreaId && haRegistry.areas.has(entityAreaId)) {
|
|
149359
|
+
areaId = entityAreaId;
|
|
149360
|
+
} else {
|
|
149361
|
+
const device = entity.device_id ? devices[entity.device_id] : void 0;
|
|
149362
|
+
const deviceAreaId = device?.area_id;
|
|
149363
|
+
if (deviceAreaId && haRegistry.areas.has(deviceAreaId)) {
|
|
149364
|
+
areaId = deviceAreaId;
|
|
149365
|
+
}
|
|
149366
|
+
}
|
|
149367
|
+
if (!areaId) continue;
|
|
149368
|
+
const summary = areaSummary.get(areaId);
|
|
149369
|
+
if (summary) {
|
|
149370
|
+
summary.entityCount++;
|
|
149371
|
+
summary.domains[domain] = (summary.domains[domain] || 0) + 1;
|
|
149372
|
+
}
|
|
149373
|
+
}
|
|
149374
|
+
const result = [...areaSummary.entries()].map(([area_id, data]) => ({
|
|
149375
|
+
area_id,
|
|
149376
|
+
name: data.name,
|
|
149377
|
+
entityCount: data.entityCount,
|
|
149378
|
+
domains: data.domains
|
|
149379
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
149380
|
+
res.status(200).json(result);
|
|
149381
|
+
});
|
|
149305
149382
|
router.get("/filter-values", async (_, res) => {
|
|
149306
149383
|
if (!haRegistry) {
|
|
149307
149384
|
res.status(503).json({ error: "Home Assistant registry not available" });
|
|
@@ -164463,15 +164540,45 @@ import debounce4 from "debounce";
|
|
|
164463
164540
|
// src/matter/endpoints/entity-endpoint.ts
|
|
164464
164541
|
init_esm7();
|
|
164465
164542
|
var EntityEndpoint = class extends Endpoint {
|
|
164466
|
-
constructor(type, entityId, customName) {
|
|
164543
|
+
constructor(type, entityId, customName, mappedEntityIds) {
|
|
164467
164544
|
super(type, { id: createEndpointId(entityId, customName) });
|
|
164468
164545
|
this.entityId = entityId;
|
|
164546
|
+
this.mappedEntityIds = mappedEntityIds ?? [];
|
|
164547
|
+
}
|
|
164548
|
+
mappedEntityIds;
|
|
164549
|
+
lastMappedStates = {};
|
|
164550
|
+
hasMappedEntityChanged(states) {
|
|
164551
|
+
let changed = false;
|
|
164552
|
+
for (const mappedId of this.mappedEntityIds) {
|
|
164553
|
+
const mappedState = states[mappedId];
|
|
164554
|
+
if (!mappedState) continue;
|
|
164555
|
+
const fp = mappedState.state;
|
|
164556
|
+
if (fp !== this.lastMappedStates[mappedId]) {
|
|
164557
|
+
this.lastMappedStates[mappedId] = fp;
|
|
164558
|
+
changed = true;
|
|
164559
|
+
}
|
|
164560
|
+
}
|
|
164561
|
+
return changed;
|
|
164469
164562
|
}
|
|
164470
164563
|
};
|
|
164471
164564
|
function createEndpointId(entityId, customName) {
|
|
164472
164565
|
const baseName = customName || entityId;
|
|
164473
164566
|
return baseName.replace(/\./g, "_").replace(/\s+/g, "_");
|
|
164474
164567
|
}
|
|
164568
|
+
function getMappedEntityIds(mapping) {
|
|
164569
|
+
if (!mapping) return [];
|
|
164570
|
+
const ids = [];
|
|
164571
|
+
if (mapping.batteryEntity) ids.push(mapping.batteryEntity);
|
|
164572
|
+
if (mapping.humidityEntity) ids.push(mapping.humidityEntity);
|
|
164573
|
+
if (mapping.pressureEntity) ids.push(mapping.pressureEntity);
|
|
164574
|
+
if (mapping.cleaningModeEntity) ids.push(mapping.cleaningModeEntity);
|
|
164575
|
+
if (mapping.suctionLevelEntity) ids.push(mapping.suctionLevelEntity);
|
|
164576
|
+
if (mapping.mopIntensityEntity) ids.push(mapping.mopIntensityEntity);
|
|
164577
|
+
if (mapping.filterLifeEntity) ids.push(mapping.filterLifeEntity);
|
|
164578
|
+
if (mapping.powerEntity) ids.push(mapping.powerEntity);
|
|
164579
|
+
if (mapping.energyEntity) ids.push(mapping.energyEntity);
|
|
164580
|
+
return ids;
|
|
164581
|
+
}
|
|
164475
164582
|
|
|
164476
164583
|
// src/matter/endpoints/composed/composed-air-purifier-endpoint.ts
|
|
164477
164584
|
init_dist();
|
|
@@ -165901,12 +166008,8 @@ var FanControlServerBase = class extends FeaturedBase3 {
|
|
|
165901
166008
|
return;
|
|
165902
166009
|
}
|
|
165903
166010
|
this.agent.asLocalActor(() => {
|
|
165904
|
-
const
|
|
165905
|
-
this.
|
|
165906
|
-
percentSetting,
|
|
165907
|
-
this.state.percentSetting,
|
|
165908
|
-
context
|
|
165909
|
-
);
|
|
166011
|
+
const percentage = Math.floor(speed / this.state.speedMax * 100);
|
|
166012
|
+
this.applyPercentageAction(percentage);
|
|
165910
166013
|
});
|
|
165911
166014
|
}
|
|
165912
166015
|
targetFanModeChanged(fanMode, _oldValue, context) {
|
|
@@ -165919,16 +166022,12 @@ var FanControlServerBase = class extends FeaturedBase3 {
|
|
|
165919
166022
|
return;
|
|
165920
166023
|
}
|
|
165921
166024
|
const targetFanMode = FanMode.create(fanMode, this.state.fanModeSequence);
|
|
165922
|
-
const config10 = this.state.config;
|
|
165923
166025
|
if (targetFanMode.mode === FanControl3.FanMode.Auto) {
|
|
165924
|
-
homeAssistant.callAction(
|
|
165925
|
-
|
|
165926
|
-
const percentage = targetFanMode.speedPercent();
|
|
165927
|
-
this.targetPercentSettingChanged(
|
|
165928
|
-
percentage,
|
|
165929
|
-
this.state.percentSetting,
|
|
165930
|
-
context
|
|
166026
|
+
homeAssistant.callAction(
|
|
166027
|
+
this.state.config.setAutoMode(void 0, this.agent)
|
|
165931
166028
|
);
|
|
166029
|
+
} else {
|
|
166030
|
+
this.applyPercentageAction(targetFanMode.speedPercent());
|
|
165932
166031
|
}
|
|
165933
166032
|
});
|
|
165934
166033
|
}
|
|
@@ -165940,45 +166039,48 @@ var FanControlServerBase = class extends FeaturedBase3 {
|
|
|
165940
166039
|
return;
|
|
165941
166040
|
}
|
|
165942
166041
|
this.agent.asLocalActor(() => {
|
|
165943
|
-
|
|
165944
|
-
|
|
165945
|
-
|
|
165946
|
-
|
|
165947
|
-
|
|
165948
|
-
|
|
166042
|
+
this.applyPercentageAction(percentage);
|
|
166043
|
+
});
|
|
166044
|
+
}
|
|
166045
|
+
applyPercentageAction(percentage) {
|
|
166046
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
166047
|
+
if (!homeAssistant.isAvailable) {
|
|
166048
|
+
return;
|
|
166049
|
+
}
|
|
166050
|
+
const config10 = this.state.config;
|
|
166051
|
+
const supportsPercentage = config10.supportsPercentage(
|
|
166052
|
+
homeAssistant.entity.state,
|
|
166053
|
+
this.agent
|
|
166054
|
+
);
|
|
166055
|
+
if (percentage === 0) {
|
|
166056
|
+
homeAssistant.callAction(config10.turnOff(void 0, this.agent));
|
|
166057
|
+
} else if (supportsPercentage) {
|
|
166058
|
+
const stepSize = config10.getStepSize(
|
|
165949
166059
|
homeAssistant.entity.state,
|
|
165950
166060
|
this.agent
|
|
165951
166061
|
);
|
|
165952
|
-
|
|
165953
|
-
|
|
165954
|
-
|
|
165955
|
-
|
|
165956
|
-
|
|
165957
|
-
|
|
165958
|
-
|
|
165959
|
-
|
|
165960
|
-
|
|
165961
|
-
|
|
165962
|
-
|
|
166062
|
+
const roundedPercentage = stepSize && stepSize > 0 ? Math.round(percentage / stepSize) * stepSize : percentage;
|
|
166063
|
+
const clampedPercentage = Math.max(
|
|
166064
|
+
stepSize ?? 1,
|
|
166065
|
+
Math.min(100, roundedPercentage)
|
|
166066
|
+
);
|
|
166067
|
+
homeAssistant.callAction(config10.turnOn(clampedPercentage, this.agent));
|
|
166068
|
+
} else {
|
|
166069
|
+
const presetModes = config10.getPresetModes(homeAssistant.entity.state, this.agent) ?? [];
|
|
166070
|
+
const speedPresets = presetModes.filter(
|
|
166071
|
+
(m) => m.toLowerCase() !== "auto"
|
|
166072
|
+
);
|
|
166073
|
+
if (speedPresets.length > 0) {
|
|
166074
|
+
const presetIndex = Math.min(
|
|
166075
|
+
Math.floor(percentage / 100 * speedPresets.length),
|
|
166076
|
+
speedPresets.length - 1
|
|
165963
166077
|
);
|
|
165964
|
-
|
|
165965
|
-
|
|
165966
|
-
|
|
165967
|
-
const speedPresets = presetModes.filter(
|
|
165968
|
-
(m) => m.toLowerCase() !== "auto"
|
|
166078
|
+
const targetPreset = speedPresets[presetIndex];
|
|
166079
|
+
homeAssistant.callAction(
|
|
166080
|
+
config10.setPresetMode(targetPreset, this.agent)
|
|
165969
166081
|
);
|
|
165970
|
-
if (speedPresets.length > 0) {
|
|
165971
|
-
const presetIndex = Math.min(
|
|
165972
|
-
Math.floor(percentage / 100 * speedPresets.length),
|
|
165973
|
-
speedPresets.length - 1
|
|
165974
|
-
);
|
|
165975
|
-
const targetPreset = speedPresets[presetIndex];
|
|
165976
|
-
homeAssistant.callAction(
|
|
165977
|
-
config10.setPresetMode(targetPreset, this.agent)
|
|
165978
|
-
);
|
|
165979
|
-
}
|
|
165980
166082
|
}
|
|
165981
|
-
}
|
|
166083
|
+
}
|
|
165982
166084
|
}
|
|
165983
166085
|
targetAirflowDirectionChanged(airflowDirection, _oldValue, context) {
|
|
165984
166086
|
if (transactionIsOffline(context)) {
|
|
@@ -166118,6 +166220,8 @@ init_home_assistant_entity_behavior();
|
|
|
166118
166220
|
init_esm();
|
|
166119
166221
|
init_home_assistant_entity_behavior();
|
|
166120
166222
|
var lastTurnOnTimestamps = /* @__PURE__ */ new Map();
|
|
166223
|
+
var optimisticLevelTimestamps = /* @__PURE__ */ new Map();
|
|
166224
|
+
var OPTIMISTIC_LEVEL_COOLDOWN_MS = 2e3;
|
|
166121
166225
|
function notifyLightTurnedOn(entityId) {
|
|
166122
166226
|
lastTurnOnTimestamps.set(entityId, Date.now());
|
|
166123
166227
|
}
|
|
@@ -166162,6 +166266,11 @@ var LevelControlServerBase = class extends FeaturedBase4 {
|
|
|
166162
166266
|
if (currentLevel != null) {
|
|
166163
166267
|
currentLevel = Math.min(Math.max(minLevel, currentLevel), maxLevel);
|
|
166164
166268
|
}
|
|
166269
|
+
const lastOptimistic = optimisticLevelTimestamps.get(entity.entity_id);
|
|
166270
|
+
const inCooldown = lastOptimistic != null && Date.now() - lastOptimistic < OPTIMISTIC_LEVEL_COOLDOWN_MS;
|
|
166271
|
+
if (inCooldown && currentLevel != null) {
|
|
166272
|
+
currentLevel = null;
|
|
166273
|
+
}
|
|
166165
166274
|
applyPatchState(this.state, {
|
|
166166
166275
|
minLevel,
|
|
166167
166276
|
maxLevel,
|
|
@@ -166229,6 +166338,7 @@ var LevelControlServerBase = class extends FeaturedBase4 {
|
|
|
166229
166338
|
};
|
|
166230
166339
|
}
|
|
166231
166340
|
this.state.currentLevel = level;
|
|
166341
|
+
optimisticLevelTimestamps.set(entityId, Date.now());
|
|
166232
166342
|
homeAssistant.callAction(action);
|
|
166233
166343
|
}
|
|
166234
166344
|
};
|
|
@@ -168606,9 +168716,9 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
168606
168716
|
`handleMovement: type=${MovementType[type]}, direction=${MovementDirection[direction]}, target=${targetPercent100ths}, currentLift=${currentLift}, currentTilt=${currentTilt}, absolutePosition=${this.features.absolutePosition}`
|
|
168607
168717
|
);
|
|
168608
168718
|
if (type === MovementType.Lift) {
|
|
168609
|
-
if (
|
|
168719
|
+
if (targetPercent100ths === 0) {
|
|
168610
168720
|
this.handleLiftOpen();
|
|
168611
|
-
} else if (
|
|
168721
|
+
} else if (targetPercent100ths === 1e4) {
|
|
168612
168722
|
this.handleLiftClose();
|
|
168613
168723
|
} else if (targetPercent100ths != null && this.features.absolutePosition) {
|
|
168614
168724
|
this.handleGoToLiftPosition(targetPercent100ths);
|
|
@@ -168618,9 +168728,9 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
168618
168728
|
this.handleLiftClose();
|
|
168619
168729
|
}
|
|
168620
168730
|
} else if (type === MovementType.Tilt) {
|
|
168621
|
-
if (
|
|
168731
|
+
if (targetPercent100ths === 0) {
|
|
168622
168732
|
this.handleTiltOpen();
|
|
168623
|
-
} else if (
|
|
168733
|
+
} else if (targetPercent100ths === 1e4) {
|
|
168624
168734
|
this.handleTiltClose();
|
|
168625
168735
|
} else if (targetPercent100ths != null && this.features.absolutePosition) {
|
|
168626
168736
|
this.handleGoToTiltPosition(targetPercent100ths);
|
|
@@ -169317,6 +169427,8 @@ init_nodejs();
|
|
|
169317
169427
|
// src/matter/behaviors/color-control-server.ts
|
|
169318
169428
|
init_home_assistant_entity_behavior();
|
|
169319
169429
|
var logger170 = Logger.get("ColorControlServer");
|
|
169430
|
+
var optimisticColorTimestamps = /* @__PURE__ */ new Map();
|
|
169431
|
+
var OPTIMISTIC_COLOR_COOLDOWN_MS = 2e3;
|
|
169320
169432
|
var FeaturedBase7 = ColorControlServer.with("ColorTemperature", "HueSaturation");
|
|
169321
169433
|
var ColorControlServerBase = class extends FeaturedBase7 {
|
|
169322
169434
|
pendingTransitionTime;
|
|
@@ -169395,6 +169507,8 @@ var ColorControlServerBase = class extends FeaturedBase7 {
|
|
|
169395
169507
|
const newColorMode = this.getColorModeFromFeatures(
|
|
169396
169508
|
config10.getCurrentMode(entity.state, this.agent)
|
|
169397
169509
|
);
|
|
169510
|
+
const lastOptimistic = optimisticColorTimestamps.get(entity.entity_id);
|
|
169511
|
+
const inCooldown = lastOptimistic != null && Date.now() - lastOptimistic < OPTIMISTIC_COLOR_COOLDOWN_MS;
|
|
169398
169512
|
if (this.features.colorTemperature) {
|
|
169399
169513
|
const existingMireds = this.state.colorTemperatureMireds;
|
|
169400
169514
|
if (existingMireds != null) {
|
|
@@ -169419,12 +169533,12 @@ var ColorControlServerBase = class extends FeaturedBase7 {
|
|
|
169419
169533
|
applyPatchState(this.state, {
|
|
169420
169534
|
coupleColorTempToLevelMinMireds: minMireds,
|
|
169421
169535
|
startUpColorTemperatureMireds: startUpMireds,
|
|
169422
|
-
colorTemperatureMireds: effectiveMireds
|
|
169536
|
+
...inCooldown ? {} : { colorTemperatureMireds: effectiveMireds }
|
|
169423
169537
|
});
|
|
169424
169538
|
}
|
|
169425
169539
|
applyPatchState(this.state, {
|
|
169426
|
-
colorMode: newColorMode,
|
|
169427
|
-
...this.features.hueSaturation ? {
|
|
169540
|
+
...inCooldown ? {} : { colorMode: newColorMode },
|
|
169541
|
+
...this.features.hueSaturation && !inCooldown ? {
|
|
169428
169542
|
currentHue: hue,
|
|
169429
169543
|
currentSaturation: saturation
|
|
169430
169544
|
} : {}
|
|
@@ -169451,6 +169565,7 @@ var ColorControlServerBase = class extends FeaturedBase7 {
|
|
|
169451
169565
|
colorTemperatureMireds: targetMireds,
|
|
169452
169566
|
colorMode: ColorControl3.ColorMode.ColorTemperatureMireds
|
|
169453
169567
|
});
|
|
169568
|
+
optimisticColorTimestamps.set(homeAssistant.entityId, Date.now());
|
|
169454
169569
|
homeAssistant.callAction(action);
|
|
169455
169570
|
}
|
|
169456
169571
|
moveToHueLogic(targetHue) {
|
|
@@ -169481,6 +169596,7 @@ var ColorControlServerBase = class extends FeaturedBase7 {
|
|
|
169481
169596
|
currentSaturation: targetSaturation,
|
|
169482
169597
|
colorMode: ColorControl3.ColorMode.CurrentHueAndCurrentSaturation
|
|
169483
169598
|
});
|
|
169599
|
+
optimisticColorTimestamps.set(homeAssistant.entityId, Date.now());
|
|
169484
169600
|
homeAssistant.callAction(action);
|
|
169485
169601
|
}
|
|
169486
169602
|
applyTransition(action) {
|
|
@@ -170784,6 +170900,8 @@ init_home_assistant_entity_behavior();
|
|
|
170784
170900
|
init_esm();
|
|
170785
170901
|
init_home_assistant_entity_behavior();
|
|
170786
170902
|
var logger175 = Logger.get("SpeakerLevelControlServer");
|
|
170903
|
+
var optimisticLevelTimestamps2 = /* @__PURE__ */ new Map();
|
|
170904
|
+
var OPTIMISTIC_LEVEL_COOLDOWN_MS2 = 2e3;
|
|
170787
170905
|
var FeaturedBase9 = LevelControlServer.with("OnOff");
|
|
170788
170906
|
var SpeakerLevelControlServerBase = class extends FeaturedBase9 {
|
|
170789
170907
|
async initialize() {
|
|
@@ -170819,10 +170937,15 @@ var SpeakerLevelControlServerBase = class extends FeaturedBase9 {
|
|
|
170819
170937
|
logger175.debug(
|
|
170820
170938
|
`[${entityId}] Volume update: HA=${currentLevelPercent != null ? Math.round(currentLevelPercent * 100) : "null"}% -> currentLevel=${currentLevel}`
|
|
170821
170939
|
);
|
|
170940
|
+
const lastOptimistic = optimisticLevelTimestamps2.get(entity.entity_id);
|
|
170941
|
+
const inCooldown = lastOptimistic != null && Date.now() - lastOptimistic < OPTIMISTIC_LEVEL_COOLDOWN_MS2;
|
|
170942
|
+
if (inCooldown && currentLevel != null) {
|
|
170943
|
+
currentLevel = null;
|
|
170944
|
+
}
|
|
170822
170945
|
applyPatchState(this.state, {
|
|
170823
170946
|
minLevel,
|
|
170824
170947
|
maxLevel,
|
|
170825
|
-
currentLevel
|
|
170948
|
+
...currentLevel != null ? { currentLevel } : {}
|
|
170826
170949
|
});
|
|
170827
170950
|
}
|
|
170828
170951
|
async moveToLevel(request) {
|
|
@@ -170865,6 +170988,7 @@ var SpeakerLevelControlServerBase = class extends FeaturedBase9 {
|
|
|
170865
170988
|
return;
|
|
170866
170989
|
}
|
|
170867
170990
|
this.state.currentLevel = level;
|
|
170991
|
+
optimisticLevelTimestamps2.set(entityId, Date.now());
|
|
170868
170992
|
homeAssistant.callAction(
|
|
170869
170993
|
config10.moveToLevelPercent(levelPercent, this.agent)
|
|
170870
170994
|
);
|
|
@@ -174790,10 +174914,11 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
174790
174914
|
return;
|
|
174791
174915
|
}
|
|
174792
174916
|
const customName = effectiveMapping?.customName;
|
|
174793
|
-
|
|
174917
|
+
const mappedIds = getMappedEntityIds(effectiveMapping);
|
|
174918
|
+
return new _LegacyEndpoint(type, entityId, customName, mappedIds);
|
|
174794
174919
|
}
|
|
174795
|
-
constructor(type, entityId, customName) {
|
|
174796
|
-
super(type, entityId, customName);
|
|
174920
|
+
constructor(type, entityId, customName, mappedEntityIds) {
|
|
174921
|
+
super(type, entityId, customName, mappedEntityIds);
|
|
174797
174922
|
this.flushUpdate = debounce4(this.flushPendingUpdate.bind(this), 50);
|
|
174798
174923
|
}
|
|
174799
174924
|
lastState;
|
|
@@ -174804,9 +174929,15 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
|
|
|
174804
174929
|
}
|
|
174805
174930
|
async updateStates(states) {
|
|
174806
174931
|
const state = states[this.entityId] ?? {};
|
|
174807
|
-
|
|
174932
|
+
const mappedChanged = this.hasMappedEntityChanged(states);
|
|
174933
|
+
if (!mappedChanged && state.state === this.lastState?.state && JSON.stringify(state.attributes) === JSON.stringify(this.lastState?.attributes)) {
|
|
174808
174934
|
return;
|
|
174809
174935
|
}
|
|
174936
|
+
if (mappedChanged) {
|
|
174937
|
+
logger189.debug(
|
|
174938
|
+
`Mapped entity change detected for ${this.entityId}, forcing update`
|
|
174939
|
+
);
|
|
174940
|
+
}
|
|
174810
174941
|
logger189.debug(
|
|
174811
174942
|
`State update received for ${this.entityId}: state=${state.state}`
|
|
174812
174943
|
);
|
|
@@ -175105,12 +175236,26 @@ var BridgeEndpointManager = class extends Service {
|
|
|
175105
175236
|
if (!this.entityIds.length) {
|
|
175106
175237
|
return;
|
|
175107
175238
|
}
|
|
175239
|
+
const subscriptionIds = this.collectSubscriptionEntityIds();
|
|
175108
175240
|
this.unsubscribe = subscribeEntities(
|
|
175109
175241
|
this.client.connection,
|
|
175110
175242
|
(e) => this.updateStates(e),
|
|
175111
|
-
|
|
175243
|
+
subscriptionIds
|
|
175112
175244
|
);
|
|
175113
175245
|
}
|
|
175246
|
+
collectSubscriptionEntityIds() {
|
|
175247
|
+
const ids = new Set(this.entityIds);
|
|
175248
|
+
const endpoints = this.root.parts.map((p) => p);
|
|
175249
|
+
for (const endpoint of endpoints) {
|
|
175250
|
+
const mappedIds = endpoint.mappedEntityIds;
|
|
175251
|
+
if (mappedIds) {
|
|
175252
|
+
for (const mappedId of mappedIds) {
|
|
175253
|
+
ids.add(mappedId);
|
|
175254
|
+
}
|
|
175255
|
+
}
|
|
175256
|
+
}
|
|
175257
|
+
return [...ids];
|
|
175258
|
+
}
|
|
175114
175259
|
stopObserving() {
|
|
175115
175260
|
this.unsubscribe?.();
|
|
175116
175261
|
this.unsubscribe = void 0;
|
|
@@ -175235,6 +175380,7 @@ var BridgeEndpointManager = class extends Service {
|
|
|
175235
175380
|
}
|
|
175236
175381
|
}
|
|
175237
175382
|
async updateStates(states) {
|
|
175383
|
+
this.registry.mergeExternalStates(states);
|
|
175238
175384
|
const endpoints = this.root.parts.map((p) => p);
|
|
175239
175385
|
const results = await Promise.allSettled(
|
|
175240
175386
|
endpoints.map((endpoint) => endpoint.updateStates(states))
|
|
@@ -175391,15 +175537,15 @@ var BridgeRegistry = class _BridgeRegistry {
|
|
|
175391
175537
|
}
|
|
175392
175538
|
/**
|
|
175393
175539
|
* Check if auto humidity mapping is enabled for this bridge.
|
|
175394
|
-
* Default:
|
|
175540
|
+
* Default: true (enabled by default).
|
|
175395
175541
|
* When enabled, humidity sensors on the same device as a temperature sensor
|
|
175396
175542
|
* are combined into a single TemperatureHumiditySensor endpoint.
|
|
175397
175543
|
* Note: Apple Home does not display humidity on TemperatureSensorDevice
|
|
175398
|
-
* endpoints, so users on Apple Home should
|
|
175544
|
+
* endpoints, so users on Apple Home should explicitly disable this.
|
|
175399
175545
|
* See: https://github.com/RiDDiX/home-assistant-matter-hub/issues/133
|
|
175400
175546
|
*/
|
|
175401
175547
|
isAutoHumidityMappingEnabled() {
|
|
175402
|
-
return this.dataProvider.featureFlags?.autoHumidityMapping
|
|
175548
|
+
return this.dataProvider.featureFlags?.autoHumidityMapping !== false || this.dataProvider.featureFlags?.autoComposedDevices === true;
|
|
175403
175549
|
}
|
|
175404
175550
|
/**
|
|
175405
175551
|
* Find a humidity sensor entity that belongs to the same HA device.
|
|
@@ -175705,6 +175851,12 @@ var BridgeRegistry = class _BridgeRegistry {
|
|
|
175705
175851
|
isEnergyEntityUsed(entityId) {
|
|
175706
175852
|
return this._usedEnergyEntities.has(entityId);
|
|
175707
175853
|
}
|
|
175854
|
+
mergeExternalStates(states) {
|
|
175855
|
+
const registryStates = this.registry.states;
|
|
175856
|
+
for (const entityId of Object.keys(states)) {
|
|
175857
|
+
registryStates[entityId] = states[entityId];
|
|
175858
|
+
}
|
|
175859
|
+
}
|
|
175708
175860
|
/**
|
|
175709
175861
|
* Get the area name for an entity, resolving from HA area registry.
|
|
175710
175862
|
* Priority: entity area_id > device area_id > undefined
|
|
@@ -176401,12 +176553,18 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
176401
176553
|
if (!endpointType) {
|
|
176402
176554
|
return void 0;
|
|
176403
176555
|
}
|
|
176404
|
-
|
|
176556
|
+
const mappedIds = getMappedEntityIds(effectiveMapping);
|
|
176557
|
+
return new _ServerModeVacuumEndpoint(
|
|
176558
|
+
endpointType,
|
|
176559
|
+
entityId,
|
|
176560
|
+
customName,
|
|
176561
|
+
mappedIds
|
|
176562
|
+
);
|
|
176405
176563
|
}
|
|
176406
176564
|
lastState;
|
|
176407
176565
|
flushUpdate;
|
|
176408
|
-
constructor(type, entityId, customName) {
|
|
176409
|
-
super(type, entityId, customName);
|
|
176566
|
+
constructor(type, entityId, customName, mappedEntityIds) {
|
|
176567
|
+
super(type, entityId, customName, mappedEntityIds);
|
|
176410
176568
|
this.flushUpdate = debounce5(this.flushPendingUpdate.bind(this), 50);
|
|
176411
176569
|
}
|
|
176412
176570
|
async delete() {
|
|
@@ -176415,9 +176573,15 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
176415
176573
|
}
|
|
176416
176574
|
async updateStates(states) {
|
|
176417
176575
|
const state = states[this.entityId] ?? {};
|
|
176418
|
-
|
|
176576
|
+
const mappedChanged = this.hasMappedEntityChanged(states);
|
|
176577
|
+
if (!mappedChanged && state.state === this.lastState?.state && JSON.stringify(state.attributes) === JSON.stringify(this.lastState?.attributes)) {
|
|
176419
176578
|
return;
|
|
176420
176579
|
}
|
|
176580
|
+
if (mappedChanged) {
|
|
176581
|
+
logger192.debug(
|
|
176582
|
+
`Mapped entity change detected for ${this.entityId}, forcing update`
|
|
176583
|
+
);
|
|
176584
|
+
}
|
|
176421
176585
|
logger192.debug(
|
|
176422
176586
|
`State update received for ${this.entityId}: state=${state.state}`
|
|
176423
176587
|
);
|
|
@@ -176498,12 +176662,25 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
176498
176662
|
if (!this.entityIds.length) {
|
|
176499
176663
|
return;
|
|
176500
176664
|
}
|
|
176665
|
+
const subscriptionIds = this.collectSubscriptionEntityIds();
|
|
176501
176666
|
this.unsubscribe = subscribeEntities(
|
|
176502
176667
|
this.client.connection,
|
|
176503
176668
|
(e) => this.updateStates(e),
|
|
176504
|
-
|
|
176669
|
+
subscriptionIds
|
|
176505
176670
|
);
|
|
176506
176671
|
}
|
|
176672
|
+
collectSubscriptionEntityIds() {
|
|
176673
|
+
const ids = new Set(this.entityIds);
|
|
176674
|
+
if (this.deviceEndpoint) {
|
|
176675
|
+
const mappedIds = this.deviceEndpoint.mappedEntityIds;
|
|
176676
|
+
if (mappedIds) {
|
|
176677
|
+
for (const mappedId of mappedIds) {
|
|
176678
|
+
ids.add(mappedId);
|
|
176679
|
+
}
|
|
176680
|
+
}
|
|
176681
|
+
}
|
|
176682
|
+
return [...ids];
|
|
176683
|
+
}
|
|
176507
176684
|
stopObserving() {
|
|
176508
176685
|
this.unsubscribe?.();
|
|
176509
176686
|
this.unsubscribe = void 0;
|
|
@@ -176616,6 +176793,7 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
176616
176793
|
}
|
|
176617
176794
|
}
|
|
176618
176795
|
async updateStates(states) {
|
|
176796
|
+
this.registry.mergeExternalStates(states);
|
|
176619
176797
|
if (this.deviceEndpoint) {
|
|
176620
176798
|
try {
|
|
176621
176799
|
await this.deviceEndpoint.updateStates(states);
|