@riddix/hamh 2.0.42 → 2.0.44
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 +81 -37
- package/dist/backend/cli.js +899 -383
- package/dist/frontend/assets/index-DFWoxgED.js +469 -0
- package/dist/frontend/index.html +1 -1
- package/package.json +4 -4
- package/dist/frontend/assets/index-COXfpHPr.js +0 -473
package/dist/backend/cli.js
CHANGED
|
@@ -145734,6 +145734,9 @@ var init_home_assistant_filter = __esm({
|
|
|
145734
145734
|
HomeAssistantMatcherType2["Label"] = "label";
|
|
145735
145735
|
HomeAssistantMatcherType2["EntityLabel"] = "entity_label";
|
|
145736
145736
|
HomeAssistantMatcherType2["DeviceLabel"] = "device_label";
|
|
145737
|
+
HomeAssistantMatcherType2["EntityLabelRegex"] = "entity_label_regex";
|
|
145738
|
+
HomeAssistantMatcherType2["DeviceLabelRegex"] = "device_label_regex";
|
|
145739
|
+
HomeAssistantMatcherType2["AnyFieldRegex"] = "any_field_regex";
|
|
145737
145740
|
HomeAssistantMatcherType2["Area"] = "area";
|
|
145738
145741
|
HomeAssistantMatcherType2["EntityCategory"] = "entity_category";
|
|
145739
145742
|
HomeAssistantMatcherType2["DeviceName"] = "device_name";
|
|
@@ -146125,6 +146128,7 @@ var init_clusters2 = __esm({
|
|
|
146125
146128
|
ClusterId3["pm10ConcentrationMeasurement"] = "pm10ConcentrationMeasurement";
|
|
146126
146129
|
ClusterId3["pm25ConcentrationMeasurement"] = "pm25ConcentrationMeasurement";
|
|
146127
146130
|
ClusterId3["powerSource"] = "powerSource";
|
|
146131
|
+
ClusterId3["powerTopology"] = "powerTopology";
|
|
146128
146132
|
ClusterId3["pressureMeasurement"] = "pressureMeasurement";
|
|
146129
146133
|
ClusterId3["relativeHumidityMeasurement"] = "relativeHumidityMeasurement";
|
|
146130
146134
|
ClusterId3["smokeCoAlarm"] = "smokeCoAlarm";
|
|
@@ -146638,7 +146642,7 @@ var init_bridge_config_schema = __esm({
|
|
|
146638
146642
|
{
|
|
146639
146643
|
const: "regex",
|
|
146640
146644
|
title: "regex",
|
|
146641
|
-
description: "
|
|
146645
|
+
description: "Regex tested against the entity_id only (e.g. 'light.kitchen_lamp'). For labels use entity_label_regex or device_label_regex. Example: '^(light|switch)\\.kitchen_.*'."
|
|
146642
146646
|
},
|
|
146643
146647
|
{
|
|
146644
146648
|
const: "domain",
|
|
@@ -146665,6 +146669,21 @@ var init_bridge_config_schema = __esm({
|
|
|
146665
146669
|
title: "device_label",
|
|
146666
146670
|
description: "Matches ALL entities of a device if the device has this label. Use this to include a complete device with all its entities."
|
|
146667
146671
|
},
|
|
146672
|
+
{
|
|
146673
|
+
const: "entity_label_regex",
|
|
146674
|
+
title: "entity_label_regex",
|
|
146675
|
+
description: "Regex tested against entity-label slugs and display names. Matches if any label assigned to the entity matches. Example: '^(matter|voice).*'."
|
|
146676
|
+
},
|
|
146677
|
+
{
|
|
146678
|
+
const: "device_label_regex",
|
|
146679
|
+
title: "device_label_regex",
|
|
146680
|
+
description: "Regex tested against device-label slugs and display names. Matches ALL entities of a device whose label matches. Example: '^(matter|voice).*'."
|
|
146681
|
+
},
|
|
146682
|
+
{
|
|
146683
|
+
const: "any_field_regex",
|
|
146684
|
+
title: "any_field_regex",
|
|
146685
|
+
description: "Regex tested against a single-line key=value haystack covering entity_id, domain, platform, area, entity_category, device_class, entity_labels, entity_label_names, device_labels, device_label_names, device_name, product_name. Use lookaheads for AND, alternation for OR. Example: '(?=.*domain=light)(?=.*area=living_room)|(?=.*domain=switch)(?=.*entity_labels=.*\\bvoice\\b)'."
|
|
146686
|
+
},
|
|
146668
146687
|
{
|
|
146669
146688
|
const: "area",
|
|
146670
146689
|
title: "area",
|
|
@@ -146781,7 +146800,7 @@ var init_bridge_config_schema = __esm({
|
|
|
146781
146800
|
},
|
|
146782
146801
|
autoComposedDevices: {
|
|
146783
146802
|
title: "Auto Composed Devices",
|
|
146784
|
-
description: "Master toggle: combine related entities from the same Home Assistant device into a single Matter endpoint. Turns on battery, humidity, pressure, power, and energy auto-mapping at once
|
|
146803
|
+
description: "Master toggle: combine related entities from the same Home Assistant device into a single Matter endpoint. Turns on battery, humidity, pressure, power, and energy auto-mapping at once, a Shelly Plug shows up as one device with power monitoring instead of several siblings.",
|
|
146785
146804
|
type: "boolean",
|
|
146786
146805
|
default: false
|
|
146787
146806
|
},
|
|
@@ -146799,13 +146818,13 @@ var init_bridge_config_schema = __esm({
|
|
|
146799
146818
|
},
|
|
146800
146819
|
preferEntityRegistryName: {
|
|
146801
146820
|
title: "Prefer Entity Registry Name (HA 2026.4 workaround)",
|
|
146802
|
-
description: "Use the entity registry name (or original_name) as nodeLabel instead of the composed friendly_name. Since Home Assistant 2026.4, friendly_name is prefixed with the device name, which breaks voice commands that relied on the short entity name. Resolution order: customName \u2192 registry name \u2192 registry original_name \u2192 friendly_name \u2192 entity_id. Matter has no alias concept
|
|
146821
|
+
description: "Use the entity registry name (or original_name) as nodeLabel instead of the composed friendly_name. Since Home Assistant 2026.4, friendly_name is prefixed with the device name, which breaks voice commands that relied on the short entity name. Resolution order: customName \u2192 registry name \u2192 registry original_name \u2192 friendly_name \u2192 entity_id. Matter has no alias concept, this only changes which single name is reported.",
|
|
146803
146822
|
type: "boolean",
|
|
146804
146823
|
default: false
|
|
146805
146824
|
},
|
|
146806
146825
|
vacuumOnOff: {
|
|
146807
146826
|
title: "Vacuum: Include OnOff Cluster (Alexa)",
|
|
146808
|
-
description: "Add an OnOff cluster to robot vacuum endpoints. Alexa REQUIRES this (PowerController) to show robotic vacuums in the app. Without it, Alexa commissions the device but never displays it. In Server Mode this is enabled automatically
|
|
146827
|
+
description: "Add an OnOff cluster to robot vacuum endpoints. Alexa REQUIRES this (PowerController) to show robotic vacuums in the app. Without it, Alexa commissions the device but never displays it. In Server Mode this is enabled automatically, only check this for bridge mode. WARNING: OnOff is NOT part of the Matter RVC device type specification. Enabling this may break Apple Home (shows 'Updating') and Google Home.",
|
|
146809
146828
|
type: "boolean"
|
|
146810
146829
|
},
|
|
146811
146830
|
alexaPreserveBrightnessOnTurnOn: {
|
|
@@ -146813,6 +146832,20 @@ var init_bridge_config_schema = __esm({
|
|
|
146813
146832
|
description: "Workaround for Alexa resetting light brightness to 100% after subscription renewal. When enabled, the bridge ignores brightness commands that set lights to 100% within 200ms of a turn-on command for the same light. WARNING: breaks Apple Home's 'set room to 100%' Siri commands, which use the same on() + moveToLevel(254) pattern. Only enable on Alexa-only bridges.",
|
|
146814
146833
|
type: "boolean",
|
|
146815
146834
|
default: false
|
|
146835
|
+
},
|
|
146836
|
+
useHaRegistrySerial: {
|
|
146837
|
+
title: "Use HA Registry Serial Number",
|
|
146838
|
+
description: "Fall back to the Home Assistant device registry serial_number when no per-entity customSerialNumber is configured. Default off because changing serialNumber after commissioning can confuse controllers. A per-entity customSerialNumber still takes precedence.",
|
|
146839
|
+
type: "boolean",
|
|
146840
|
+
default: false
|
|
146841
|
+
},
|
|
146842
|
+
coverSliderDebounceMs: {
|
|
146843
|
+
title: "Cover Slider Debounce (ms)",
|
|
146844
|
+
description: "Override the cover position-update debounce window for this bridge. Some controllers (Apple Home) stream slider updates while the user is still dragging, causing covers to start moving toward an intermediate target. Set to the time the bridge should wait after the last update before sending the final value to Home Assistant. 0 keeps the built-in two-phase debounce (400 ms initial / 150 ms subsequent), which fits most controllers. Try 800\u20131500 ms for slow blinds. A per-entity override on a single cover wins over this flag.",
|
|
146845
|
+
type: "number",
|
|
146846
|
+
minimum: 0,
|
|
146847
|
+
maximum: 5e3,
|
|
146848
|
+
default: 0
|
|
146816
146849
|
}
|
|
146817
146850
|
}
|
|
146818
146851
|
};
|
|
@@ -146879,6 +146912,14 @@ var init_bridge_config_schema = __esm({
|
|
|
146879
146912
|
description: "Append a suffix to every entity serial number on this bridge. Useful for forcing controllers like Aqara to treat devices as new and bypass cached device data. Leave empty for default behavior.",
|
|
146880
146913
|
maxLength: 16
|
|
146881
146914
|
},
|
|
146915
|
+
sessionMaxAgeHours: {
|
|
146916
|
+
title: "Session Rotation Max Age (hours)",
|
|
146917
|
+
type: "number",
|
|
146918
|
+
description: "Server Mode only. Rotate matter sessions older than this many hours so iPhone clients re-subscribe and Apple Home unsticks 'Updating' tiles. Set 0 to disable rotation. Range 0-168, default 4. (#287)",
|
|
146919
|
+
default: 4,
|
|
146920
|
+
minimum: 0,
|
|
146921
|
+
maximum: 168
|
|
146922
|
+
},
|
|
146882
146923
|
filter: homeAssistantFilterSchema,
|
|
146883
146924
|
featureFlags: featureFlagSchema
|
|
146884
146925
|
},
|
|
@@ -147325,7 +147366,7 @@ var HA_MESSAGE_TIMEOUT_MS;
|
|
|
147325
147366
|
var init_send_ha_message = __esm({
|
|
147326
147367
|
"src/utils/send-ha-message.ts"() {
|
|
147327
147368
|
"use strict";
|
|
147328
|
-
HA_MESSAGE_TIMEOUT_MS =
|
|
147369
|
+
HA_MESSAGE_TIMEOUT_MS = 6e4;
|
|
147329
147370
|
}
|
|
147330
147371
|
});
|
|
147331
147372
|
|
|
@@ -147873,6 +147914,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
147873
147914
|
mopIntensityEntity: config10.mopIntensityEntity,
|
|
147874
147915
|
valetudoIdentifier: config10.valetudoIdentifier,
|
|
147875
147916
|
coverSwapOpenClose: config10.coverSwapOpenClose,
|
|
147917
|
+
coverSliderDebounceMs: config10.coverSliderDebounceMs,
|
|
147876
147918
|
disableClimateOnOff: config10.disableClimateOnOff,
|
|
147877
147919
|
disableClimateFanControl: config10.disableClimateFanControl,
|
|
147878
147920
|
customServiceAreas: config10.customServiceAreas,
|
|
@@ -148020,6 +148062,7 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
148020
148062
|
temperatureEntity: config10.temperatureEntity,
|
|
148021
148063
|
valetudoIdentifier: config10.valetudoIdentifier,
|
|
148022
148064
|
coverSwapOpenClose: config10.coverSwapOpenClose,
|
|
148065
|
+
coverSliderDebounceMs: config10.coverSliderDebounceMs,
|
|
148023
148066
|
disableClimateOnOff: config10.disableClimateOnOff,
|
|
148024
148067
|
disableClimateFanControl: config10.disableClimateFanControl,
|
|
148025
148068
|
customServiceAreas: config10.customServiceAreas,
|
|
@@ -148390,6 +148433,15 @@ function bridgeIconApi(storageLocation) {
|
|
|
148390
148433
|
res.json({ success: true, iconUrl });
|
|
148391
148434
|
}
|
|
148392
148435
|
);
|
|
148436
|
+
router.get(
|
|
148437
|
+
"/:bridgeId/exists",
|
|
148438
|
+
(req, res) => {
|
|
148439
|
+
const bridgeId = req.params.bridgeId;
|
|
148440
|
+
const files = fs2.readdirSync(iconsDir);
|
|
148441
|
+
const exists = files.some((f) => f.startsWith(`${bridgeId}.`));
|
|
148442
|
+
res.json({ exists });
|
|
148443
|
+
}
|
|
148444
|
+
);
|
|
148393
148445
|
router.get("/:bridgeId", (req, res) => {
|
|
148394
148446
|
const bridgeId = req.params.bridgeId;
|
|
148395
148447
|
const files = fs2.readdirSync(iconsDir);
|
|
@@ -148413,16 +148465,6 @@ function bridgeIconApi(storageLocation) {
|
|
|
148413
148465
|
fs2.unlinkSync(filePath);
|
|
148414
148466
|
res.json({ success: true });
|
|
148415
148467
|
});
|
|
148416
|
-
router.head("/:bridgeId", (req, res) => {
|
|
148417
|
-
const bridgeId = req.params.bridgeId;
|
|
148418
|
-
const files = fs2.readdirSync(iconsDir);
|
|
148419
|
-
const iconFile = files.find((f) => f.startsWith(`${bridgeId}.`));
|
|
148420
|
-
if (iconFile) {
|
|
148421
|
-
res.status(200).end();
|
|
148422
|
-
} else {
|
|
148423
|
-
res.status(404).end();
|
|
148424
|
-
}
|
|
148425
|
-
});
|
|
148426
148468
|
return router;
|
|
148427
148469
|
}
|
|
148428
148470
|
|
|
@@ -148596,7 +148638,14 @@ function addLogEntry(entry) {
|
|
|
148596
148638
|
function logsApi(_logger) {
|
|
148597
148639
|
const router = express5.Router();
|
|
148598
148640
|
router.get("/", (req, res) => {
|
|
148599
|
-
const {
|
|
148641
|
+
const {
|
|
148642
|
+
level,
|
|
148643
|
+
search,
|
|
148644
|
+
category,
|
|
148645
|
+
facility,
|
|
148646
|
+
limit = "100",
|
|
148647
|
+
offset = "0"
|
|
148648
|
+
} = req.query;
|
|
148600
148649
|
const limitNum = Math.min(
|
|
148601
148650
|
500,
|
|
148602
148651
|
Math.max(1, parseInt(limit, 10) || 100)
|
|
@@ -148607,6 +148656,18 @@ function logsApi(_logger) {
|
|
|
148607
148656
|
const levels = level.split(",").map((l) => l.toLowerCase().trim());
|
|
148608
148657
|
entries = entries.filter((e) => levels.includes(e.level.toLowerCase()));
|
|
148609
148658
|
}
|
|
148659
|
+
if (category && typeof category === "string") {
|
|
148660
|
+
const cats = category.split(",").map((c) => c.toLowerCase().trim());
|
|
148661
|
+
entries = entries.filter(
|
|
148662
|
+
(e) => e.category != null && cats.includes(e.category.toLowerCase())
|
|
148663
|
+
);
|
|
148664
|
+
}
|
|
148665
|
+
if (facility && typeof facility === "string") {
|
|
148666
|
+
const facLower = facility.toLowerCase();
|
|
148667
|
+
entries = entries.filter(
|
|
148668
|
+
(e) => e.facility?.toLowerCase().includes(facLower)
|
|
148669
|
+
);
|
|
148670
|
+
}
|
|
148610
148671
|
if (search && typeof search === "string") {
|
|
148611
148672
|
const searchLower = search.toLowerCase();
|
|
148612
148673
|
entries = entries.filter(
|
|
@@ -148823,6 +148884,7 @@ function entityMappingApi(mappingStorage) {
|
|
|
148823
148884
|
customProductName: body.customProductName,
|
|
148824
148885
|
customVendorName: body.customVendorName,
|
|
148825
148886
|
customSerialNumber: body.customSerialNumber,
|
|
148887
|
+
customVendorId: body.customVendorId,
|
|
148826
148888
|
disabled: body.disabled,
|
|
148827
148889
|
filterLifeEntity: body.filterLifeEntity,
|
|
148828
148890
|
cleaningModeEntity: body.cleaningModeEntity,
|
|
@@ -148841,8 +148903,10 @@ function entityMappingApi(mappingStorage) {
|
|
|
148841
148903
|
currentRoomEntity: body.currentRoomEntity,
|
|
148842
148904
|
valetudoIdentifier: body.valetudoIdentifier,
|
|
148843
148905
|
coverSwapOpenClose: body.coverSwapOpenClose,
|
|
148906
|
+
coverSliderDebounceMs: body.coverSliderDebounceMs,
|
|
148844
148907
|
disableClimateOnOff: body.disableClimateOnOff,
|
|
148845
148908
|
disableClimateFanControl: body.disableClimateFanControl,
|
|
148909
|
+
climateKeepModeOnIdle: body.climateKeepModeOnIdle,
|
|
148846
148910
|
composedEntities: body.composedEntities
|
|
148847
148911
|
};
|
|
148848
148912
|
const config10 = await mappingStorage.setMapping(request);
|
|
@@ -149306,8 +149370,10 @@ function configToProfileEntry(config10) {
|
|
|
149306
149370
|
customFanSpeedTags: config10.customFanSpeedTags,
|
|
149307
149371
|
valetudoIdentifier: config10.valetudoIdentifier,
|
|
149308
149372
|
coverSwapOpenClose: config10.coverSwapOpenClose,
|
|
149373
|
+
coverSliderDebounceMs: config10.coverSliderDebounceMs,
|
|
149309
149374
|
disableClimateOnOff: config10.disableClimateOnOff,
|
|
149310
|
-
disableClimateFanControl: config10.disableClimateFanControl
|
|
149375
|
+
disableClimateFanControl: config10.disableClimateFanControl,
|
|
149376
|
+
climateKeepModeOnIdle: config10.climateKeepModeOnIdle
|
|
149311
149377
|
};
|
|
149312
149378
|
}
|
|
149313
149379
|
function mappingProfileApi(mappingStorage) {
|
|
@@ -149429,8 +149495,10 @@ function mappingProfileApi(mappingStorage) {
|
|
|
149429
149495
|
customFanSpeedTags: entry.customFanSpeedTags,
|
|
149430
149496
|
valetudoIdentifier: entry.valetudoIdentifier,
|
|
149431
149497
|
coverSwapOpenClose: entry.coverSwapOpenClose,
|
|
149498
|
+
coverSliderDebounceMs: entry.coverSliderDebounceMs,
|
|
149432
149499
|
disableClimateOnOff: entry.disableClimateOnOff,
|
|
149433
|
-
disableClimateFanControl: entry.disableClimateFanControl
|
|
149500
|
+
disableClimateFanControl: entry.disableClimateFanControl,
|
|
149501
|
+
climateKeepModeOnIdle: entry.climateKeepModeOnIdle
|
|
149434
149502
|
});
|
|
149435
149503
|
applied++;
|
|
149436
149504
|
} catch (e) {
|
|
@@ -149482,6 +149550,18 @@ function testMatcher(matcher, device, entity, entityState, labels) {
|
|
|
149482
149550
|
const slug = resolveLabelValue(matcher.value, labels);
|
|
149483
149551
|
return !!device?.labels && device.labels.includes(slug);
|
|
149484
149552
|
}
|
|
149553
|
+
case "entity_label_regex":
|
|
149554
|
+
return testLabelRegex(matcher.value, entity?.labels, labels);
|
|
149555
|
+
case "device_label_regex":
|
|
149556
|
+
return testLabelRegex(matcher.value, device?.labels, labels);
|
|
149557
|
+
case "any_field_regex":
|
|
149558
|
+
return testAnyFieldRegex(
|
|
149559
|
+
matcher.value,
|
|
149560
|
+
entity,
|
|
149561
|
+
device,
|
|
149562
|
+
entityState,
|
|
149563
|
+
labels
|
|
149564
|
+
);
|
|
149485
149565
|
case "entity_category":
|
|
149486
149566
|
return entity?.entity_category === matcher.value;
|
|
149487
149567
|
case "platform":
|
|
@@ -149516,6 +149596,62 @@ function testRegex(pattern2, value) {
|
|
|
149516
149596
|
return false;
|
|
149517
149597
|
}
|
|
149518
149598
|
}
|
|
149599
|
+
function testLabelRegex(pattern2, assigned, labels) {
|
|
149600
|
+
if (!assigned?.length) {
|
|
149601
|
+
return false;
|
|
149602
|
+
}
|
|
149603
|
+
let regex;
|
|
149604
|
+
try {
|
|
149605
|
+
regex = new RegExp(pattern2);
|
|
149606
|
+
} catch {
|
|
149607
|
+
return false;
|
|
149608
|
+
}
|
|
149609
|
+
for (const slug of assigned) {
|
|
149610
|
+
if (regex.test(slug)) {
|
|
149611
|
+
return true;
|
|
149612
|
+
}
|
|
149613
|
+
const named = labels?.find((l) => l.label_id === slug)?.name;
|
|
149614
|
+
if (named && regex.test(named)) {
|
|
149615
|
+
return true;
|
|
149616
|
+
}
|
|
149617
|
+
}
|
|
149618
|
+
return false;
|
|
149619
|
+
}
|
|
149620
|
+
function testAnyFieldRegex(pattern2, entity, device, entityState, labels) {
|
|
149621
|
+
let regex;
|
|
149622
|
+
try {
|
|
149623
|
+
regex = new RegExp(pattern2);
|
|
149624
|
+
} catch {
|
|
149625
|
+
return false;
|
|
149626
|
+
}
|
|
149627
|
+
return regex.test(buildEntityHaystack(entity, device, entityState, labels));
|
|
149628
|
+
}
|
|
149629
|
+
function buildEntityHaystack(entity, device, entityState, labels) {
|
|
149630
|
+
const domain = entity.entity_id.split(".")[0];
|
|
149631
|
+
const entityArea = entity.area_id ?? (typeof device?.area_id === "string" ? device.area_id : "");
|
|
149632
|
+
const entityCategory = typeof entity.entity_category === "string" ? entity.entity_category : "";
|
|
149633
|
+
const deviceClass = typeof entityState?.attributes?.device_class === "string" ? entityState.attributes.device_class : "";
|
|
149634
|
+
const entityLabelSlugs = entity.labels ?? [];
|
|
149635
|
+
const deviceLabelSlugs = device?.labels ?? [];
|
|
149636
|
+
const entityLabelNames = entityLabelSlugs.map((slug) => labels?.find((l) => l.label_id === slug)?.name ?? "").filter((name) => name.length > 0);
|
|
149637
|
+
const deviceLabelNames = deviceLabelSlugs.map((slug) => labels?.find((l) => l.label_id === slug)?.name ?? "").filter((name) => name.length > 0);
|
|
149638
|
+
const deviceName = device?.name_by_user ?? device?.name ?? device?.default_name ?? "";
|
|
149639
|
+
const productName = device?.model ?? device?.default_model ?? "";
|
|
149640
|
+
return [
|
|
149641
|
+
`entity_id=${entity.entity_id}`,
|
|
149642
|
+
`domain=${domain}`,
|
|
149643
|
+
`platform=${entity.platform ?? ""}`,
|
|
149644
|
+
`area=${entityArea ?? ""}`,
|
|
149645
|
+
`entity_category=${entityCategory}`,
|
|
149646
|
+
`device_class=${deviceClass}`,
|
|
149647
|
+
`entity_labels=${entityLabelSlugs.join(",")}`,
|
|
149648
|
+
`entity_label_names=${entityLabelNames.join(",")}`,
|
|
149649
|
+
`device_labels=${deviceLabelSlugs.join(",")}`,
|
|
149650
|
+
`device_label_names=${deviceLabelNames.join(",")}`,
|
|
149651
|
+
`device_name=${deviceName}`,
|
|
149652
|
+
`product_name=${productName}`
|
|
149653
|
+
].join(" ");
|
|
149654
|
+
}
|
|
149519
149655
|
function testDeviceName(pattern2, device) {
|
|
149520
149656
|
if (!device) {
|
|
149521
149657
|
return false;
|
|
@@ -151608,6 +151744,17 @@ init_esm7();
|
|
|
151608
151744
|
|
|
151609
151745
|
// src/core/app/logger.ts
|
|
151610
151746
|
init_esm();
|
|
151747
|
+
var MATTER_TRAFFIC_FACILITIES = /* @__PURE__ */ new Set([
|
|
151748
|
+
"InteractionServer",
|
|
151749
|
+
"MessageExchange",
|
|
151750
|
+
"MessageChannel",
|
|
151751
|
+
"ServerSubscription",
|
|
151752
|
+
"IncomingInteractionServerMessenger",
|
|
151753
|
+
"ExchangeManager"
|
|
151754
|
+
]);
|
|
151755
|
+
function categoryFor(facility) {
|
|
151756
|
+
return MATTER_TRAFFIC_FACILITIES.has(facility) ? "matter-traffic" : void 0;
|
|
151757
|
+
}
|
|
151611
151758
|
function logLevelFromString(level) {
|
|
151612
151759
|
const customNames = {
|
|
151613
151760
|
SILLY: -1 /* SILLY */
|
|
@@ -151653,6 +151800,26 @@ var LoggerService = class {
|
|
|
151653
151800
|
MessageExchange: resolvedProtocolLevel
|
|
151654
151801
|
};
|
|
151655
151802
|
Logger.format = options.disableColors ? LogFormat.PLAIN : LogFormat.ANSI;
|
|
151803
|
+
const destinationLevel = this.customLogLevelMapping[this._level] ?? this._level;
|
|
151804
|
+
Logger.destinations["hamh-buffer"] = LogDestination({
|
|
151805
|
+
name: "hamh-buffer",
|
|
151806
|
+
level: destinationLevel,
|
|
151807
|
+
facilityLevels: {
|
|
151808
|
+
MessageChannel: resolvedProtocolLevel,
|
|
151809
|
+
MessageExchange: resolvedProtocolLevel
|
|
151810
|
+
},
|
|
151811
|
+
write: (text, message) => {
|
|
151812
|
+
const category = categoryFor(message.facility);
|
|
151813
|
+
if (!category) return;
|
|
151814
|
+
addLogEntry({
|
|
151815
|
+
timestamp: message.now.toISOString(),
|
|
151816
|
+
level: logLevelToString(message.level).toLowerCase(),
|
|
151817
|
+
message: text,
|
|
151818
|
+
facility: message.facility,
|
|
151819
|
+
category
|
|
151820
|
+
});
|
|
151821
|
+
}
|
|
151822
|
+
});
|
|
151656
151823
|
}
|
|
151657
151824
|
get(nameOrService) {
|
|
151658
151825
|
let name;
|
|
@@ -151992,7 +152159,8 @@ var Options = class {
|
|
|
151992
152159
|
return {
|
|
151993
152160
|
url: this.startOptions.homeAssistantUrl,
|
|
151994
152161
|
accessToken: this.startOptions.homeAssistantAccessToken,
|
|
151995
|
-
refreshInterval: this.startOptions.homeAssistantRefreshInterval
|
|
152162
|
+
refreshInterval: this.startOptions.homeAssistantRefreshInterval,
|
|
152163
|
+
messageTimeoutMs: this.startOptions.haMessageTimeout
|
|
151996
152164
|
};
|
|
151997
152165
|
}
|
|
151998
152166
|
get webApi() {
|
|
@@ -152684,25 +152852,33 @@ init_retry();
|
|
|
152684
152852
|
|
|
152685
152853
|
// src/services/home-assistant/api/get-registry.ts
|
|
152686
152854
|
init_send_ha_message();
|
|
152687
|
-
async function getRegistry(connection) {
|
|
152688
|
-
return sendHaMessage(
|
|
152689
|
-
|
|
152690
|
-
|
|
152855
|
+
async function getRegistry(connection, timeoutMs) {
|
|
152856
|
+
return sendHaMessage(
|
|
152857
|
+
connection,
|
|
152858
|
+
{ type: "config/entity_registry/list" },
|
|
152859
|
+
timeoutMs
|
|
152860
|
+
);
|
|
152691
152861
|
}
|
|
152692
|
-
async function getDeviceRegistry(connection) {
|
|
152693
|
-
return sendHaMessage(
|
|
152694
|
-
|
|
152695
|
-
|
|
152862
|
+
async function getDeviceRegistry(connection, timeoutMs) {
|
|
152863
|
+
return sendHaMessage(
|
|
152864
|
+
connection,
|
|
152865
|
+
{ type: "config/device_registry/list" },
|
|
152866
|
+
timeoutMs
|
|
152867
|
+
);
|
|
152696
152868
|
}
|
|
152697
|
-
async function getLabelRegistry(connection) {
|
|
152698
|
-
return sendHaMessage(
|
|
152699
|
-
|
|
152700
|
-
|
|
152869
|
+
async function getLabelRegistry(connection, timeoutMs) {
|
|
152870
|
+
return sendHaMessage(
|
|
152871
|
+
connection,
|
|
152872
|
+
{ type: "config/label_registry/list" },
|
|
152873
|
+
timeoutMs
|
|
152874
|
+
);
|
|
152701
152875
|
}
|
|
152702
|
-
async function getAreaRegistry(connection) {
|
|
152703
|
-
return sendHaMessage(
|
|
152704
|
-
|
|
152705
|
-
|
|
152876
|
+
async function getAreaRegistry(connection, timeoutMs) {
|
|
152877
|
+
return sendHaMessage(
|
|
152878
|
+
connection,
|
|
152879
|
+
{ type: "config/area_registry/list" },
|
|
152880
|
+
timeoutMs
|
|
152881
|
+
);
|
|
152706
152882
|
}
|
|
152707
152883
|
|
|
152708
152884
|
// src/services/home-assistant/home-assistant-registry.ts
|
|
@@ -152748,7 +152924,7 @@ var HomeAssistantRegistry = class extends Service {
|
|
|
152748
152924
|
let refreshing = false;
|
|
152749
152925
|
this.autoRefresh = setInterval(async () => {
|
|
152750
152926
|
if (refreshing) {
|
|
152751
|
-
logger146.debug("Skipping registry refresh
|
|
152927
|
+
logger146.debug("Skipping registry refresh, previous tick still running");
|
|
152752
152928
|
return;
|
|
152753
152929
|
}
|
|
152754
152930
|
refreshing = true;
|
|
@@ -152805,12 +152981,15 @@ var HomeAssistantRegistry = class extends Service {
|
|
|
152805
152981
|
}
|
|
152806
152982
|
});
|
|
152807
152983
|
}
|
|
152984
|
+
const timeoutMs = this.options.messageTimeoutMs;
|
|
152808
152985
|
const [entityRegistry, statesList, deviceRegistry, labels, areas] = await Promise.all([
|
|
152809
|
-
getRegistry(connection),
|
|
152986
|
+
getRegistry(connection, timeoutMs),
|
|
152810
152987
|
getStates(connection),
|
|
152811
|
-
getDeviceRegistry(connection),
|
|
152812
|
-
getLabelRegistry(connection).catch(
|
|
152813
|
-
|
|
152988
|
+
getDeviceRegistry(connection, timeoutMs),
|
|
152989
|
+
getLabelRegistry(connection, timeoutMs).catch(
|
|
152990
|
+
() => []
|
|
152991
|
+
),
|
|
152992
|
+
getAreaRegistry(connection, timeoutMs).catch(
|
|
152814
152993
|
() => []
|
|
152815
152994
|
)
|
|
152816
152995
|
]);
|
|
@@ -153279,6 +153458,7 @@ var EntityMappingStorage = class extends Service {
|
|
|
153279
153458
|
customProductName: request.customProductName?.trim() || void 0,
|
|
153280
153459
|
customVendorName: request.customVendorName?.trim() || void 0,
|
|
153281
153460
|
customSerialNumber: request.customSerialNumber?.trim() || void 0,
|
|
153461
|
+
customVendorId: sanitizeVendorId(request.customVendorId),
|
|
153282
153462
|
disabled: request.disabled,
|
|
153283
153463
|
filterLifeEntity: request.filterLifeEntity?.trim() || void 0,
|
|
153284
153464
|
cleaningModeEntity: request.cleaningModeEntity?.trim() || void 0,
|
|
@@ -153299,11 +153479,13 @@ var EntityMappingStorage = class extends Service {
|
|
|
153299
153479
|
currentRoomEntity: request.currentRoomEntity?.trim() || void 0,
|
|
153300
153480
|
valetudoIdentifier: request.valetudoIdentifier?.trim() || void 0,
|
|
153301
153481
|
coverSwapOpenClose: request.coverSwapOpenClose || void 0,
|
|
153482
|
+
coverSliderDebounceMs: sanitizeDebounceMs(request.coverSliderDebounceMs),
|
|
153302
153483
|
disableClimateOnOff: request.disableClimateOnOff || void 0,
|
|
153303
153484
|
disableClimateFanControl: request.disableClimateFanControl || void 0,
|
|
153485
|
+
climateKeepModeOnIdle: request.climateKeepModeOnIdle || void 0,
|
|
153304
153486
|
composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0
|
|
153305
153487
|
};
|
|
153306
|
-
if (!config10.matterDeviceType && !config10.customName && !config10.customProductName && !config10.customVendorName && !config10.customSerialNumber && config10.disabled !== true && !config10.filterLifeEntity && !config10.cleaningModeEntity && !config10.temperatureEntity && !config10.humidityEntity && !config10.batteryEntity && !config10.roomEntities && !config10.disableLockPin && !config10.powerEntity && !config10.energyEntity && !config10.pressureEntity && !config10.suctionLevelEntity && !config10.mopIntensityEntity && (!config10.customServiceAreas || config10.customServiceAreas.length === 0) && (!config10.customFanSpeedTags || Object.keys(config10.customFanSpeedTags).length === 0) && !config10.currentRoomEntity && !config10.valetudoIdentifier && !config10.coverSwapOpenClose && !config10.disableClimateOnOff && !config10.disableClimateFanControl && (!config10.composedEntities || config10.composedEntities.length === 0)) {
|
|
153488
|
+
if (!config10.matterDeviceType && !config10.customName && !config10.customProductName && !config10.customVendorName && !config10.customSerialNumber && config10.customVendorId === void 0 && config10.disabled !== true && !config10.filterLifeEntity && !config10.cleaningModeEntity && !config10.temperatureEntity && !config10.humidityEntity && !config10.batteryEntity && !config10.roomEntities && !config10.disableLockPin && !config10.powerEntity && !config10.energyEntity && !config10.pressureEntity && !config10.suctionLevelEntity && !config10.mopIntensityEntity && (!config10.customServiceAreas || config10.customServiceAreas.length === 0) && (!config10.customFanSpeedTags || Object.keys(config10.customFanSpeedTags).length === 0) && !config10.currentRoomEntity && !config10.valetudoIdentifier && !config10.coverSwapOpenClose && !config10.coverSliderDebounceMs && !config10.disableClimateOnOff && !config10.disableClimateFanControl && !config10.climateKeepModeOnIdle && (!config10.composedEntities || config10.composedEntities.length === 0)) {
|
|
153307
153489
|
bridgeMap.delete(request.entityId);
|
|
153308
153490
|
} else {
|
|
153309
153491
|
bridgeMap.set(request.entityId, config10);
|
|
@@ -153323,6 +153505,26 @@ var EntityMappingStorage = class extends Service {
|
|
|
153323
153505
|
await this.persist();
|
|
153324
153506
|
}
|
|
153325
153507
|
};
|
|
153508
|
+
function sanitizeVendorId(value) {
|
|
153509
|
+
if (value === void 0 || value === null || value === "") {
|
|
153510
|
+
return void 0;
|
|
153511
|
+
}
|
|
153512
|
+
const n = typeof value === "string" ? Number(value) : value;
|
|
153513
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 65534) {
|
|
153514
|
+
return void 0;
|
|
153515
|
+
}
|
|
153516
|
+
return n;
|
|
153517
|
+
}
|
|
153518
|
+
function sanitizeDebounceMs(value) {
|
|
153519
|
+
if (value === void 0 || value === null || value === "") {
|
|
153520
|
+
return void 0;
|
|
153521
|
+
}
|
|
153522
|
+
const n = typeof value === "string" ? Number(value) : value;
|
|
153523
|
+
if (typeof n !== "number" || !Number.isFinite(n) || n <= 0) {
|
|
153524
|
+
return void 0;
|
|
153525
|
+
}
|
|
153526
|
+
return Math.min(5e3, Math.round(n));
|
|
153527
|
+
}
|
|
153326
153528
|
|
|
153327
153529
|
// src/services/storage/lock-credential-storage.ts
|
|
153328
153530
|
init_service();
|
|
@@ -165337,14 +165539,16 @@ var ServerModeServerNode = class extends ServerNode {
|
|
|
165337
165539
|
/**
|
|
165338
165540
|
* Update root-level BasicInformation with entity-specific data.
|
|
165339
165541
|
* In server mode, controllers (Apple Home, Alexa) read the root node's
|
|
165340
|
-
* BasicInformation
|
|
165542
|
+
* BasicInformation, not the device endpoint's BridgedDeviceBasicInformation.
|
|
165341
165543
|
* Without this, server-mode devices show bridge defaults (e.g. "riddix" / "MatterHub").
|
|
165342
165544
|
*/
|
|
165343
165545
|
async updateDeviceIdentity(entityId, device, mapping, friendlyName) {
|
|
165344
165546
|
const nodeLabel = trimToLength(mapping?.customName, 32, "...") ?? trimToLength(friendlyName, 32, "...") ?? trimToLength(entityId, 32, "...");
|
|
165345
165547
|
const productNameFromNodeLabel = this.featureFlags?.productNameFromNodeLabel === true ? trimToLength(sanitizeMatterString(nodeLabel ?? ""), 32, "...") ?? void 0 : void 0;
|
|
165346
|
-
const
|
|
165347
|
-
const
|
|
165548
|
+
const maxRawLen = 32 - (this.serialNumberSuffix?.length ?? 0);
|
|
165549
|
+
const registrySerial = this.featureFlags?.useHaRegistrySerial ? trimToLength(device?.serial_number, maxRawLen, "...") : void 0;
|
|
165550
|
+
const rawSerial = trimToLength(mapping?.customSerialNumber, maxRawLen, "...") ?? registrySerial;
|
|
165551
|
+
const serialNumber = rawSerial && this.serialNumberSuffix ? `${rawSerial}${this.serialNumberSuffix}` : rawSerial;
|
|
165348
165552
|
const basicInformation = dropUndefined({
|
|
165349
165553
|
vendorName: trimToLength(mapping?.customVendorName, 32, "...") ?? trimToLength(device?.manufacturer, 32, "..."),
|
|
165350
165554
|
productName: trimToLength(mapping?.customProductName, 32, "...") ?? productNameFromNodeLabel ?? trimToLength(device?.model_id, 32, "...") ?? trimToLength(device?.model, 32, "..."),
|
|
@@ -166447,6 +166651,9 @@ var BridgeDataProvider = class extends Service {
|
|
|
166447
166651
|
get serialNumberSuffix() {
|
|
166448
166652
|
return this.data.serialNumberSuffix;
|
|
166449
166653
|
}
|
|
166654
|
+
get sessionMaxAgeHours() {
|
|
166655
|
+
return this.data.sessionMaxAgeHours;
|
|
166656
|
+
}
|
|
166450
166657
|
/************************************************
|
|
166451
166658
|
* Functions
|
|
166452
166659
|
************************************************/
|
|
@@ -166472,6 +166679,7 @@ var BridgeDataProvider = class extends Service {
|
|
|
166472
166679
|
icon: this.icon,
|
|
166473
166680
|
priority: this.priority,
|
|
166474
166681
|
serialNumberSuffix: this.serialNumberSuffix,
|
|
166682
|
+
sessionMaxAgeHours: this.sessionMaxAgeHours,
|
|
166475
166683
|
status: status3.code,
|
|
166476
166684
|
statusReason: status3.reason,
|
|
166477
166685
|
commissioning: commissioning ? {
|
|
@@ -167848,7 +168056,7 @@ ${e?.toString()}`);
|
|
|
167848
168056
|
);
|
|
167849
168057
|
if (totalSubs === 0 && sessions.length > 0) {
|
|
167850
168058
|
this.log.warn(
|
|
167851
|
-
`All subscriptions lost
|
|
168059
|
+
`All subscriptions lost, ${sessions.length} session(s) still active, waiting for controller to re-subscribe`
|
|
167852
168060
|
);
|
|
167853
168061
|
if (!this.deadSessionTimer) {
|
|
167854
168062
|
this.deadSessionTimer = setTimeout(() => {
|
|
@@ -167896,7 +168104,7 @@ ${e?.toString()}`);
|
|
|
167896
168104
|
for (const s of [...sessionManager.sessions]) {
|
|
167897
168105
|
if (s !== newSession && !s.isClosing && s.peerNodeId === newSession.peerNodeId && s.fabric?.fabricIndex === newSession.fabric?.fabricIndex && s.subscriptions.size === 0) {
|
|
167898
168106
|
this.log.info(
|
|
167899
|
-
`Closing stale session ${s.id} (peer ${s.peerNodeId}, 0 subs)
|
|
168107
|
+
`Closing stale session ${s.id} (peer ${s.peerNodeId}, 0 subs), replaced by session ${newSession.id}`
|
|
167900
168108
|
);
|
|
167901
168109
|
s.initiateForceClose().catch(() => {
|
|
167902
168110
|
});
|
|
@@ -168068,6 +168276,12 @@ ${e?.toString()}`);
|
|
|
168068
168276
|
if (!this.dataProvider.featureFlags?.autoForceSync) {
|
|
168069
168277
|
return 0;
|
|
168070
168278
|
}
|
|
168279
|
+
if (isHeapUnderPressure()) {
|
|
168280
|
+
this.log.warn(
|
|
168281
|
+
"Force sync skipped: heap under pressure, reduce entities or raise NODE_OPTIONS=--max-old-space-size"
|
|
168282
|
+
);
|
|
168283
|
+
return 0;
|
|
168284
|
+
}
|
|
168071
168285
|
const { HomeAssistantEntityBehavior: HomeAssistantEntityBehavior2 } = await Promise.resolve().then(() => (init_home_assistant_entity_behavior(), home_assistant_entity_behavior_exports));
|
|
168072
168286
|
const allEndpoints = [];
|
|
168073
168287
|
const collect = (ep) => {
|
|
@@ -168351,7 +168565,7 @@ var BasicInformationServer2 = class extends BridgedDeviceBasicInformationServer
|
|
|
168351
168565
|
await super.initialize();
|
|
168352
168566
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
168353
168567
|
this.update(homeAssistant.entity);
|
|
168354
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
168568
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
168355
168569
|
}
|
|
168356
168570
|
update(entity) {
|
|
168357
168571
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -168365,10 +168579,14 @@ var BasicInformationServer2 = class extends BridgedDeviceBasicInformationServer
|
|
|
168365
168579
|
const nodeLabel = ellipse(32, homeAssistant.state.customName) ?? ellipse(32, registryName) ?? ellipse(32, entity.state?.attributes?.friendly_name) ?? ellipse(32, entity.entity_id);
|
|
168366
168580
|
const productNameFromNodeLabel = featureFlags?.productNameFromNodeLabel === true ? ellipse(32, sanitizeMatterString(nodeLabel ?? "")) ?? void 0 : void 0;
|
|
168367
168581
|
const serialNumberSuffix = this.env.get(BridgeDataProvider).serialNumberSuffix;
|
|
168368
|
-
const
|
|
168369
|
-
const
|
|
168582
|
+
const maxRawLen = 32 - (serialNumberSuffix?.length ?? 0);
|
|
168583
|
+
const registrySerial = featureFlags?.useHaRegistrySerial ? ellipse(maxRawLen, device?.serial_number) : void 0;
|
|
168584
|
+
const rawSerial = ellipse(maxRawLen, mapping?.customSerialNumber) ?? registrySerial ?? hash(maxRawLen, entity.entity_id);
|
|
168585
|
+
const serialNumber = rawSerial && serialNumberSuffix ? `${rawSerial}${serialNumberSuffix}` : rawSerial;
|
|
168586
|
+
const customVendorId = mapping?.customVendorId;
|
|
168587
|
+
const vendorId3 = isValidVendorId(customVendorId) ? customVendorId : basicInformation.vendorId;
|
|
168370
168588
|
applyPatchState(this.state, {
|
|
168371
|
-
vendorId: VendorId(
|
|
168589
|
+
vendorId: VendorId(vendorId3),
|
|
168372
168590
|
vendorName: ellipse(32, mapping?.customVendorName) ?? ellipse(32, device?.manufacturer) ?? hash(32, basicInformation.vendorName),
|
|
168373
168591
|
productName: ellipse(32, mapping?.customProductName) ?? productNameFromNodeLabel ?? ellipse(32, device?.model_id) ?? ellipse(32, device?.model) ?? hash(32, basicInformation.productName),
|
|
168374
168592
|
productLabel: ellipse(64, device?.model) ?? hash(64, basicInformation.productLabel),
|
|
@@ -168393,6 +168611,9 @@ function hash(maxLength, value) {
|
|
|
168393
168611
|
const suffix = crypto6.createHash("md5").update(value ?? "").digest("hex").substring(0, hashLength);
|
|
168394
168612
|
return trimToLength(value, maxLength, suffix);
|
|
168395
168613
|
}
|
|
168614
|
+
function isValidVendorId(value) {
|
|
168615
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65534;
|
|
168616
|
+
}
|
|
168396
168617
|
|
|
168397
168618
|
// src/matter/behaviors/electrical-energy-measurement-server.ts
|
|
168398
168619
|
init_esm();
|
|
@@ -168417,7 +168638,7 @@ var ElectricalEnergyMeasurementServerBase = class extends FeaturedBase {
|
|
|
168417
168638
|
);
|
|
168418
168639
|
}
|
|
168419
168640
|
this.update();
|
|
168420
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
168641
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
168421
168642
|
}
|
|
168422
168643
|
update() {
|
|
168423
168644
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
@@ -168443,6 +168664,10 @@ var ElectricalEnergyMeasurementServerBase = class extends FeaturedBase {
|
|
|
168443
168664
|
ElectricalEnergyMeasurementServerBase2.State = State;
|
|
168444
168665
|
})(ElectricalEnergyMeasurementServerBase || (ElectricalEnergyMeasurementServerBase = {}));
|
|
168445
168666
|
var HaElectricalEnergyMeasurementServer = ElectricalEnergyMeasurementServerBase.set({
|
|
168667
|
+
// Match the activePower=0 default in HaElectricalPowerMeasurementServer
|
|
168668
|
+
// so SmartThings doesn't show "- kWh" before the first state update or
|
|
168669
|
+
// when an entity only carries power (not energy) data.
|
|
168670
|
+
cumulativeEnergyImported: { energy: 0 },
|
|
168446
168671
|
accuracy: {
|
|
168447
168672
|
measurementType: ElectricalPowerMeasurement3.MeasurementType.ElectricalEnergy,
|
|
168448
168673
|
measured: true,
|
|
@@ -168477,7 +168702,7 @@ var ElectricalPowerMeasurementServerBase = class extends ElectricalPowerMeasurem
|
|
|
168477
168702
|
);
|
|
168478
168703
|
}
|
|
168479
168704
|
this.update();
|
|
168480
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
168705
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
168481
168706
|
}
|
|
168482
168707
|
update() {
|
|
168483
168708
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
@@ -168530,7 +168755,7 @@ var HumidityMeasurementServerBase = class extends RelativeHumidityMeasurementSer
|
|
|
168530
168755
|
await super.initialize();
|
|
168531
168756
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
168532
168757
|
this.update(homeAssistant.entity);
|
|
168533
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
168758
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
168534
168759
|
}
|
|
168535
168760
|
update(entity) {
|
|
168536
168761
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -168594,7 +168819,7 @@ var PowerSourceServerBase = class extends FeaturedBase2 {
|
|
|
168594
168819
|
);
|
|
168595
168820
|
}
|
|
168596
168821
|
this.update(homeAssistant.entity);
|
|
168597
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
168822
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
168598
168823
|
}
|
|
168599
168824
|
update(entity) {
|
|
168600
168825
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -168673,6 +168898,11 @@ var defaultBatteryConfig = {
|
|
|
168673
168898
|
};
|
|
168674
168899
|
var DefaultPowerSourceServer = PowerSourceServer2(defaultBatteryConfig);
|
|
168675
168900
|
|
|
168901
|
+
// src/matter/behaviors/power-topology-server.ts
|
|
168902
|
+
var HaPowerTopologyServer = PowerTopologyServer.with(
|
|
168903
|
+
PowerTopology3.Feature.NodeTopology
|
|
168904
|
+
);
|
|
168905
|
+
|
|
168676
168906
|
// src/matter/behaviors/temperature-measurement-server.ts
|
|
168677
168907
|
init_home_assistant_entity_behavior();
|
|
168678
168908
|
var TemperatureMeasurementServerBase = class extends TemperatureMeasurementServer {
|
|
@@ -168680,7 +168910,7 @@ var TemperatureMeasurementServerBase = class extends TemperatureMeasurementServe
|
|
|
168680
168910
|
await super.initialize();
|
|
168681
168911
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
168682
168912
|
this.update(homeAssistant.entity);
|
|
168683
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
168913
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
168684
168914
|
}
|
|
168685
168915
|
update(entity) {
|
|
168686
168916
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -168722,7 +168952,7 @@ var HepaFilterMonitoringServerBase = class extends FeaturedBase3 {
|
|
|
168722
168952
|
await super.initialize();
|
|
168723
168953
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
168724
168954
|
this.update(homeAssistant.entity);
|
|
168725
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
168955
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
168726
168956
|
}
|
|
168727
168957
|
update(entity) {
|
|
168728
168958
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -169200,7 +169430,7 @@ var FeaturedBase5 = FanControlServer.with(
|
|
|
169200
169430
|
"Rocking",
|
|
169201
169431
|
"Wind"
|
|
169202
169432
|
).set({
|
|
169203
|
-
// rockSupport / windSupport are Fixed quality attributes
|
|
169433
|
+
// rockSupport / windSupport are Fixed quality attributes, they MUST be set
|
|
169204
169434
|
// via .set() at behavior creation time, NOT in initialize().
|
|
169205
169435
|
// Without these, controllers reject attempts to enable rocking/wind.
|
|
169206
169436
|
rockSupport: { rockUpDown: true },
|
|
@@ -169776,6 +170006,9 @@ var ComposedAirPurifierEndpoint = class _ComposedAirPurifierEndpoint extends End
|
|
|
169776
170006
|
if (config10.batteryEntityId) {
|
|
169777
170007
|
parentType = parentType.with(PowerSourceServer2(batteryConfig));
|
|
169778
170008
|
}
|
|
170009
|
+
if (config10.powerEntityId || config10.energyEntityId) {
|
|
170010
|
+
parentType = parentType.with(HaPowerTopologyServer);
|
|
170011
|
+
}
|
|
169779
170012
|
if (config10.powerEntityId) {
|
|
169780
170013
|
parentType = parentType.with(HaElectricalPowerMeasurementServer);
|
|
169781
170014
|
}
|
|
@@ -169976,7 +170209,7 @@ var PressureMeasurementServerBase = class extends PressureMeasurementServer {
|
|
|
169976
170209
|
await super.initialize();
|
|
169977
170210
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
169978
170211
|
this.update(homeAssistant.entity);
|
|
169979
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
170212
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
169980
170213
|
}
|
|
169981
170214
|
update(entity) {
|
|
169982
170215
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -170111,6 +170344,9 @@ var ComposedSensorEndpoint = class _ComposedSensorEndpoint extends Endpoint {
|
|
|
170111
170344
|
if (config10.batteryEntityId) {
|
|
170112
170345
|
parentType = parentType.with(PowerSourceServer2(batteryConfig2));
|
|
170113
170346
|
}
|
|
170347
|
+
if (config10.powerEntityId || config10.energyEntityId) {
|
|
170348
|
+
parentType = parentType.with(HaPowerTopologyServer);
|
|
170349
|
+
}
|
|
170114
170350
|
if (config10.powerEntityId) {
|
|
170115
170351
|
parentType = parentType.with(HaElectricalPowerMeasurementServer);
|
|
170116
170352
|
}
|
|
@@ -170536,7 +170772,7 @@ var BooleanStateServerBase = class extends BooleanStateServer {
|
|
|
170536
170772
|
await super.initialize();
|
|
170537
170773
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170538
170774
|
this.update(homeAssistant.entity);
|
|
170539
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
170775
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170540
170776
|
}
|
|
170541
170777
|
update(entity) {
|
|
170542
170778
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -170614,7 +170850,7 @@ var PirOccupancySensingServer = class extends PirOccupancySensingServerBase {
|
|
|
170614
170850
|
await super.initialize();
|
|
170615
170851
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170616
170852
|
this.update(homeAssistant.entity);
|
|
170617
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
170853
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170618
170854
|
}
|
|
170619
170855
|
update(entity) {
|
|
170620
170856
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -170682,7 +170918,7 @@ var OccupancySensingServer2 = class extends OccupancySensingServerBase {
|
|
|
170682
170918
|
await super.initialize();
|
|
170683
170919
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170684
170920
|
this.update(homeAssistant.entity);
|
|
170685
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
170921
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170686
170922
|
}
|
|
170687
170923
|
update(entity) {
|
|
170688
170924
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -170797,7 +171033,7 @@ var SmokeAlarmServerImpl = class extends SmokeAlarmServerWithFeature {
|
|
|
170797
171033
|
await super.initialize();
|
|
170798
171034
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170799
171035
|
this.update(homeAssistant.entity);
|
|
170800
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
171036
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170801
171037
|
}
|
|
170802
171038
|
update(entity) {
|
|
170803
171039
|
const isOn = this.agent.get(HomeAssistantEntityBehavior).isAvailable && entity.state.state === "on";
|
|
@@ -170811,7 +171047,7 @@ var CoAlarmServerImpl = class extends CoAlarmServerWithFeature {
|
|
|
170811
171047
|
await super.initialize();
|
|
170812
171048
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170813
171049
|
this.update(homeAssistant.entity);
|
|
170814
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
171050
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170815
171051
|
}
|
|
170816
171052
|
update(entity) {
|
|
170817
171053
|
const isOn = this.agent.get(HomeAssistantEntityBehavior).isAvailable && entity.state.state === "on";
|
|
@@ -171092,6 +171328,23 @@ var ClimateOnOffServer = OnOffServer2({
|
|
|
171092
171328
|
|
|
171093
171329
|
// src/matter/endpoints/legacy/climate/behaviors/climate-thermostat-server.ts
|
|
171094
171330
|
init_dist();
|
|
171331
|
+
|
|
171332
|
+
// src/utils/converters/snap-to-step.ts
|
|
171333
|
+
function snapToStep(target, current, step) {
|
|
171334
|
+
if (step == null || !Number.isFinite(step) || step <= 0) return target;
|
|
171335
|
+
const ratio = target / step;
|
|
171336
|
+
const lower = Math.floor(ratio) * step;
|
|
171337
|
+
const upper = Math.ceil(ratio) * step;
|
|
171338
|
+
if (lower === upper) return target;
|
|
171339
|
+
const distLower = target - lower;
|
|
171340
|
+
const distUpper = upper - target;
|
|
171341
|
+
if (distLower < distUpper) return lower;
|
|
171342
|
+
if (distUpper < distLower) return upper;
|
|
171343
|
+
if (current == null || !Number.isFinite(current)) return upper;
|
|
171344
|
+
return target > current ? upper : lower;
|
|
171345
|
+
}
|
|
171346
|
+
|
|
171347
|
+
// src/matter/endpoints/legacy/climate/behaviors/climate-thermostat-server.ts
|
|
171095
171348
|
init_home_assistant_entity_behavior();
|
|
171096
171349
|
|
|
171097
171350
|
// src/matter/behaviors/thermostat-server.ts
|
|
@@ -171287,7 +171540,7 @@ var ThermostatServerBase = class extends FullFeaturedBase {
|
|
|
171287
171540
|
// Heat/Cool/Off but SKIPS Auto (see #handleSystemModeChange in Matter.js).
|
|
171288
171541
|
// Without this, switching Heat→Auto leaves runningMode stale at Heat.
|
|
171289
171542
|
// 889010b set for ALL modes (conflicted with reactor), 0678d35 set for NONE
|
|
171290
|
-
// (stale in Auto). da04b2e's Auto-only approach is correct
|
|
171543
|
+
// (stale in Auto). da04b2e's Auto-only approach is correct, the issues
|
|
171291
171544
|
// reported after it were caused by localTemperature fallback, not runningMode.
|
|
171292
171545
|
...this.features.heating && this.features.cooling && systemMode === Thermostat3.SystemMode.Auto ? { thermostatRunningMode: runningMode } : {}
|
|
171293
171546
|
});
|
|
@@ -171572,7 +171825,7 @@ var CoolingOnlyThermostatServerBase = class extends CoolingOnlyFeaturedBase {
|
|
|
171572
171825
|
static State = class extends CoolingOnlyFeaturedBase.State {
|
|
171573
171826
|
config;
|
|
171574
171827
|
};
|
|
171575
|
-
// Each variant MUST define its own initialize()
|
|
171828
|
+
// Each variant MUST define its own initialize(), see HeatingOnly comment above.
|
|
171576
171829
|
async initialize() {
|
|
171577
171830
|
thermostatPreInitialize(this);
|
|
171578
171831
|
await super.initialize();
|
|
@@ -171594,7 +171847,7 @@ var HeatingAndCoolingThermostatServerBase = class extends HeatingAndCoolingFeatu
|
|
|
171594
171847
|
static State = class extends HeatingAndCoolingFeaturedBase.State {
|
|
171595
171848
|
config;
|
|
171596
171849
|
};
|
|
171597
|
-
// Each variant MUST define its own initialize()
|
|
171850
|
+
// Each variant MUST define its own initialize(), see HeatingOnly comment above.
|
|
171598
171851
|
async initialize() {
|
|
171599
171852
|
thermostatPreInitialize(this);
|
|
171600
171853
|
await super.initialize();
|
|
@@ -171675,6 +171928,18 @@ var getTemp = (agent, entity, attributeName) => {
|
|
|
171675
171928
|
return Temperature.withUnit(+temperature3, unit);
|
|
171676
171929
|
}
|
|
171677
171930
|
};
|
|
171931
|
+
var getStep = (entity) => {
|
|
171932
|
+
const raw = attributes4(entity).target_temp_step;
|
|
171933
|
+
if (raw == null) return void 0;
|
|
171934
|
+
const num = typeof raw === "string" ? parseFloat(raw) : raw;
|
|
171935
|
+
return Number.isFinite(num) && num > 0 ? num : void 0;
|
|
171936
|
+
};
|
|
171937
|
+
var getRefTemp = (entity, attr) => {
|
|
171938
|
+
const raw = attributes4(entity)[attr];
|
|
171939
|
+
if (raw == null) return void 0;
|
|
171940
|
+
const num = typeof raw === "string" ? parseFloat(raw) : raw;
|
|
171941
|
+
return Number.isFinite(num) ? num : void 0;
|
|
171942
|
+
};
|
|
171678
171943
|
var systemModeToHvacMode = {
|
|
171679
171944
|
[Thermostat3.SystemMode.Auto]: ClimateHvacMode.heat_cool,
|
|
171680
171945
|
[Thermostat3.SystemMode.Precooling]: ClimateHvacMode.cool,
|
|
@@ -171712,6 +171977,7 @@ function isHeatCoolOnly(modes) {
|
|
|
171712
171977
|
return modes.includes(ClimateHvacMode.heat_cool) && !modes.includes(ClimateHvacMode.heat) && !modes.includes(ClimateHvacMode.cool);
|
|
171713
171978
|
}
|
|
171714
171979
|
var lastHvacDirection = /* @__PURE__ */ new Map();
|
|
171980
|
+
var climateFreezeState = /* @__PURE__ */ new Map();
|
|
171715
171981
|
function getHeatCoolOnlyDirection(entity, agent) {
|
|
171716
171982
|
const action = attributes4(entity).hvac_action;
|
|
171717
171983
|
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
@@ -171726,6 +171992,95 @@ function getHeatCoolOnlyDirection(entity, agent) {
|
|
|
171726
171992
|
}
|
|
171727
171993
|
return lastHvacDirection.get(entityId) ?? "heating";
|
|
171728
171994
|
}
|
|
171995
|
+
function computeSystemMode(entity, agent) {
|
|
171996
|
+
const hvacMode = entity.state;
|
|
171997
|
+
const systemMode = hvacModeToSystemMode[hvacMode] ?? Thermostat3.SystemMode.Off;
|
|
171998
|
+
if (systemMode === Thermostat3.SystemMode.Auto) {
|
|
171999
|
+
const modes = attributes4(entity).hvac_modes ?? [];
|
|
172000
|
+
if (isHeatCoolOnly(modes)) {
|
|
172001
|
+
const direction = getHeatCoolOnlyDirection(entity, agent);
|
|
172002
|
+
return direction === "cooling" ? Thermostat3.SystemMode.Cool : Thermostat3.SystemMode.Heat;
|
|
172003
|
+
}
|
|
172004
|
+
const hasMatterAuto = modes.includes(ClimateHvacMode.heat_cool) && (modes.includes(ClimateHvacMode.heat) || modes.includes(ClimateHvacMode.cool));
|
|
172005
|
+
if (hasMatterAuto) {
|
|
172006
|
+
return systemMode;
|
|
172007
|
+
}
|
|
172008
|
+
const hasCooling = modes.some((m) => m === ClimateHvacMode.cool);
|
|
172009
|
+
const hasHeating = modes.some(
|
|
172010
|
+
(m) => m === ClimateHvacMode.heat || m === ClimateHvacMode.auto
|
|
172011
|
+
);
|
|
172012
|
+
if (hasHeating && !hasCooling) {
|
|
172013
|
+
return Thermostat3.SystemMode.Heat;
|
|
172014
|
+
}
|
|
172015
|
+
if (hasCooling && !hasHeating) {
|
|
172016
|
+
return Thermostat3.SystemMode.Cool;
|
|
172017
|
+
}
|
|
172018
|
+
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
172019
|
+
const entityId = homeAssistant.entityId;
|
|
172020
|
+
const action = attributes4(entity).hvac_action;
|
|
172021
|
+
if (action === ClimateHvacAction.cooling) {
|
|
172022
|
+
lastHvacDirection.set(entityId, "cooling");
|
|
172023
|
+
return Thermostat3.SystemMode.Cool;
|
|
172024
|
+
}
|
|
172025
|
+
if (action === ClimateHvacAction.heating) {
|
|
172026
|
+
lastHvacDirection.set(entityId, "heating");
|
|
172027
|
+
return Thermostat3.SystemMode.Heat;
|
|
172028
|
+
}
|
|
172029
|
+
const remembered = lastHvacDirection.get(entityId);
|
|
172030
|
+
if (remembered) {
|
|
172031
|
+
return remembered === "cooling" ? Thermostat3.SystemMode.Cool : Thermostat3.SystemMode.Heat;
|
|
172032
|
+
}
|
|
172033
|
+
const current = attributes4(entity).current_temperature;
|
|
172034
|
+
const target = attributes4(entity).temperature;
|
|
172035
|
+
if (typeof current === "number" && typeof target === "number") {
|
|
172036
|
+
if (current > target) {
|
|
172037
|
+
lastHvacDirection.set(entityId, "cooling");
|
|
172038
|
+
return Thermostat3.SystemMode.Cool;
|
|
172039
|
+
}
|
|
172040
|
+
if (current < target) {
|
|
172041
|
+
lastHvacDirection.set(entityId, "heating");
|
|
172042
|
+
return Thermostat3.SystemMode.Heat;
|
|
172043
|
+
}
|
|
172044
|
+
}
|
|
172045
|
+
return Thermostat3.SystemMode.Cool;
|
|
172046
|
+
}
|
|
172047
|
+
return systemMode;
|
|
172048
|
+
}
|
|
172049
|
+
function applyClimateFreezeForKeepModeOnIdle(computed, entity, entityId, keepModeOnIdle) {
|
|
172050
|
+
if (computed !== Thermostat3.SystemMode.Off) {
|
|
172051
|
+
const existing2 = climateFreezeState.get(entityId);
|
|
172052
|
+
if (existing2) {
|
|
172053
|
+
existing2.lastNonOffMode = computed;
|
|
172054
|
+
existing2.pending = true;
|
|
172055
|
+
existing2.confirmedOff = false;
|
|
172056
|
+
} else {
|
|
172057
|
+
climateFreezeState.set(entityId, {
|
|
172058
|
+
lastNonOffMode: computed,
|
|
172059
|
+
pending: true,
|
|
172060
|
+
confirmedOff: false
|
|
172061
|
+
});
|
|
172062
|
+
}
|
|
172063
|
+
return computed;
|
|
172064
|
+
}
|
|
172065
|
+
const action = attributes4(entity).hvac_action;
|
|
172066
|
+
const existing = climateFreezeState.get(entityId);
|
|
172067
|
+
if (action === ClimateHvacAction.off) {
|
|
172068
|
+
if (existing) {
|
|
172069
|
+
if (existing.confirmedOff) {
|
|
172070
|
+
existing.pending = false;
|
|
172071
|
+
existing.confirmedOff = false;
|
|
172072
|
+
} else {
|
|
172073
|
+
existing.confirmedOff = true;
|
|
172074
|
+
}
|
|
172075
|
+
}
|
|
172076
|
+
} else if (existing) {
|
|
172077
|
+
existing.confirmedOff = false;
|
|
172078
|
+
}
|
|
172079
|
+
if (keepModeOnIdle && existing?.pending) {
|
|
172080
|
+
return existing.lastNonOffMode;
|
|
172081
|
+
}
|
|
172082
|
+
return computed;
|
|
172083
|
+
}
|
|
171729
172084
|
var config4 = {
|
|
171730
172085
|
// Temperature range (target_temp_low/high) only works in heat_cool mode.
|
|
171731
172086
|
// In heat or cool mode, HA expects a single "temperature" value.
|
|
@@ -171745,37 +172100,14 @@ var config4 = {
|
|
|
171745
172100
|
getTargetHeatingTemperature: (entity, agent) => getTemp(agent, entity, "target_temp_low") ?? getTemp(agent, entity, "target_temperature") ?? getTemp(agent, entity, "temperature"),
|
|
171746
172101
|
getTargetCoolingTemperature: (entity, agent) => getTemp(agent, entity, "target_temp_high") ?? getTemp(agent, entity, "target_temperature") ?? getTemp(agent, entity, "temperature"),
|
|
171747
172102
|
getSystemMode: (entity, agent) => {
|
|
171748
|
-
const
|
|
171749
|
-
const
|
|
171750
|
-
|
|
171751
|
-
|
|
171752
|
-
|
|
171753
|
-
|
|
171754
|
-
|
|
171755
|
-
|
|
171756
|
-
const hasCoolCapability = modes.includes(ClimateHvacMode.cool) || modes.includes(ClimateHvacMode.heat_cool);
|
|
171757
|
-
const hasHeatCapability = modes.includes(ClimateHvacMode.heat) || modes.includes(ClimateHvacMode.heat_cool);
|
|
171758
|
-
const hasMatterAuto = (modes.includes(ClimateHvacMode.heat_cool) || modes.includes(ClimateHvacMode.auto)) && hasCoolCapability && hasHeatCapability;
|
|
171759
|
-
if (hasMatterAuto) {
|
|
171760
|
-
return systemMode;
|
|
171761
|
-
}
|
|
171762
|
-
const hasCooling = modes.some((m) => m === ClimateHvacMode.cool);
|
|
171763
|
-
const hasHeating = modes.some(
|
|
171764
|
-
(m) => m === ClimateHvacMode.heat || m === ClimateHvacMode.auto
|
|
171765
|
-
);
|
|
171766
|
-
if (hasHeating && !hasCooling) {
|
|
171767
|
-
return Thermostat3.SystemMode.Heat;
|
|
171768
|
-
}
|
|
171769
|
-
if (hasCooling && !hasHeating) {
|
|
171770
|
-
return Thermostat3.SystemMode.Cool;
|
|
171771
|
-
}
|
|
171772
|
-
const action = attributes4(entity).hvac_action;
|
|
171773
|
-
if (action === ClimateHvacAction.cooling) {
|
|
171774
|
-
return Thermostat3.SystemMode.Cool;
|
|
171775
|
-
}
|
|
171776
|
-
return Thermostat3.SystemMode.Heat;
|
|
171777
|
-
}
|
|
171778
|
-
return systemMode;
|
|
172103
|
+
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
172104
|
+
const computed = computeSystemMode(entity, agent);
|
|
172105
|
+
return applyClimateFreezeForKeepModeOnIdle(
|
|
172106
|
+
computed,
|
|
172107
|
+
entity,
|
|
172108
|
+
homeAssistant.entityId,
|
|
172109
|
+
homeAssistant.state.mapping?.climateKeepModeOnIdle === true
|
|
172110
|
+
);
|
|
171779
172111
|
},
|
|
171780
172112
|
getRunningMode: (entity) => {
|
|
171781
172113
|
const action = attributes4(entity).hvac_action;
|
|
@@ -171835,19 +172167,37 @@ var config4 = {
|
|
|
171835
172167
|
data: { hvac_mode: targetMode }
|
|
171836
172168
|
};
|
|
171837
172169
|
},
|
|
171838
|
-
setTargetTemperature: (value, agent) =>
|
|
171839
|
-
|
|
171840
|
-
|
|
171841
|
-
|
|
171842
|
-
|
|
171843
|
-
|
|
171844
|
-
|
|
171845
|
-
|
|
171846
|
-
|
|
171847
|
-
|
|
171848
|
-
|
|
171849
|
-
|
|
171850
|
-
|
|
172170
|
+
setTargetTemperature: (value, agent) => {
|
|
172171
|
+
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
172172
|
+
const entity = homeAssistant.entity.state;
|
|
172173
|
+
const unit = getUnit(agent);
|
|
172174
|
+
const target = value.toUnit(unit);
|
|
172175
|
+
const step = getStep(entity);
|
|
172176
|
+
const current = getRefTemp(entity, "temperature") ?? getRefTemp(entity, "target_temperature");
|
|
172177
|
+
return {
|
|
172178
|
+
action: "climate.set_temperature",
|
|
172179
|
+
data: {
|
|
172180
|
+
temperature: snapToStep(target, current, step)
|
|
172181
|
+
}
|
|
172182
|
+
};
|
|
172183
|
+
},
|
|
172184
|
+
setTargetTemperatureRange: ({ low, high }, agent) => {
|
|
172185
|
+
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
172186
|
+
const entity = homeAssistant.entity.state;
|
|
172187
|
+
const unit = getUnit(agent);
|
|
172188
|
+
const step = getStep(entity);
|
|
172189
|
+
const lowTarget = low.toUnit(unit);
|
|
172190
|
+
const highTarget = high.toUnit(unit);
|
|
172191
|
+
const lowCurrent = getRefTemp(entity, "target_temp_low") ?? getRefTemp(entity, "temperature");
|
|
172192
|
+
const highCurrent = getRefTemp(entity, "target_temp_high") ?? getRefTemp(entity, "temperature");
|
|
172193
|
+
return {
|
|
172194
|
+
action: "climate.set_temperature",
|
|
172195
|
+
data: {
|
|
172196
|
+
target_temp_low: snapToStep(lowTarget, lowCurrent, step),
|
|
172197
|
+
target_temp_high: snapToStep(highTarget, highCurrent, step)
|
|
172198
|
+
}
|
|
172199
|
+
};
|
|
172200
|
+
}
|
|
171851
172201
|
};
|
|
171852
172202
|
function ClimateThermostatServer(initialState = {}, features2) {
|
|
171853
172203
|
return ThermostatServer2(config4, initialState, features2);
|
|
@@ -171941,7 +172291,7 @@ function ClimateDevice(homeAssistantEntity) {
|
|
|
171941
172291
|
minCoolSetpointLimit: toMatterTemp(attributes7.min_temp) ?? 0,
|
|
171942
172292
|
maxCoolSetpointLimit: toMatterTemp(attributes7.max_temp) ?? 5e3
|
|
171943
172293
|
};
|
|
171944
|
-
const autoMode = supportsHeating && supportsCooling &&
|
|
172294
|
+
const autoMode = supportsHeating && supportsCooling && attributes7.hvac_modes.includes(ClimateHvacMode.heat_cool) && (attributes7.hvac_modes.includes(ClimateHvacMode.heat) || attributes7.hvac_modes.includes(ClimateHvacMode.cool));
|
|
171945
172295
|
return ClimateDeviceType(
|
|
171946
172296
|
supportsOnOff,
|
|
171947
172297
|
supportsHumidity,
|
|
@@ -172020,6 +172370,18 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172020
172370
|
static DEBOUNCE_INITIAL_MS = 400;
|
|
172021
172371
|
static DEBOUNCE_SUBSEQUENT_MS = 150;
|
|
172022
172372
|
static COMMAND_SEQUENCE_THRESHOLD_MS = 600;
|
|
172373
|
+
// Per-entity override wins over per-bridge flag; both must be > 0 to count.
|
|
172374
|
+
resolveDebounceOverride(homeAssistant) {
|
|
172375
|
+
const fromEntity = homeAssistant.state.mapping?.coverSliderDebounceMs;
|
|
172376
|
+
if (typeof fromEntity === "number" && fromEntity > 0) {
|
|
172377
|
+
return fromEntity;
|
|
172378
|
+
}
|
|
172379
|
+
const fromBridge = this.env.get(BridgeDataProvider).featureFlags?.coverSliderDebounceMs;
|
|
172380
|
+
if (typeof fromBridge === "number" && fromBridge > 0) {
|
|
172381
|
+
return fromBridge;
|
|
172382
|
+
}
|
|
172383
|
+
return null;
|
|
172384
|
+
}
|
|
172023
172385
|
async [Symbol.asyncDispose]() {
|
|
172024
172386
|
if (this.liftDebounceTimer) {
|
|
172025
172387
|
clearTimeout(this.liftDebounceTimer);
|
|
@@ -172034,23 +172396,9 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172034
172396
|
await super[Symbol.asyncDispose]();
|
|
172035
172397
|
}
|
|
172036
172398
|
async initialize() {
|
|
172037
|
-
|
|
172038
|
-
if (this.state.installedOpenLimitLift == null) {
|
|
172039
|
-
this.state.installedOpenLimitLift = 0;
|
|
172040
|
-
}
|
|
172041
|
-
if (this.state.installedClosedLimitLift == null) {
|
|
172042
|
-
this.state.installedClosedLimitLift = 1e4;
|
|
172043
|
-
}
|
|
172044
|
-
}
|
|
172045
|
-
if (this.features.tilt) {
|
|
172046
|
-
if (this.state.installedOpenLimitTilt == null) {
|
|
172047
|
-
this.state.installedOpenLimitTilt = 0;
|
|
172048
|
-
}
|
|
172049
|
-
if (this.state.installedClosedLimitTilt == null) {
|
|
172050
|
-
this.state.installedClosedLimitTilt = 1e4;
|
|
172051
|
-
}
|
|
172052
|
-
}
|
|
172399
|
+
this.internal.disableOperationalModeHandling = true;
|
|
172053
172400
|
if (this.features.positionAwareLift) {
|
|
172401
|
+
this.state.currentPositionLiftPercentage = void 0;
|
|
172054
172402
|
if (this.state.currentPositionLiftPercent100ths === void 0) {
|
|
172055
172403
|
this.state.currentPositionLiftPercent100ths = null;
|
|
172056
172404
|
}
|
|
@@ -172059,6 +172407,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172059
172407
|
}
|
|
172060
172408
|
}
|
|
172061
172409
|
if (this.features.positionAwareTilt) {
|
|
172410
|
+
this.state.currentPositionTiltPercentage = void 0;
|
|
172062
172411
|
if (this.state.currentPositionTiltPercent100ths === void 0) {
|
|
172063
172412
|
this.state.currentPositionTiltPercent100ths = null;
|
|
172064
172413
|
}
|
|
@@ -172066,6 +172415,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172066
172415
|
this.state.targetPositionTiltPercent100ths = null;
|
|
172067
172416
|
}
|
|
172068
172417
|
}
|
|
172418
|
+
this.state.type = this.features.lift && this.features.tilt ? WindowCovering3.WindowCoveringType.TiltBlindLift : this.features.tilt ? WindowCovering3.WindowCoveringType.TiltBlindTiltOnly : WindowCovering3.WindowCoveringType.Rollershade;
|
|
172069
172419
|
await super.initialize();
|
|
172070
172420
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
172071
172421
|
this.update(homeAssistant.entity);
|
|
@@ -172114,41 +172464,39 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172114
172464
|
);
|
|
172115
172465
|
const overrideType = config10.getCoverType?.(state, this.agent);
|
|
172116
172466
|
const overrideEndProduct = config10.getEndProductType?.(state, this.agent);
|
|
172467
|
+
const previousStatus = this.state.operationalStatus?.global;
|
|
172468
|
+
const startedMoving = !isStopped && previousStatus === MovementStatus.Stopped;
|
|
172117
172469
|
const appliedPatch = applyPatchState(
|
|
172118
172470
|
this.state,
|
|
172119
172471
|
{
|
|
172120
172472
|
type: overrideType ?? (this.features.lift && this.features.tilt ? WindowCovering3.WindowCoveringType.TiltBlindLift : this.features.tilt ? WindowCovering3.WindowCoveringType.TiltBlindTiltOnly : WindowCovering3.WindowCoveringType.Rollershade),
|
|
172121
172473
|
endProductType: overrideEndProduct ?? (this.features.lift && this.features.tilt ? WindowCovering3.EndProductType.SheerShade : this.features.tilt ? WindowCovering3.EndProductType.TiltOnlyInteriorBlind : WindowCovering3.EndProductType.RollerShade),
|
|
172122
|
-
operationalStatus
|
|
172123
|
-
|
|
172124
|
-
|
|
172125
|
-
|
|
172126
|
-
},
|
|
172127
|
-
...this.features.absolutePosition && this.features.lift ? {
|
|
172128
|
-
installedOpenLimitLift: 0,
|
|
172129
|
-
installedClosedLimitLift: 1e4,
|
|
172130
|
-
currentPositionLift: currentLift100ths
|
|
172131
|
-
} : {},
|
|
172132
|
-
...this.features.absolutePosition && this.features.tilt ? {
|
|
172133
|
-
installedOpenLimitTilt: 0,
|
|
172134
|
-
installedClosedLimitTilt: 1e4,
|
|
172135
|
-
currentPositionTilt: currentTilt100ths
|
|
172136
|
-
} : {},
|
|
172474
|
+
// Target before operationalStatus so the wire order matches the
|
|
172475
|
+
// certified Eve MotionBlinds (state, target, current). Patch insertion
|
|
172476
|
+
// order propagates into matter.js's changeList via for-in over values
|
|
172477
|
+
// (Datasource.js:414), then through attrsChanged.emit (#328).
|
|
172137
172478
|
...this.features.positionAwareLift ? {
|
|
172138
|
-
currentPositionLiftPercentage: currentLift,
|
|
172139
|
-
currentPositionLiftPercent100ths: currentLift100ths,
|
|
172140
172479
|
targetPositionLiftPercent100ths: inferTarget(
|
|
172141
172480
|
currentLift100ths,
|
|
172142
172481
|
this.state.targetPositionLiftPercent100ths
|
|
172143
172482
|
)
|
|
172144
172483
|
} : {},
|
|
172145
172484
|
...this.features.positionAwareTilt ? {
|
|
172146
|
-
currentPositionTiltPercentage: currentTilt,
|
|
172147
|
-
currentPositionTiltPercent100ths: currentTilt100ths,
|
|
172148
172485
|
targetPositionTiltPercent100ths: inferTarget(
|
|
172149
172486
|
currentTilt100ths,
|
|
172150
172487
|
this.state.targetPositionTiltPercent100ths
|
|
172151
172488
|
)
|
|
172489
|
+
} : {},
|
|
172490
|
+
operationalStatus: {
|
|
172491
|
+
global: movementStatus,
|
|
172492
|
+
...this.features.lift ? { lift: movementStatus } : {},
|
|
172493
|
+
...this.features.tilt ? { tilt: movementStatus } : {}
|
|
172494
|
+
},
|
|
172495
|
+
...this.features.positionAwareLift && !startedMoving ? {
|
|
172496
|
+
currentPositionLiftPercent100ths: currentLift100ths
|
|
172497
|
+
} : {},
|
|
172498
|
+
...this.features.positionAwareTilt && !startedMoving ? {
|
|
172499
|
+
currentPositionTiltPercent100ths: currentTilt100ths
|
|
172152
172500
|
} : {}
|
|
172153
172501
|
}
|
|
172154
172502
|
);
|
|
@@ -172165,7 +172513,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172165
172513
|
const currentLift = this.state.currentPositionLiftPercent100ths ?? 0;
|
|
172166
172514
|
const currentTilt = this.state.currentPositionTiltPercent100ths ?? 0;
|
|
172167
172515
|
logger179.info(
|
|
172168
|
-
`handleMovement: type=${MovementType[type]}, direction=${MovementDirection[direction]}, target=${targetPercent100ths}, currentLift=${currentLift}, currentTilt=${currentTilt}
|
|
172516
|
+
`handleMovement: type=${MovementType[type]}, direction=${MovementDirection[direction]}, target=${targetPercent100ths}, currentLift=${currentLift}, currentTilt=${currentTilt}`
|
|
172169
172517
|
);
|
|
172170
172518
|
if (type === MovementType.Lift) {
|
|
172171
172519
|
this.lastLiftMovementMs = Date.now();
|
|
@@ -172174,7 +172522,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172174
172522
|
this.handleLiftOpen();
|
|
172175
172523
|
} else if (targetPercent100ths === 1e4) {
|
|
172176
172524
|
this.handleLiftClose();
|
|
172177
|
-
} else if (targetPercent100ths != null && this.features.
|
|
172525
|
+
} else if (targetPercent100ths != null && this.features.positionAwareLift) {
|
|
172178
172526
|
this.handleGoToLiftPosition(targetPercent100ths);
|
|
172179
172527
|
} else if (direction === MovementDirection.Open) {
|
|
172180
172528
|
this.handleLiftOpen();
|
|
@@ -172184,7 +172532,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172184
172532
|
} else if (type === MovementType.Tilt) {
|
|
172185
172533
|
if (targetPercent100ths == null && this.lastLiftMovementDirection === direction && Date.now() - this.lastLiftMovementMs < 50) {
|
|
172186
172534
|
logger179.info(
|
|
172187
|
-
`Skipping tilt ${MovementDirection[direction]}
|
|
172535
|
+
`Skipping tilt ${MovementDirection[direction]}, lift already moving in same direction`
|
|
172188
172536
|
);
|
|
172189
172537
|
return;
|
|
172190
172538
|
}
|
|
@@ -172192,7 +172540,7 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172192
172540
|
this.handleTiltOpen();
|
|
172193
172541
|
} else if (targetPercent100ths === 1e4) {
|
|
172194
172542
|
this.handleTiltClose();
|
|
172195
|
-
} else if (targetPercent100ths != null && this.features.
|
|
172543
|
+
} else if (targetPercent100ths != null && this.features.positionAwareTilt) {
|
|
172196
172544
|
this.handleGoToTiltPosition(targetPercent100ths);
|
|
172197
172545
|
} else if (direction === MovementDirection.Open) {
|
|
172198
172546
|
this.handleTiltOpen();
|
|
@@ -172234,9 +172582,10 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172234
172582
|
const timeSinceLastCommand = now - this.lastLiftCommandTime;
|
|
172235
172583
|
this.lastLiftCommandTime = now;
|
|
172236
172584
|
const isFirstInSequence = timeSinceLastCommand > _WindowCoveringServerBase.COMMAND_SEQUENCE_THRESHOLD_MS;
|
|
172237
|
-
const
|
|
172585
|
+
const overrideMs = this.resolveDebounceOverride(homeAssistant);
|
|
172586
|
+
const debounceMs = overrideMs != null ? overrideMs : isFirstInSequence ? _WindowCoveringServerBase.DEBOUNCE_INITIAL_MS : _WindowCoveringServerBase.DEBOUNCE_SUBSEQUENT_MS;
|
|
172238
172587
|
logger179.debug(
|
|
172239
|
-
`Lift command: target=${targetPosition}%, debounce=${debounceMs}ms (${isFirstInSequence ? "initial" : "subsequent"})`
|
|
172588
|
+
`Lift command: target=${targetPosition}%, debounce=${debounceMs}ms (${overrideMs != null ? "override" : isFirstInSequence ? "initial" : "subsequent"})`
|
|
172240
172589
|
);
|
|
172241
172590
|
if (this.liftDebounceTimer) {
|
|
172242
172591
|
clearTimeout(this.liftDebounceTimer);
|
|
@@ -172283,9 +172632,10 @@ var WindowCoveringServerBase = class _WindowCoveringServerBase extends FeaturedB
|
|
|
172283
172632
|
const timeSinceLastCommand = now - this.lastTiltCommandTime;
|
|
172284
172633
|
this.lastTiltCommandTime = now;
|
|
172285
172634
|
const isFirstInSequence = timeSinceLastCommand > _WindowCoveringServerBase.COMMAND_SEQUENCE_THRESHOLD_MS;
|
|
172286
|
-
const
|
|
172635
|
+
const overrideMs = this.resolveDebounceOverride(homeAssistant);
|
|
172636
|
+
const debounceMs = overrideMs != null ? overrideMs : isFirstInSequence ? _WindowCoveringServerBase.DEBOUNCE_INITIAL_MS : _WindowCoveringServerBase.DEBOUNCE_SUBSEQUENT_MS;
|
|
172287
172637
|
logger179.debug(
|
|
172288
|
-
`Tilt command: target=${targetPosition}%, debounce=${debounceMs}ms (${isFirstInSequence ? "initial" : "subsequent"})`
|
|
172638
|
+
`Tilt command: target=${targetPosition}%, debounce=${debounceMs}ms (${overrideMs != null ? "override" : isFirstInSequence ? "initial" : "subsequent"})`
|
|
172289
172639
|
);
|
|
172290
172640
|
if (this.tiltDebounceTimer) {
|
|
172291
172641
|
clearTimeout(this.tiltDebounceTimer);
|
|
@@ -172358,6 +172708,14 @@ var DEVICE_CLASS_TO_MATTER_TYPE = {
|
|
|
172358
172708
|
shade: {
|
|
172359
172709
|
type: WindowCovering3.WindowCoveringType.Rollershade,
|
|
172360
172710
|
endProductType: WindowCovering3.EndProductType.RollerShade
|
|
172711
|
+
},
|
|
172712
|
+
// Velux-style motorized roof/casement windows. No Matter WindowCoveringType
|
|
172713
|
+
// for "window", so map to Rollershade + RollerShade (the spec default pair).
|
|
172714
|
+
// EndProductType is a FixedAttribute, and Alexa's routine picker drops
|
|
172715
|
+
// devices it can't categorize - Unknown (255) lands there (#312).
|
|
172716
|
+
window: {
|
|
172717
|
+
type: WindowCovering3.WindowCoveringType.Rollershade,
|
|
172718
|
+
endProductType: WindowCovering3.EndProductType.RollerShade
|
|
172361
172719
|
}
|
|
172362
172720
|
};
|
|
172363
172721
|
var deviceClassMapping = (entity) => {
|
|
@@ -172366,12 +172724,21 @@ var deviceClassMapping = (entity) => {
|
|
|
172366
172724
|
const mapping = DEVICE_CLASS_TO_MATTER_TYPE[raw.toLowerCase()];
|
|
172367
172725
|
if (!mapping) return void 0;
|
|
172368
172726
|
const supportedFeatures = attributes5(entity).supported_features ?? 0;
|
|
172727
|
+
const hasLift = (supportedFeatures & CoverSupportedFeatures.support_open) !== 0;
|
|
172369
172728
|
const hasTilt = (supportedFeatures & CoverSupportedFeatures.support_open_tilt) !== 0;
|
|
172370
|
-
if (mapping.type === WindowCovering3.WindowCoveringType.TiltBlindTiltOnly
|
|
172371
|
-
|
|
172372
|
-
|
|
172373
|
-
|
|
172374
|
-
|
|
172729
|
+
if (mapping.type === WindowCovering3.WindowCoveringType.TiltBlindTiltOnly) {
|
|
172730
|
+
if (!hasTilt) {
|
|
172731
|
+
return {
|
|
172732
|
+
type: WindowCovering3.WindowCoveringType.Rollershade,
|
|
172733
|
+
endProductType: mapping.endProductType
|
|
172734
|
+
};
|
|
172735
|
+
}
|
|
172736
|
+
if (hasLift) {
|
|
172737
|
+
return {
|
|
172738
|
+
type: WindowCovering3.WindowCoveringType.TiltBlindLift,
|
|
172739
|
+
endProductType: mapping.endProductType
|
|
172740
|
+
};
|
|
172741
|
+
}
|
|
172375
172742
|
}
|
|
172376
172743
|
return mapping;
|
|
172377
172744
|
};
|
|
@@ -172501,24 +172868,17 @@ var CoverWindowCoveringServer = WindowCoveringServer2(config5);
|
|
|
172501
172868
|
|
|
172502
172869
|
// src/matter/endpoints/legacy/cover/index.ts
|
|
172503
172870
|
var logger181 = Logger.get("CoverDevice");
|
|
172504
|
-
var
|
|
172505
|
-
var CoverDeviceType = (supportedFeatures, hasBattery, entityId, isDiscrete) => {
|
|
172871
|
+
var CoverDeviceType = (supportedFeatures, hasBattery, entityId) => {
|
|
172506
172872
|
const features2 = /* @__PURE__ */ new Set();
|
|
172507
172873
|
if (testBit(supportedFeatures, CoverSupportedFeatures.support_open)) {
|
|
172508
172874
|
features2.add("Lift");
|
|
172509
172875
|
features2.add("PositionAwareLift");
|
|
172510
|
-
if (!isDiscrete) {
|
|
172511
|
-
features2.add("AbsolutePosition");
|
|
172512
|
-
}
|
|
172513
172876
|
} else {
|
|
172514
172877
|
logger181.warn(
|
|
172515
172878
|
`[${entityId}] Cover has no support_open feature (supported_features=${supportedFeatures}), adding Lift anyway`
|
|
172516
172879
|
);
|
|
172517
172880
|
features2.add("Lift");
|
|
172518
172881
|
features2.add("PositionAwareLift");
|
|
172519
|
-
if (!isDiscrete) {
|
|
172520
|
-
features2.add("AbsolutePosition");
|
|
172521
|
-
}
|
|
172522
172882
|
}
|
|
172523
172883
|
if (testBit(supportedFeatures, CoverSupportedFeatures.support_open_tilt)) {
|
|
172524
172884
|
features2.add("Tilt");
|
|
@@ -172527,7 +172887,6 @@ var CoverDeviceType = (supportedFeatures, hasBattery, entityId, isDiscrete) => {
|
|
|
172527
172887
|
CoverSupportedFeatures.support_set_tilt_position
|
|
172528
172888
|
)) {
|
|
172529
172889
|
features2.add("PositionAwareTilt");
|
|
172530
|
-
features2.add("AbsolutePosition");
|
|
172531
172890
|
}
|
|
172532
172891
|
}
|
|
172533
172892
|
logger181.info(
|
|
@@ -172562,18 +172921,10 @@ function CoverDevice(homeAssistantEntity) {
|
|
|
172562
172921
|
`[${entityId}] Creating cover without battery (batteryAttr=${hasBatteryAttr}, batteryEntity=${homeAssistantEntity.mapping?.batteryEntity ?? "none"})`
|
|
172563
172922
|
);
|
|
172564
172923
|
}
|
|
172565
|
-
const deviceClass = attributes7.device_class;
|
|
172566
|
-
const isDiscrete = typeof deviceClass === "string" && DISCRETE_COVER_CLASSES.has(deviceClass.toLowerCase());
|
|
172567
|
-
if (isDiscrete) {
|
|
172568
|
-
logger181.info(
|
|
172569
|
-
`[${entityId}] Garage/gate cover (device_class=${deviceClass}): using discrete Open/Close mode`
|
|
172570
|
-
);
|
|
172571
|
-
}
|
|
172572
172924
|
return CoverDeviceType(
|
|
172573
172925
|
attributes7.supported_features ?? 0,
|
|
172574
172926
|
hasBattery,
|
|
172575
|
-
entityId
|
|
172576
|
-
isDiscrete
|
|
172927
|
+
entityId
|
|
172577
172928
|
).set({
|
|
172578
172929
|
homeAssistantEntity
|
|
172579
172930
|
});
|
|
@@ -173640,6 +173991,9 @@ function LightDevice(homeAssistantEntity) {
|
|
|
173640
173991
|
const hasPowerEntity = !!homeAssistantEntity.mapping?.powerEntity;
|
|
173641
173992
|
const hasEnergyEntity = !!homeAssistantEntity.mapping?.energyEntity;
|
|
173642
173993
|
let device = deviceType;
|
|
173994
|
+
if (hasPowerEntity || hasEnergyEntity) {
|
|
173995
|
+
device = device.with(HaPowerTopologyServer);
|
|
173996
|
+
}
|
|
173643
173997
|
if (hasPowerEntity) {
|
|
173644
173998
|
device = device.with(HaElectricalPowerMeasurementServer);
|
|
173645
173999
|
}
|
|
@@ -174603,7 +174957,7 @@ var SpeakerLevelControlServerBase = class extends FeaturedBase8 {
|
|
|
174603
174957
|
*
|
|
174604
174958
|
* The base class registers a reactor on onOff$Changed that sets
|
|
174605
174959
|
* currentLevel = onLevel. This is designed for lights (restore brightness
|
|
174606
|
-
* on power-on) but is wrong for speakers
|
|
174960
|
+
* on power-on) but is wrong for speakers, it overwrites the correct
|
|
174607
174961
|
* volume (e.g. 191 for 75%) with a stale onLevel value, causing Google
|
|
174608
174962
|
* Home to display the wrong percentage (Issue #79).
|
|
174609
174963
|
*/
|
|
@@ -174950,7 +175304,7 @@ var AirQualitySensorServerImpl = class extends AirQualityServerWithFeatures {
|
|
|
174950
175304
|
await super.initialize();
|
|
174951
175305
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
174952
175306
|
this.update(homeAssistant.entity);
|
|
174953
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175307
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
174954
175308
|
}
|
|
174955
175309
|
update(entity) {
|
|
174956
175310
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175061,7 +175415,7 @@ var CarbonMonoxideConcentrationMeasurementServer2 = class extends CarbonMonoxide
|
|
|
175061
175415
|
await super.initialize();
|
|
175062
175416
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175063
175417
|
this.update(homeAssistant.entity);
|
|
175064
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175418
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175065
175419
|
}
|
|
175066
175420
|
update(entity) {
|
|
175067
175421
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175092,7 +175446,7 @@ var CoAirQualityServer = class extends CoAirQualityServerBase {
|
|
|
175092
175446
|
await super.initialize();
|
|
175093
175447
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175094
175448
|
this.update(homeAssistant.entity);
|
|
175095
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175449
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175096
175450
|
}
|
|
175097
175451
|
update(entity) {
|
|
175098
175452
|
const state = entity.state.state;
|
|
@@ -175132,7 +175486,7 @@ var StandalonePowerServer = class extends ElectricalPowerMeasurementServer {
|
|
|
175132
175486
|
await super.initialize();
|
|
175133
175487
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175134
175488
|
this.update(homeAssistant.entity);
|
|
175135
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175489
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175136
175490
|
}
|
|
175137
175491
|
update(entity) {
|
|
175138
175492
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175165,6 +175519,10 @@ var StandalonePowerServer = class extends ElectricalPowerMeasurementServer {
|
|
|
175165
175519
|
var PowerServer = StandalonePowerServer.set({
|
|
175166
175520
|
powerMode: ElectricalPowerMeasurement3.PowerMode.Ac,
|
|
175167
175521
|
numberOfMeasurementTypes: 1,
|
|
175522
|
+
// SmartThings keeps the endpoint in a "device not yet updated" state
|
|
175523
|
+
// when activePower stays null. Seed 0 so the cluster reports a value
|
|
175524
|
+
// for energy/voltage/current-only sensors that never feed activePower.
|
|
175525
|
+
activePower: 0,
|
|
175168
175526
|
accuracy: [
|
|
175169
175527
|
{
|
|
175170
175528
|
measurementType: ElectricalPowerMeasurement3.MeasurementType.ActivePower,
|
|
@@ -175190,7 +175548,7 @@ var StandaloneEnergyServer = class extends EnergyFeaturedBase {
|
|
|
175190
175548
|
await super.initialize();
|
|
175191
175549
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175192
175550
|
this.update(homeAssistant.entity);
|
|
175193
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175551
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175194
175552
|
}
|
|
175195
175553
|
update(entity) {
|
|
175196
175554
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175217,6 +175575,9 @@ var StandaloneEnergyServer = class extends EnergyFeaturedBase {
|
|
|
175217
175575
|
StandaloneEnergyServer2.State = State;
|
|
175218
175576
|
})(StandaloneEnergyServer || (StandaloneEnergyServer = {}));
|
|
175219
175577
|
var EnergyServer = StandaloneEnergyServer.set({
|
|
175578
|
+
// Match the activePower=0 default so SmartThings doesn't show "- kWh"
|
|
175579
|
+
// on a SolarPower endpoint whose mapped entity only carries power data.
|
|
175580
|
+
cumulativeEnergyImported: { energy: 0 },
|
|
175220
175581
|
accuracy: {
|
|
175221
175582
|
measurementType: ElectricalPowerMeasurement3.MeasurementType.ElectricalEnergy,
|
|
175222
175583
|
measured: true,
|
|
@@ -175235,6 +175596,7 @@ var ElectricalSensorType = SolarPowerDevice.with(
|
|
|
175235
175596
|
BasicInformationServer2,
|
|
175236
175597
|
IdentifyServer2,
|
|
175237
175598
|
HomeAssistantEntityBehavior,
|
|
175599
|
+
HaPowerTopologyServer,
|
|
175238
175600
|
PowerServer,
|
|
175239
175601
|
EnergyServer
|
|
175240
175602
|
);
|
|
@@ -175246,7 +175608,7 @@ var FlowMeasurementServerBase = class extends FlowMeasurementServer {
|
|
|
175246
175608
|
await super.initialize();
|
|
175247
175609
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175248
175610
|
this.update(homeAssistant.entity);
|
|
175249
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175611
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175250
175612
|
}
|
|
175251
175613
|
update(entity) {
|
|
175252
175614
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175338,7 +175700,7 @@ var FormaldehydeConcentrationMeasurementServer2 = class extends FormaldehydeConc
|
|
|
175338
175700
|
await super.initialize();
|
|
175339
175701
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175340
175702
|
this.update(homeAssistant.entity);
|
|
175341
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175703
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175342
175704
|
}
|
|
175343
175705
|
update(entity) {
|
|
175344
175706
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175369,7 +175731,7 @@ var FormaldehydeAirQualityServer = class extends FormaldehydeAirQualityServerBas
|
|
|
175369
175731
|
await super.initialize();
|
|
175370
175732
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175371
175733
|
this.update(homeAssistant.entity);
|
|
175372
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175734
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175373
175735
|
}
|
|
175374
175736
|
update(entity) {
|
|
175375
175737
|
const state = entity.state.state;
|
|
@@ -175428,7 +175790,7 @@ var IlluminanceMeasurementServerBase = class extends IlluminanceMeasurementServe
|
|
|
175428
175790
|
await super.initialize();
|
|
175429
175791
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175430
175792
|
this.update(homeAssistant.entity);
|
|
175431
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175793
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175432
175794
|
}
|
|
175433
175795
|
update(entity) {
|
|
175434
175796
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175512,7 +175874,7 @@ var NitrogenDioxideConcentrationMeasurementServer2 = class extends NitrogenDioxi
|
|
|
175512
175874
|
await super.initialize();
|
|
175513
175875
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175514
175876
|
this.update(homeAssistant.entity);
|
|
175515
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175877
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175516
175878
|
}
|
|
175517
175879
|
update(entity) {
|
|
175518
175880
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175542,7 +175904,7 @@ var No2AirQualityServer = class extends No2AirQualityServerBase {
|
|
|
175542
175904
|
await super.initialize();
|
|
175543
175905
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175544
175906
|
this.update(homeAssistant.entity);
|
|
175545
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175907
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175546
175908
|
}
|
|
175547
175909
|
update(entity) {
|
|
175548
175910
|
const state = entity.state.state;
|
|
@@ -175605,7 +175967,7 @@ var OzoneConcentrationMeasurementServer2 = class extends OzoneConcentrationMeasu
|
|
|
175605
175967
|
await super.initialize();
|
|
175606
175968
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175607
175969
|
this.update(homeAssistant.entity);
|
|
175608
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
175970
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175609
175971
|
}
|
|
175610
175972
|
update(entity) {
|
|
175611
175973
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175635,7 +175997,7 @@ var OzoneAirQualityServer = class extends OzoneAirQualityServerBase {
|
|
|
175635
175997
|
await super.initialize();
|
|
175636
175998
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175637
175999
|
this.update(homeAssistant.entity);
|
|
175638
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176000
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175639
176001
|
}
|
|
175640
176002
|
update(entity) {
|
|
175641
176003
|
const state = entity.state.state;
|
|
@@ -175698,7 +176060,7 @@ var Pm1ConcentrationMeasurementServer2 = class extends Pm1ConcentrationMeasureme
|
|
|
175698
176060
|
await super.initialize();
|
|
175699
176061
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175700
176062
|
this.update(homeAssistant.entity);
|
|
175701
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176063
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175702
176064
|
}
|
|
175703
176065
|
update(entity) {
|
|
175704
176066
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175728,7 +176090,7 @@ var Pm1AirQualityServer = class extends Pm1AirQualityServerBase {
|
|
|
175728
176090
|
await super.initialize();
|
|
175729
176091
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175730
176092
|
this.update(homeAssistant.entity);
|
|
175731
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176093
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175732
176094
|
}
|
|
175733
176095
|
update(entity) {
|
|
175734
176096
|
const state = entity.state.state;
|
|
@@ -175811,7 +176173,7 @@ var RadonConcentrationMeasurementServer2 = class extends RadonConcentrationMeasu
|
|
|
175811
176173
|
await super.initialize();
|
|
175812
176174
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175813
176175
|
this.update(homeAssistant.entity);
|
|
175814
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176176
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175815
176177
|
}
|
|
175816
176178
|
update(entity) {
|
|
175817
176179
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -175841,7 +176203,7 @@ var RadonAirQualityServer = class extends RadonAirQualityServerBase {
|
|
|
175841
176203
|
await super.initialize();
|
|
175842
176204
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175843
176205
|
this.update(homeAssistant.entity);
|
|
175844
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176206
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175845
176207
|
}
|
|
175846
176208
|
update(entity) {
|
|
175847
176209
|
const state = entity.state.state;
|
|
@@ -175959,7 +176321,7 @@ var TvocConcentrationMeasurementServer = class extends TvocConcentrationMeasurem
|
|
|
175959
176321
|
await super.initialize();
|
|
175960
176322
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
175961
176323
|
this.update(homeAssistant.entity);
|
|
175962
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176324
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
175963
176325
|
}
|
|
175964
176326
|
update(entity) {
|
|
175965
176327
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -176009,7 +176371,7 @@ var TvocAirQualityServer = class extends TvocAirQualityServerBase {
|
|
|
176009
176371
|
await super.initialize();
|
|
176010
176372
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
176011
176373
|
this.update(homeAssistant.entity);
|
|
176012
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176374
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
176013
176375
|
}
|
|
176014
176376
|
update(entity) {
|
|
176015
176377
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -176076,7 +176438,7 @@ var CarbonDioxideConcentrationMeasurementServer2 = class extends CarbonDioxideCo
|
|
|
176076
176438
|
await super.initialize();
|
|
176077
176439
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
176078
176440
|
this.update(homeAssistant.entity);
|
|
176079
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176441
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
176080
176442
|
}
|
|
176081
176443
|
update(entity) {
|
|
176082
176444
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -176109,7 +176471,7 @@ var Co2AirQualityServer = class extends Co2AirQualityServerBase {
|
|
|
176109
176471
|
await super.initialize();
|
|
176110
176472
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
176111
176473
|
this.update(homeAssistant.entity);
|
|
176112
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176474
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
176113
176475
|
}
|
|
176114
176476
|
update(entity) {
|
|
176115
176477
|
const state = entity.state.state;
|
|
@@ -176172,7 +176534,7 @@ var Pm10ConcentrationMeasurementServer2 = class extends Pm10ConcentrationMeasure
|
|
|
176172
176534
|
await super.initialize();
|
|
176173
176535
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
176174
176536
|
this.update(homeAssistant.entity);
|
|
176175
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176537
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
176176
176538
|
}
|
|
176177
176539
|
update(entity) {
|
|
176178
176540
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -176204,7 +176566,7 @@ var Pm10AirQualityServer = class extends Pm10AirQualityServerBase {
|
|
|
176204
176566
|
await super.initialize();
|
|
176205
176567
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
176206
176568
|
this.update(homeAssistant.entity);
|
|
176207
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176569
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
176208
176570
|
}
|
|
176209
176571
|
update(entity) {
|
|
176210
176572
|
const state = entity.state.state;
|
|
@@ -176276,7 +176638,7 @@ var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasure
|
|
|
176276
176638
|
);
|
|
176277
176639
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
176278
176640
|
this.update(homeAssistant.entity);
|
|
176279
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176641
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
176280
176642
|
}
|
|
176281
176643
|
update(entity) {
|
|
176282
176644
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -176311,7 +176673,7 @@ var Pm25AirQualityServer = class extends Pm25AirQualityServerBase {
|
|
|
176311
176673
|
logger189.debug("Pm25AirQualityServer: after super.initialize()");
|
|
176312
176674
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
176313
176675
|
this.update(homeAssistant.entity);
|
|
176314
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
176676
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
176315
176677
|
}
|
|
176316
176678
|
update(entity) {
|
|
176317
176679
|
const state = entity.state.state;
|
|
@@ -176608,7 +176970,7 @@ function SensorDevice(homeAssistantEntity) {
|
|
|
176608
176970
|
if (deviceClass) {
|
|
176609
176971
|
diagnosticEventBus.emit(
|
|
176610
176972
|
"entity_warning",
|
|
176611
|
-
`Sensor "${homeAssistantEntity.entity.entity_id}" has unsupported device_class "${deviceClass}"
|
|
176973
|
+
`Sensor "${homeAssistantEntity.entity.entity_id}" has unsupported device_class "${deviceClass}", skipped`,
|
|
176612
176974
|
{
|
|
176613
176975
|
entityId: homeAssistantEntity.entity.entity_id,
|
|
176614
176976
|
details: { device_class: deviceClass }
|
|
@@ -176677,6 +177039,9 @@ function SwitchDevice(homeAssistantEntity) {
|
|
|
176677
177039
|
const hasPowerEntity = !!homeAssistantEntity.mapping?.powerEntity;
|
|
176678
177040
|
const hasEnergyEntity = !!homeAssistantEntity.mapping?.energyEntity;
|
|
176679
177041
|
let device = hasBatteryAttr || hasBatteryEntity ? SwitchWithBatteryEndpointType : SwitchEndpointType;
|
|
177042
|
+
if (hasPowerEntity || hasEnergyEntity) {
|
|
177043
|
+
device = device.with(HaPowerTopologyServer);
|
|
177044
|
+
}
|
|
176680
177045
|
if (hasPowerEntity) {
|
|
176681
177046
|
device = device.with(HaElectricalPowerMeasurementServer);
|
|
176682
177047
|
}
|
|
@@ -176695,6 +177060,7 @@ init_dist();
|
|
|
176695
177060
|
init_esm();
|
|
176696
177061
|
init_home_assistant_entity_behavior();
|
|
176697
177062
|
var logger190 = Logger.get("VacuumIdentifyServer");
|
|
177063
|
+
var IDENTIFY_BUTTON_SUFFIXES = ["_identify", "_locate", "_find_me"];
|
|
176698
177064
|
var VacuumIdentifyServer = class extends IdentifyServer2 {
|
|
176699
177065
|
triggerEffect(effect) {
|
|
176700
177066
|
this.#locate("triggerEffect");
|
|
@@ -176709,16 +177075,40 @@ var VacuumIdentifyServer = class extends IdentifyServer2 {
|
|
|
176709
177075
|
#locate(source) {
|
|
176710
177076
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
176711
177077
|
const features2 = homeAssistant.entity.state.attributes.supported_features ?? 0;
|
|
176712
|
-
|
|
176713
|
-
if (!hasLocate) {
|
|
176714
|
-
logger190.warn(
|
|
176715
|
-
`${source} for ${homeAssistant.entityId} \u2014 LOCATE not in supported_features (${features2}), calling vacuum.locate anyway`
|
|
176716
|
-
);
|
|
176717
|
-
} else {
|
|
177078
|
+
if (testBit(features2, VacuumDeviceFeature.LOCATE)) {
|
|
176718
177079
|
logger190.info(`${source} \u2192 vacuum.locate for ${homeAssistant.entityId}`);
|
|
177080
|
+
homeAssistant.callAction({ action: "vacuum.locate" });
|
|
177081
|
+
return;
|
|
177082
|
+
}
|
|
177083
|
+
const sibling = this.#findIdentifyButton(homeAssistant);
|
|
177084
|
+
if (sibling) {
|
|
177085
|
+
logger190.info(
|
|
177086
|
+
`${source} \u2192 button.press ${sibling} for ${homeAssistant.entityId}`
|
|
177087
|
+
);
|
|
177088
|
+
homeAssistant.callAction({ action: "button.press", target: sibling });
|
|
177089
|
+
return;
|
|
176719
177090
|
}
|
|
177091
|
+
logger190.warn(
|
|
177092
|
+
`${source} for ${homeAssistant.entityId}, LOCATE not in supported_features (${features2}), trying vacuum.locate anyway`
|
|
177093
|
+
);
|
|
176720
177094
|
homeAssistant.callAction({ action: "vacuum.locate" });
|
|
176721
177095
|
}
|
|
177096
|
+
#findIdentifyButton(homeAssistant) {
|
|
177097
|
+
const deviceId = homeAssistant.entity.registry?.device_id;
|
|
177098
|
+
if (!deviceId) return void 0;
|
|
177099
|
+
const registry2 = this.env.get(HomeAssistantRegistry);
|
|
177100
|
+
for (const entity of Object.values(registry2.entities)) {
|
|
177101
|
+
if (entity.device_id !== deviceId) continue;
|
|
177102
|
+
if (!entity.entity_id.startsWith("button.")) continue;
|
|
177103
|
+
const uniqueId = entity.unique_id ?? "";
|
|
177104
|
+
if (IDENTIFY_BUTTON_SUFFIXES.some(
|
|
177105
|
+
(s) => entity.entity_id.endsWith(s) || uniqueId.endsWith(s)
|
|
177106
|
+
)) {
|
|
177107
|
+
return entity.entity_id;
|
|
177108
|
+
}
|
|
177109
|
+
}
|
|
177110
|
+
return void 0;
|
|
177111
|
+
}
|
|
176722
177112
|
};
|
|
176723
177113
|
|
|
176724
177114
|
// src/matter/behaviors/rvc-run-mode-server.ts
|
|
@@ -176747,7 +177137,9 @@ function getSession(endpoint) {
|
|
|
176747
177137
|
completedAreas: /* @__PURE__ */ new Set(),
|
|
176748
177138
|
lastCurrentArea: null,
|
|
176749
177139
|
activeAreas: [],
|
|
176750
|
-
loggedShortCircuits: /* @__PURE__ */ new Set()
|
|
177140
|
+
loggedShortCircuits: /* @__PURE__ */ new Set(),
|
|
177141
|
+
observedCleaning: false,
|
|
177142
|
+
pendingDispatches: []
|
|
176751
177143
|
};
|
|
176752
177144
|
cleaningSessions.set(endpoint, session);
|
|
176753
177145
|
}
|
|
@@ -176778,17 +177170,45 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
|
|
|
176778
177170
|
},
|
|
176779
177171
|
{ force: true }
|
|
176780
177172
|
);
|
|
177173
|
+
if (newMode === 1 /* Cleaning */) {
|
|
177174
|
+
s.observedCleaning = true;
|
|
177175
|
+
}
|
|
176781
177176
|
if (previousMode !== newMode) {
|
|
176782
177177
|
if (newMode === 0 /* Idle */) {
|
|
176783
177178
|
if (s.lastCurrentArea !== null) {
|
|
176784
177179
|
s.completedAreas.add(s.lastCurrentArea);
|
|
176785
177180
|
s.lastCurrentArea = null;
|
|
177181
|
+
}
|
|
177182
|
+
if (s.pendingDispatches.length > 0 && s.observedCleaning) {
|
|
177183
|
+
try {
|
|
177184
|
+
const serviceArea = this.agent.get(ServiceAreaBehavior);
|
|
177185
|
+
const prev = serviceArea.state.currentArea;
|
|
177186
|
+
if (typeof prev === "number") {
|
|
177187
|
+
s.completedAreas.add(prev);
|
|
177188
|
+
}
|
|
177189
|
+
const next = s.pendingDispatches.shift();
|
|
177190
|
+
if (next) {
|
|
177191
|
+
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
177192
|
+
homeAssistant.callAction(next.action);
|
|
177193
|
+
this.trySetCurrentArea(next.areaId);
|
|
177194
|
+
}
|
|
177195
|
+
} catch {
|
|
177196
|
+
}
|
|
177197
|
+
s.loggedShortCircuits.clear();
|
|
177198
|
+
return;
|
|
177199
|
+
}
|
|
177200
|
+
if (s.observedCleaning) {
|
|
176786
177201
|
try {
|
|
176787
177202
|
const serviceArea = this.agent.get(ServiceAreaBehavior);
|
|
176788
|
-
serviceArea.state.currentArea
|
|
177203
|
+
const last = serviceArea.state.currentArea;
|
|
177204
|
+
if (typeof last === "number") {
|
|
177205
|
+
s.completedAreas.add(last);
|
|
177206
|
+
serviceArea.state.currentArea = null;
|
|
177207
|
+
}
|
|
176789
177208
|
this.updateProgressFromTracking(serviceArea);
|
|
176790
177209
|
} catch {
|
|
176791
177210
|
}
|
|
177211
|
+
s.observedCleaning = false;
|
|
176792
177212
|
}
|
|
176793
177213
|
s.loggedShortCircuits.clear();
|
|
176794
177214
|
} else if (newMode === 1 /* Cleaning */) {
|
|
@@ -176802,6 +177222,15 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
|
|
|
176802
177222
|
}
|
|
176803
177223
|
}
|
|
176804
177224
|
}
|
|
177225
|
+
if (newMode === 0 /* Idle */ && s.activeAreas.length === 0) {
|
|
177226
|
+
try {
|
|
177227
|
+
const serviceArea = this.agent.get(ServiceAreaBehavior);
|
|
177228
|
+
if (serviceArea.state.currentArea !== null) {
|
|
177229
|
+
serviceArea.state.currentArea = null;
|
|
177230
|
+
}
|
|
177231
|
+
} catch {
|
|
177232
|
+
}
|
|
177233
|
+
}
|
|
176805
177234
|
if (newMode === 1 /* Cleaning */) {
|
|
176806
177235
|
this.updateCurrentRoomFromSensor();
|
|
176807
177236
|
}
|
|
@@ -176829,7 +177258,7 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
|
|
|
176829
177258
|
if (!currentRoomEntityId) {
|
|
176830
177259
|
this.logShortCircuitOnce(
|
|
176831
177260
|
"no-mapping",
|
|
176832
|
-
"currentRoom sensor: no currentRoomEntity in mapping
|
|
177261
|
+
"currentRoom sensor: no currentRoomEntity in mapping, auto-detect did not run or sensor not on same HA device"
|
|
176833
177262
|
);
|
|
176834
177263
|
return;
|
|
176835
177264
|
}
|
|
@@ -176842,26 +177271,22 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
|
|
|
176842
177271
|
);
|
|
176843
177272
|
return;
|
|
176844
177273
|
}
|
|
176845
|
-
if (s.activeAreas.length === 0) {
|
|
176846
|
-
this.logShortCircuitOnce(
|
|
176847
|
-
"no-active-areas",
|
|
176848
|
-
`currentRoom sensor: activeAreas empty while cleaning \u2014 sensor=${currentRoomEntityId} state="${roomState.state}"`
|
|
176849
|
-
);
|
|
176850
|
-
return;
|
|
176851
|
-
}
|
|
176852
177274
|
const serviceArea = this.agent.get(ServiceAreaBehavior);
|
|
177275
|
+
const externalSession = s.activeAreas.length === 0;
|
|
177276
|
+
const supportedAreaIds = serviceArea.state.supportedAreas.map(
|
|
177277
|
+
(a) => a.areaId
|
|
177278
|
+
);
|
|
177279
|
+
const isAllowedArea = (id) => externalSession ? supportedAreaIds.includes(id) : s.activeAreas.includes(id);
|
|
176853
177280
|
const sensorAttrs = roomState.attributes;
|
|
176854
177281
|
const segmentId = sensorAttrs.segment_id ?? sensorAttrs.room_id;
|
|
176855
177282
|
const roomName = roomState.state;
|
|
176856
177283
|
let matchedAreaId = null;
|
|
176857
|
-
if (segmentId != null) {
|
|
176858
|
-
|
|
176859
|
-
matchedAreaId = segmentId;
|
|
176860
|
-
}
|
|
177284
|
+
if (segmentId != null && isAllowedArea(segmentId)) {
|
|
177285
|
+
matchedAreaId = segmentId;
|
|
176861
177286
|
}
|
|
176862
177287
|
if (matchedAreaId === null && segmentId != null) {
|
|
176863
177288
|
for (const area of serviceArea.state.supportedAreas) {
|
|
176864
|
-
if (
|
|
177289
|
+
if (isAllowedArea(area.areaId) && area.areaId % 1e4 === segmentId) {
|
|
176865
177290
|
matchedAreaId = area.areaId;
|
|
176866
177291
|
break;
|
|
176867
177292
|
}
|
|
@@ -176871,7 +177296,7 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
|
|
|
176871
177296
|
const area = serviceArea.state.supportedAreas.find(
|
|
176872
177297
|
(a) => a.areaInfo.locationInfo?.locationName?.toLowerCase() === roomName.toLowerCase()
|
|
176873
177298
|
);
|
|
176874
|
-
if (area &&
|
|
177299
|
+
if (area && isAllowedArea(area.areaId)) {
|
|
176875
177300
|
matchedAreaId = area.areaId;
|
|
176876
177301
|
}
|
|
176877
177302
|
}
|
|
@@ -176990,6 +177415,7 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
|
|
|
176990
177415
|
s.completedAreas.clear();
|
|
176991
177416
|
s.lastCurrentArea = null;
|
|
176992
177417
|
s.loggedShortCircuits.clear();
|
|
177418
|
+
s.pendingDispatches = [];
|
|
176993
177419
|
this.trySetCurrentArea(s.activeAreas[0]);
|
|
176994
177420
|
homeAssistant.callAction(this.state.config.start(void 0, this.agent));
|
|
176995
177421
|
this.state.currentMode = newMode;
|
|
@@ -177006,6 +177432,7 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
|
|
|
177006
177432
|
s.completedAreas.clear();
|
|
177007
177433
|
s.lastCurrentArea = null;
|
|
177008
177434
|
s.loggedShortCircuits.clear();
|
|
177435
|
+
s.pendingDispatches = [];
|
|
177009
177436
|
this.trySetCurrentArea(areaId);
|
|
177010
177437
|
homeAssistant.callAction(
|
|
177011
177438
|
this.state.config.cleanRoom(newMode, this.agent)
|
|
@@ -177026,6 +177453,7 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
|
|
|
177026
177453
|
s.completedAreas.clear();
|
|
177027
177454
|
s.lastCurrentArea = null;
|
|
177028
177455
|
s.loggedShortCircuits.clear();
|
|
177456
|
+
s.pendingDispatches = [];
|
|
177029
177457
|
this.trySetCurrentArea(s.activeAreas[0]);
|
|
177030
177458
|
}
|
|
177031
177459
|
} catch {
|
|
@@ -177039,6 +177467,8 @@ var RvcRunModeServerBase = class extends RvcRunModeServer {
|
|
|
177039
177467
|
s.lastCurrentArea = null;
|
|
177040
177468
|
s.activeAreas = [];
|
|
177041
177469
|
s.loggedShortCircuits.clear();
|
|
177470
|
+
s.pendingDispatches = [];
|
|
177471
|
+
s.observedCleaning = false;
|
|
177042
177472
|
homeAssistant.callAction(
|
|
177043
177473
|
this.state.config.returnToBase(void 0, this.agent)
|
|
177044
177474
|
);
|
|
@@ -177615,8 +178045,8 @@ function buildSupportedModes2(attributes7, includeUnnamedRooms = false, customAr
|
|
|
177615
178045
|
}
|
|
177616
178046
|
return modes;
|
|
177617
178047
|
}
|
|
177618
|
-
function handleCustomServiceAreas(selectedAreas, customAreas,
|
|
177619
|
-
const matched = selectedAreas.map((areaId) => customAreas[areaId - 1]).filter(
|
|
178048
|
+
function handleCustomServiceAreas(selectedAreas, customAreas, session) {
|
|
178049
|
+
const matched = selectedAreas.map((areaId) => ({ areaId, area: customAreas[areaId - 1] })).filter((m) => !!m.area);
|
|
177620
178050
|
if (matched.length === 0) {
|
|
177621
178051
|
logger194.warn(
|
|
177622
178052
|
`Custom service areas: no match for selected IDs ${selectedAreas.join(", ")}`
|
|
@@ -177624,17 +178054,13 @@ function handleCustomServiceAreas(selectedAreas, customAreas, homeAssistant) {
|
|
|
177624
178054
|
return { action: "vacuum.start" };
|
|
177625
178055
|
}
|
|
177626
178056
|
logger194.info(
|
|
177627
|
-
`Custom service areas:
|
|
178057
|
+
`Custom service areas: ${matched.length} room(s) queued: ${matched.map(({ area }) => `${area.service} (${area.name})`).join(", ")}`
|
|
177628
178058
|
);
|
|
177629
|
-
|
|
177630
|
-
|
|
177631
|
-
|
|
177632
|
-
|
|
177633
|
-
|
|
177634
|
-
data: area.data
|
|
177635
|
-
});
|
|
177636
|
-
}
|
|
177637
|
-
const first = matched[0];
|
|
178059
|
+
session.pendingDispatches = matched.slice(1).map(({ areaId, area }) => ({
|
|
178060
|
+
areaId,
|
|
178061
|
+
action: { action: area.service, target: area.target, data: area.data }
|
|
178062
|
+
}));
|
|
178063
|
+
const first = matched[0].area;
|
|
177638
178064
|
return {
|
|
177639
178065
|
action: first.service,
|
|
177640
178066
|
target: first.target,
|
|
@@ -177668,9 +178094,14 @@ var vacuumRvcRunModeConfig = {
|
|
|
177668
178094
|
);
|
|
177669
178095
|
return isCleaning ? 1 /* Cleaning */ : 0 /* Idle */;
|
|
177670
178096
|
},
|
|
177671
|
-
getSupportedModes: (entity) => {
|
|
178097
|
+
getSupportedModes: (entity, agent) => {
|
|
177672
178098
|
const attributes7 = entity.attributes;
|
|
177673
|
-
|
|
178099
|
+
const customAreas = agent.get(HomeAssistantEntityBehavior).state.mapping?.customServiceAreas;
|
|
178100
|
+
return buildSupportedModes2(
|
|
178101
|
+
attributes7,
|
|
178102
|
+
false,
|
|
178103
|
+
customAreas && customAreas.length > 0 ? customAreas : void 0
|
|
178104
|
+
);
|
|
177674
178105
|
},
|
|
177675
178106
|
// biome-ignore lint/suspicious/noConfusingVoidType: Required by ValueSetter<void> interface
|
|
177676
178107
|
start: (_, agent) => {
|
|
@@ -177681,13 +178112,10 @@ var vacuumRvcRunModeConfig = {
|
|
|
177681
178112
|
const homeAssistant = agent.get(HomeAssistantEntityBehavior);
|
|
177682
178113
|
const entity = homeAssistant.entity;
|
|
177683
178114
|
const attributes7 = entity.state.attributes;
|
|
178115
|
+
const session = getSession(homeAssistant.endpoint);
|
|
177684
178116
|
const customAreas = homeAssistant.state.mapping?.customServiceAreas;
|
|
177685
178117
|
if (customAreas && customAreas.length > 0) {
|
|
177686
|
-
return handleCustomServiceAreas(
|
|
177687
|
-
selectedAreas,
|
|
177688
|
-
customAreas,
|
|
177689
|
-
homeAssistant
|
|
177690
|
-
);
|
|
178118
|
+
return handleCustomServiceAreas(selectedAreas, customAreas, session);
|
|
177691
178119
|
}
|
|
177692
178120
|
const cleanAreaRooms = homeAssistant.state.mapping?.cleanAreaRooms;
|
|
177693
178121
|
if (cleanAreaRooms && cleanAreaRooms.length > 0) {
|
|
@@ -177704,28 +178132,24 @@ var vacuumRvcRunModeConfig = {
|
|
|
177704
178132
|
}
|
|
177705
178133
|
const roomEntities = homeAssistant.state.mapping?.roomEntities;
|
|
177706
178134
|
if (roomEntities && roomEntities.length > 0) {
|
|
177707
|
-
const
|
|
178135
|
+
const matched = [];
|
|
177708
178136
|
for (const areaId of selectedAreas) {
|
|
177709
|
-
const
|
|
177710
|
-
|
|
177711
|
-
|
|
177712
|
-
if (buttonEntityId) {
|
|
177713
|
-
buttonEntityIds.push(buttonEntityId);
|
|
178137
|
+
const entityId = roomEntities.find((id) => toAreaId(id) === areaId);
|
|
178138
|
+
if (entityId) {
|
|
178139
|
+
matched.push({ areaId, entityId });
|
|
177714
178140
|
}
|
|
177715
178141
|
}
|
|
177716
|
-
if (
|
|
178142
|
+
if (matched.length > 0) {
|
|
177717
178143
|
logger194.info(
|
|
177718
|
-
`Roborock:
|
|
178144
|
+
`Roborock: ${matched.length} room button(s) queued: ${matched.map((m) => m.entityId).join(", ")}`
|
|
177719
178145
|
);
|
|
177720
|
-
|
|
177721
|
-
|
|
177722
|
-
|
|
177723
|
-
|
|
177724
|
-
});
|
|
177725
|
-
}
|
|
178146
|
+
session.pendingDispatches = matched.slice(1).map(({ areaId, entityId }) => ({
|
|
178147
|
+
areaId,
|
|
178148
|
+
action: { action: "button.press", target: entityId }
|
|
178149
|
+
}));
|
|
177726
178150
|
return {
|
|
177727
178151
|
action: "button.press",
|
|
177728
|
-
target:
|
|
178152
|
+
target: matched[0].entityId
|
|
177729
178153
|
};
|
|
177730
178154
|
}
|
|
177731
178155
|
}
|
|
@@ -178062,7 +178486,7 @@ var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeS
|
|
|
178062
178486
|
await super.initialize();
|
|
178063
178487
|
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
178064
178488
|
this.update(homeAssistant.entity);
|
|
178065
|
-
this.reactTo(homeAssistant.onChange, this.update);
|
|
178489
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
178066
178490
|
}
|
|
178067
178491
|
update(entity) {
|
|
178068
178492
|
if (!entity.state || !entity.state.attributes) {
|
|
@@ -178081,13 +178505,17 @@ var RvcCleanModeServerBase = class _RvcCleanModeServerBase extends RvcCleanModeS
|
|
|
178081
178505
|
currentMode = this.pendingMode;
|
|
178082
178506
|
}
|
|
178083
178507
|
}
|
|
178084
|
-
applyPatchState(
|
|
178085
|
-
|
|
178086
|
-
|
|
178087
|
-
|
|
178088
|
-
this.
|
|
178089
|
-
|
|
178090
|
-
|
|
178508
|
+
applyPatchState(
|
|
178509
|
+
this.state,
|
|
178510
|
+
{
|
|
178511
|
+
currentMode,
|
|
178512
|
+
supportedModes: this.state.config.getSupportedModes(
|
|
178513
|
+
entity.state,
|
|
178514
|
+
this.agent
|
|
178515
|
+
)
|
|
178516
|
+
},
|
|
178517
|
+
{ force: true }
|
|
178518
|
+
);
|
|
178091
178519
|
}
|
|
178092
178520
|
changeToMode(request) {
|
|
178093
178521
|
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
|
|
@@ -178210,33 +178638,24 @@ function buildSupportedModes3(fanSpeedList, mopIntensityList, cleaningModeOption
|
|
|
178210
178638
|
}
|
|
178211
178639
|
return modes;
|
|
178212
178640
|
}
|
|
178213
|
-
var
|
|
178214
|
-
|
|
178215
|
-
|
|
178216
|
-
|
|
178217
|
-
"Vacuuming",
|
|
178218
|
-
"Sweep",
|
|
178219
|
-
"vacuum",
|
|
178220
|
-
"sweeping"
|
|
178221
|
-
],
|
|
178222
|
-
[1 /* Mopping */]: ["Mopping", "Mop", "mopping", "mop", "wet_mop"],
|
|
178223
|
-
[2 /* SweepingAndMopping */]: [
|
|
178224
|
-
"Sweeping and mopping",
|
|
178225
|
-
"Vacuum and mop",
|
|
178226
|
-
"Vacuum & Mop",
|
|
178227
|
-
"Vacuum & mop",
|
|
178228
|
-
"vacuum_and_mop",
|
|
178229
|
-
"sweeping_and_mopping"
|
|
178230
|
-
],
|
|
178231
|
-
[3 /* MoppingAfterSweeping */]: [
|
|
178232
|
-
"Mopping after sweeping",
|
|
178233
|
-
"mopping_after_sweeping",
|
|
178234
|
-
"Vacuum then mop",
|
|
178235
|
-
"Mop after vacuum",
|
|
178236
|
-
"vacuum_then_mop",
|
|
178237
|
-
"mop_after_vacuum"
|
|
178238
|
-
]
|
|
178641
|
+
var CLEAN_TOKENS = {
|
|
178642
|
+
vacuum: /\b(vacuum|vacuuming|sweep|sweeping|suction)\b/i,
|
|
178643
|
+
mop: /\b(mop|mopping|wipe|wet|wash|scrub)\b/i,
|
|
178644
|
+
sequential: /\b(then|after|before|followed|following|first|secondly|sequentially)\b/i
|
|
178239
178645
|
};
|
|
178646
|
+
function normalizeCleanLabel(s) {
|
|
178647
|
+
return s.toLowerCase().replace(/[_\-+&/. ]+/g, " ").replace(/\band\b/g, " ").trim();
|
|
178648
|
+
}
|
|
178649
|
+
function classifyCleanOption(option) {
|
|
178650
|
+
const s = normalizeCleanLabel(option);
|
|
178651
|
+
const hasVac = CLEAN_TOKENS.vacuum.test(s);
|
|
178652
|
+
const hasMop = CLEAN_TOKENS.mop.test(s);
|
|
178653
|
+
const hasSeq = CLEAN_TOKENS.sequential.test(s);
|
|
178654
|
+
if (hasVac && hasMop && hasSeq) return 3 /* MoppingAfterSweeping */;
|
|
178655
|
+
if (hasVac && hasMop) return 2 /* SweepingAndMopping */;
|
|
178656
|
+
if (hasMop) return 1 /* Mopping */;
|
|
178657
|
+
return 0 /* Sweeping */;
|
|
178658
|
+
}
|
|
178240
178659
|
var CLEAN_TYPE_LABELS = {
|
|
178241
178660
|
[0 /* Sweeping */]: "Sweeping",
|
|
178242
178661
|
[1 /* Mopping */]: "Mopping",
|
|
@@ -178369,17 +178788,7 @@ function matchMopIntensityOption(name, availableOptions) {
|
|
|
178369
178788
|
}
|
|
178370
178789
|
function parseCleanType(modeString) {
|
|
178371
178790
|
if (!modeString) return 0 /* Sweeping */;
|
|
178372
|
-
|
|
178373
|
-
if (s.includes("mopping after") || s.includes("after sweeping") || s.includes("then_mop") || s.includes("then mop")) {
|
|
178374
|
-
return 3 /* MoppingAfterSweeping */;
|
|
178375
|
-
}
|
|
178376
|
-
if (s.includes("and") || s.includes("sweeping and mopping")) {
|
|
178377
|
-
return 2 /* SweepingAndMopping */;
|
|
178378
|
-
}
|
|
178379
|
-
if (s === "mopping" || s.includes("mop")) {
|
|
178380
|
-
return 1 /* Mopping */;
|
|
178381
|
-
}
|
|
178382
|
-
return 0 /* Sweeping */;
|
|
178791
|
+
return classifyCleanOption(modeString);
|
|
178383
178792
|
}
|
|
178384
178793
|
function cleanTypeToModeId(ct) {
|
|
178385
178794
|
switch (ct) {
|
|
@@ -178420,30 +178829,20 @@ var CLEAN_TYPE_FALLBACK = {
|
|
|
178420
178829
|
[3 /* MoppingAfterSweeping */]: 2 /* SweepingAndMopping */
|
|
178421
178830
|
};
|
|
178422
178831
|
function findMatchingCleanOption(ct, availableOptions) {
|
|
178423
|
-
|
|
178424
|
-
|
|
178832
|
+
if (!availableOptions || availableOptions.length === 0) {
|
|
178833
|
+
return CLEAN_TYPE_LABELS[ct];
|
|
178834
|
+
}
|
|
178425
178835
|
const typesToTry = [ct];
|
|
178426
178836
|
const fallback = CLEAN_TYPE_FALLBACK[ct];
|
|
178427
178837
|
if (fallback !== void 0) typesToTry.push(fallback);
|
|
178428
178838
|
for (const type of typesToTry) {
|
|
178429
|
-
const
|
|
178430
|
-
|
|
178431
|
-
const match = availableOptions.find(
|
|
178432
|
-
(o) => o.toLowerCase() === alias.toLowerCase()
|
|
178433
|
-
);
|
|
178434
|
-
if (match) return match;
|
|
178435
|
-
}
|
|
178436
|
-
for (const alias of typeAliases) {
|
|
178437
|
-
const match = availableOptions.find(
|
|
178438
|
-
(o) => o.toLowerCase().includes(alias.toLowerCase())
|
|
178439
|
-
);
|
|
178440
|
-
if (match) return match;
|
|
178441
|
-
}
|
|
178839
|
+
const match = availableOptions.find((o) => classifyCleanOption(o) === type);
|
|
178840
|
+
if (match) return match;
|
|
178442
178841
|
}
|
|
178443
178842
|
logger196.warn(
|
|
178444
178843
|
`No match for ${CLEAN_TYPE_LABELS[ct]} in [${availableOptions.join(", ")}]`
|
|
178445
178844
|
);
|
|
178446
|
-
return
|
|
178845
|
+
return availableOptions[0];
|
|
178447
178846
|
}
|
|
178448
178847
|
function buildCleaningModeAction(targetCleanType, agent) {
|
|
178449
178848
|
const selectEntityId = getCleaningModeSelectEntity(agent);
|
|
@@ -178578,7 +178977,7 @@ function createCleanModeConfig(fanSpeedList, mopIntensityList, cleaningModeOptio
|
|
|
178578
178977
|
const vacuumEntityId = homeAssistant.entityId;
|
|
178579
178978
|
const mapping = homeAssistant.state.mapping;
|
|
178580
178979
|
logger196.info(
|
|
178581
|
-
`setCleanMode(${mode}) for ${vacuumEntityId}
|
|
178980
|
+
`setCleanMode(${mode}) for ${vacuumEntityId}, suctionEntity=${mapping?.suctionLevelEntity ?? "none"}, mopEntity=${mapping?.mopIntensityEntity ?? "none"}, fanSpeedList=${JSON.stringify(fanSpeedList ?? [])}, mopIntensityList=${JSON.stringify(mopIntensityList ?? [])}, customTags=${JSON.stringify(customFanSpeedTags ?? {})}`
|
|
178582
178981
|
);
|
|
178583
178982
|
if (mopIntensityList && mopIntensityList.length > 0 && isMopIntensityMode(mode)) {
|
|
178584
178983
|
const mopIndex = mode - MOP_INTENSITY_MODE_BASE;
|
|
@@ -178919,58 +179318,65 @@ function RvcOperationalStateServer2(config10) {
|
|
|
178919
179318
|
var logger198 = Logger.get("VacuumRvcOperationalStateServer");
|
|
178920
179319
|
function isCharging(entity) {
|
|
178921
179320
|
const attrs = entity.attributes;
|
|
178922
|
-
if (attrs.battery_icon?.includes("charging")) return true;
|
|
178923
179321
|
if (attrs.is_charging === true || attrs.charging === true) return true;
|
|
179322
|
+
if (attrs.is_charging === false || attrs.charging === false) return false;
|
|
179323
|
+
const raw = attrs.battery_level ?? attrs.battery;
|
|
179324
|
+
const level = typeof raw === "number" ? raw : Number.parseFloat(String(raw ?? ""));
|
|
179325
|
+
if (Number.isFinite(level) && level >= 100) return false;
|
|
179326
|
+
if (attrs.battery_icon?.includes("charging")) return true;
|
|
178924
179327
|
if (typeof attrs.status === "string" && attrs.status.toLowerCase().includes("charg"))
|
|
178925
179328
|
return true;
|
|
178926
179329
|
return false;
|
|
178927
179330
|
}
|
|
178928
|
-
|
|
178929
|
-
|
|
178930
|
-
|
|
178931
|
-
|
|
178932
|
-
|
|
178933
|
-
|
|
178934
|
-
|
|
178935
|
-
|
|
178936
|
-
|
|
178937
|
-
|
|
178938
|
-
|
|
178939
|
-
if (
|
|
178940
|
-
|
|
178941
|
-
|
|
178942
|
-
|
|
178943
|
-
|
|
178944
|
-
|
|
178945
|
-
|
|
178946
|
-
|
|
178947
|
-
|
|
179331
|
+
function mapVacuumOperationalState(entity) {
|
|
179332
|
+
const state = entity.state;
|
|
179333
|
+
const cleaningStates = [
|
|
179334
|
+
VacuumState.cleaning,
|
|
179335
|
+
VacuumState.segment_cleaning,
|
|
179336
|
+
VacuumState.zone_cleaning,
|
|
179337
|
+
VacuumState.spot_cleaning,
|
|
179338
|
+
VacuumState.mop_cleaning
|
|
179339
|
+
];
|
|
179340
|
+
let operationalState;
|
|
179341
|
+
if (state === VacuumState.docked) {
|
|
179342
|
+
if (isCharging(entity)) {
|
|
179343
|
+
operationalState = RvcOperationalState3.OperationalState.Charging;
|
|
179344
|
+
} else {
|
|
179345
|
+
operationalState = RvcOperationalState3.OperationalState.Docked;
|
|
179346
|
+
}
|
|
179347
|
+
} else if (state === VacuumState.returning) {
|
|
179348
|
+
operationalState = RvcOperationalState3.OperationalState.SeekingCharger;
|
|
179349
|
+
} else if (cleaningStates.includes(state)) {
|
|
179350
|
+
operationalState = RvcOperationalState3.OperationalState.Running;
|
|
179351
|
+
} else if (state === VacuumState.paused) {
|
|
179352
|
+
operationalState = RvcOperationalState3.OperationalState.Paused;
|
|
179353
|
+
} else if (state === VacuumState.idle) {
|
|
179354
|
+
if (isCharging(entity)) {
|
|
179355
|
+
operationalState = RvcOperationalState3.OperationalState.Charging;
|
|
179356
|
+
} else {
|
|
179357
|
+
operationalState = RvcOperationalState3.OperationalState.Stopped;
|
|
179358
|
+
}
|
|
179359
|
+
} else if (state === VacuumState.error || state === "unavailable") {
|
|
179360
|
+
operationalState = RvcOperationalState3.OperationalState.Error;
|
|
179361
|
+
} else {
|
|
179362
|
+
if (state.toLowerCase().includes("clean")) {
|
|
179363
|
+
logger198.info(
|
|
179364
|
+
`Unknown vacuum state "${state}" contains 'clean', treating as Running`
|
|
179365
|
+
);
|
|
178948
179366
|
operationalState = RvcOperationalState3.OperationalState.Running;
|
|
178949
|
-
} else if (state === VacuumState.paused) {
|
|
178950
|
-
operationalState = RvcOperationalState3.OperationalState.Paused;
|
|
178951
|
-
} else if (state === VacuumState.idle) {
|
|
178952
|
-
if (isCharging(entity)) {
|
|
178953
|
-
operationalState = RvcOperationalState3.OperationalState.Charging;
|
|
178954
|
-
} else {
|
|
178955
|
-
operationalState = RvcOperationalState3.OperationalState.Stopped;
|
|
178956
|
-
}
|
|
178957
|
-
} else if (state === VacuumState.error || state === "unavailable") {
|
|
178958
|
-
operationalState = RvcOperationalState3.OperationalState.Error;
|
|
178959
179367
|
} else {
|
|
178960
|
-
|
|
178961
|
-
|
|
178962
|
-
`Unknown vacuum state "${state}" contains 'clean', treating as Running`
|
|
178963
|
-
);
|
|
178964
|
-
operationalState = RvcOperationalState3.OperationalState.Running;
|
|
178965
|
-
} else {
|
|
178966
|
-
logger198.info(`Unknown vacuum state "${state}", treating as Stopped`);
|
|
178967
|
-
operationalState = RvcOperationalState3.OperationalState.Stopped;
|
|
178968
|
-
}
|
|
179368
|
+
logger198.info(`Unknown vacuum state "${state}", treating as Stopped`);
|
|
179369
|
+
operationalState = RvcOperationalState3.OperationalState.Stopped;
|
|
178969
179370
|
}
|
|
178970
|
-
|
|
178971
|
-
|
|
178972
|
-
|
|
178973
|
-
|
|
179371
|
+
}
|
|
179372
|
+
logger198.debug(
|
|
179373
|
+
`Vacuum operationalState: "${state}" -> ${RvcOperationalState3.OperationalState[operationalState]}`
|
|
179374
|
+
);
|
|
179375
|
+
return operationalState;
|
|
179376
|
+
}
|
|
179377
|
+
var VacuumRvcOperationalStateServer = RvcOperationalStateServer2({
|
|
179378
|
+
getOperationalState(entity) {
|
|
179379
|
+
return mapVacuumOperationalState(entity);
|
|
178974
179380
|
},
|
|
178975
179381
|
pause: (_, agent) => {
|
|
178976
179382
|
const supportedFeatures = agent.get(HomeAssistantEntityBehavior).entity.state.attributes.supported_features ?? 0;
|
|
@@ -180545,7 +180951,7 @@ var BridgeEndpointManager = class extends Service {
|
|
|
180545
180951
|
this.mappingFingerprints.delete(endpoint.entityId);
|
|
180546
180952
|
} else if (this.registry.isAutoComposedDevicesEnabled() && this.registry.isComposedSubEntityUsed(endpoint.entityId)) {
|
|
180547
180953
|
this.log.info(
|
|
180548
|
-
`Deleting standalone endpoint ${endpoint.entityId}
|
|
180954
|
+
`Deleting standalone endpoint ${endpoint.entityId}, consumed by composed device`
|
|
180549
180955
|
);
|
|
180550
180956
|
try {
|
|
180551
180957
|
await endpoint.delete();
|
|
@@ -180583,14 +180989,14 @@ var BridgeEndpointManager = class extends Service {
|
|
|
180583
180989
|
if (!memoryLimitReached && isHeapUnderPressure()) {
|
|
180584
180990
|
memoryLimitReached = true;
|
|
180585
180991
|
this.log.error(
|
|
180586
|
-
"Memory pressure detected
|
|
180992
|
+
"Memory pressure detected, skipping remaining entities to prevent OOM crash. Reduce the number of entities in this bridge or increase the Node.js heap size (NODE_OPTIONS=--max-old-space-size=1024)."
|
|
180587
180993
|
);
|
|
180588
180994
|
}
|
|
180589
180995
|
if (memoryLimitReached) {
|
|
180590
180996
|
if (!existingEndpoints.some((e) => e.entityId === entityId)) {
|
|
180591
180997
|
this._failedEntities.push({
|
|
180592
180998
|
entityId,
|
|
180593
|
-
reason: "Skipped due to memory pressure
|
|
180999
|
+
reason: "Skipped due to memory pressure, reduce entities or increase heap size"
|
|
180594
181000
|
});
|
|
180595
181001
|
}
|
|
180596
181002
|
continue;
|
|
@@ -180602,7 +181008,7 @@ var BridgeEndpointManager = class extends Service {
|
|
|
180602
181008
|
}
|
|
180603
181009
|
if (this.registry.isAutoComposedDevicesEnabled() && this.registry.isComposedSubEntityUsed(entityId)) {
|
|
180604
181010
|
this.log.debug(
|
|
180605
|
-
`Skipping ${entityId}
|
|
181011
|
+
`Skipping ${entityId}, already part of a composed device`
|
|
180606
181012
|
);
|
|
180607
181013
|
continue;
|
|
180608
181014
|
}
|
|
@@ -180702,7 +181108,7 @@ var BridgeEndpointManager = class extends Service {
|
|
|
180702
181108
|
}
|
|
180703
181109
|
/**
|
|
180704
181110
|
* Log detailed behavior error information for debugging "Behaviors have errors".
|
|
180705
|
-
* Matter.js EndpointBehaviorsError extends AggregateError
|
|
181111
|
+
* Matter.js EndpointBehaviorsError extends AggregateError, the `errors` array
|
|
180706
181112
|
* contains individual behavior crash errors (one per failed behavior).
|
|
180707
181113
|
*/
|
|
180708
181114
|
logDetailedError(entityId, error) {
|
|
@@ -180851,7 +181257,7 @@ var BridgeRegistry = class _BridgeRegistry {
|
|
|
180851
181257
|
* Check if auto composed devices mode is enabled.
|
|
180852
181258
|
* When enabled, temperature sensors with auto-mapped humidity/pressure/battery
|
|
180853
181259
|
* build real Matter Composed Devices (BridgedNodeEndpoint with sub-endpoints)
|
|
180854
|
-
* rather than stacking extra clusters onto a flat TemperatureSensor
|
|
181260
|
+
* rather than stacking extra clusters onto a flat TemperatureSensor.
|
|
180855
181261
|
* Apple Home, Google Home, and Alexa render each sub-endpoint using its
|
|
180856
181262
|
* own device type.
|
|
180857
181263
|
*/
|
|
@@ -180997,8 +181403,8 @@ var BridgeRegistry = class _BridgeRegistry {
|
|
|
180997
181403
|
} else if (id.endsWith("_mode")) {
|
|
180998
181404
|
const options = state.attributes?.options;
|
|
180999
181405
|
if (options?.some(
|
|
181000
|
-
(o) => /^(vacuum|mop|sweep|vacuum_and_mop|vacuum_then_mop|mopping|sweeping|sweeping_and_mopping|mopping_after_sweeping)$/i.test(
|
|
181001
|
-
o
|
|
181406
|
+
(o) => /^(vacuum|mop|sweep|sweep_mop|sweep_before_mopping|sweep_then_mop|vacuum_and_mop|vacuum_then_mop|mopping|sweeping|sweeping_and_mopping|mopping_after_sweeping)$/i.test(
|
|
181407
|
+
o.replace(/\s+/g, "_")
|
|
181002
181408
|
)
|
|
181003
181409
|
)) {
|
|
181004
181410
|
cleaningModeEntity = entity.entity_id;
|
|
@@ -181156,14 +181562,14 @@ var BridgeRegistry = class _BridgeRegistry {
|
|
|
181156
181562
|
const segments = areaMapping[haAreaId];
|
|
181157
181563
|
if (!segments || segments.length === 0) {
|
|
181158
181564
|
_BridgeRegistry.cleanAreaLogger.debug(
|
|
181159
|
-
`${entityId}: Skipping HA area ${haAreaId}
|
|
181565
|
+
`${entityId}: Skipping HA area ${haAreaId}, no segments mapped`
|
|
181160
181566
|
);
|
|
181161
181567
|
continue;
|
|
181162
181568
|
}
|
|
181163
181569
|
if (validSegmentIds && !segments.some((sid) => validSegmentIds.has(sid))) {
|
|
181164
181570
|
const areaName2 = this.registry.areas.get(haAreaId) ?? haAreaId;
|
|
181165
181571
|
_BridgeRegistry.cleanAreaLogger.info(
|
|
181166
|
-
`${entityId}: Skipping stale HA area "${areaName2}" (${haAreaId})
|
|
181572
|
+
`${entityId}: Skipping stale HA area "${areaName2}" (${haAreaId}), segments [${segments.join(", ")}] no longer exist on vacuum`
|
|
181167
181573
|
);
|
|
181168
181574
|
continue;
|
|
181169
181575
|
}
|
|
@@ -181434,6 +181840,19 @@ init_dist();
|
|
|
181434
181840
|
init_diagnostic_event_bus();
|
|
181435
181841
|
var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
|
|
181436
181842
|
var DEAD_SESSION_TIMEOUT_MS2 = 6e4;
|
|
181843
|
+
var DEFAULT_SESSION_MAX_AGE_HOURS = 4;
|
|
181844
|
+
var SESSION_MAX_AGE_HOURS_RANGE = { min: 1, max: 168 };
|
|
181845
|
+
var ROTATION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
181846
|
+
function parseSessionMaxAgeHours(raw) {
|
|
181847
|
+
if (raw == null || raw === "") return DEFAULT_SESSION_MAX_AGE_HOURS;
|
|
181848
|
+
const n = Number.parseInt(raw, 10);
|
|
181849
|
+
if (Number.isNaN(n) || n < 0) return null;
|
|
181850
|
+
if (n === 0) return 0;
|
|
181851
|
+
const { min, max } = SESSION_MAX_AGE_HOURS_RANGE;
|
|
181852
|
+
if (n < min) return min;
|
|
181853
|
+
if (n > max) return max;
|
|
181854
|
+
return n;
|
|
181855
|
+
}
|
|
181437
181856
|
var ServerModeBridge = class {
|
|
181438
181857
|
constructor(logger207, dataProvider, endpointManager, server) {
|
|
181439
181858
|
this.dataProvider = dataProvider;
|
|
@@ -181456,6 +181875,10 @@ var ServerModeBridge = class {
|
|
|
181456
181875
|
deadSessionTimer = null;
|
|
181457
181876
|
staleSessionTimers = /* @__PURE__ */ new Map();
|
|
181458
181877
|
warmStartTimer = null;
|
|
181878
|
+
// Tracks when each session opened, used for age-based rotation (#287).
|
|
181879
|
+
sessionStartedAt = /* @__PURE__ */ new Map();
|
|
181880
|
+
rotationTimer = null;
|
|
181881
|
+
maxSessionAgeMs = 0;
|
|
181459
181882
|
// Tracks the last synced state JSON per entity to avoid pushing unchanged states.
|
|
181460
181883
|
lastSyncedState;
|
|
181461
181884
|
// Session lifecycle diagnostic handlers (non-destructive, logging only).
|
|
@@ -181540,6 +181963,7 @@ var ServerModeBridge = class {
|
|
|
181540
181963
|
});
|
|
181541
181964
|
}
|
|
181542
181965
|
this.wireSessionDiagnostics();
|
|
181966
|
+
this.startSessionRotation();
|
|
181543
181967
|
this.scheduleWarmStart();
|
|
181544
181968
|
logMemoryUsage(this.log, "server mode bridge running");
|
|
181545
181969
|
this.log.info("Server mode bridge started");
|
|
@@ -181555,6 +181979,7 @@ ${e?.toString()}`);
|
|
|
181555
181979
|
}
|
|
181556
181980
|
}
|
|
181557
181981
|
async stop(code = BridgeStatus.Stopped, reason = "Manually stopped") {
|
|
181982
|
+
this.stopSessionRotation();
|
|
181558
181983
|
this.unwireSessionDiagnostics();
|
|
181559
181984
|
this.cancelWarmStart();
|
|
181560
181985
|
this.stopAutoForceSync();
|
|
@@ -181583,6 +182008,7 @@ ${e?.toString()}`);
|
|
|
181583
182008
|
await this.refreshDevices();
|
|
181584
182009
|
if (this.status.code === BridgeStatus.Running) {
|
|
181585
182010
|
this.startAutoForceSyncIfEnabled();
|
|
182011
|
+
this.startSessionRotation();
|
|
181586
182012
|
}
|
|
181587
182013
|
} catch (e) {
|
|
181588
182014
|
const reason = "Failed to update server mode bridge due to error:";
|
|
@@ -181639,7 +182065,7 @@ ${e?.toString()}`);
|
|
|
181639
182065
|
);
|
|
181640
182066
|
if (totalSubs === 0 && sessions.length > 0) {
|
|
181641
182067
|
this.log.warn(
|
|
181642
|
-
`All subscriptions lost
|
|
182068
|
+
`All subscriptions lost, ${sessions.length} session(s) still active, waiting for controller to re-subscribe`
|
|
181643
182069
|
);
|
|
181644
182070
|
if (!this.deadSessionTimer) {
|
|
181645
182071
|
this.deadSessionTimer = setTimeout(() => {
|
|
@@ -181672,13 +182098,14 @@ ${e?.toString()}`);
|
|
|
181672
182098
|
};
|
|
181673
182099
|
sessionManager.subscriptionsChanged.on(this.sessionDiagHandler);
|
|
181674
182100
|
this.sessionAddedHandler = (newSession) => {
|
|
182101
|
+
this.sessionStartedAt.set(newSession.id, Date.now());
|
|
181675
182102
|
this.log.info(
|
|
181676
182103
|
`Session opened: id=${newSession.id} peer=${newSession.peerNodeId}`
|
|
181677
182104
|
);
|
|
181678
182105
|
for (const s of [...sessionManager.sessions]) {
|
|
181679
182106
|
if (s !== newSession && !s.isClosing && s.peerNodeId === newSession.peerNodeId && s.fabric?.fabricIndex === newSession.fabric?.fabricIndex && s.subscriptions.size === 0) {
|
|
181680
182107
|
this.log.info(
|
|
181681
|
-
`Closing stale session ${s.id} (peer ${s.peerNodeId}, 0 subs)
|
|
182108
|
+
`Closing stale session ${s.id} (peer ${s.peerNodeId}, 0 subs), replaced by session ${newSession.id}`
|
|
181682
182109
|
);
|
|
181683
182110
|
s.initiateForceClose().catch(() => {
|
|
181684
182111
|
});
|
|
@@ -181686,6 +182113,7 @@ ${e?.toString()}`);
|
|
|
181686
182113
|
}
|
|
181687
182114
|
};
|
|
181688
182115
|
this.sessionDeletedHandler = (session) => {
|
|
182116
|
+
this.sessionStartedAt.delete(session.id);
|
|
181689
182117
|
const sessions = [...sessionManager.sessions];
|
|
181690
182118
|
this.log.warn(
|
|
181691
182119
|
`Session closed: id=${session.id} peer=${session.peerNodeId} | remaining sessions=${sessions.length}`
|
|
@@ -181777,6 +182205,85 @@ ${e?.toString()}`);
|
|
|
181777
182205
|
clearTimeout(timer);
|
|
181778
182206
|
}
|
|
181779
182207
|
this.staleSessionTimers.clear();
|
|
182208
|
+
this.sessionStartedAt.clear();
|
|
182209
|
+
}
|
|
182210
|
+
// Start the periodic age-based session rotation (#287).
|
|
182211
|
+
startSessionRotation() {
|
|
182212
|
+
this.stopSessionRotation();
|
|
182213
|
+
const hours = this.readSessionMaxAgeHours();
|
|
182214
|
+
if (hours === 0) {
|
|
182215
|
+
this.log.info(
|
|
182216
|
+
"Session rotation disabled (HAMH_MATTER_SESSION_MAX_AGE_HOURS=0)"
|
|
182217
|
+
);
|
|
182218
|
+
return;
|
|
182219
|
+
}
|
|
182220
|
+
this.maxSessionAgeMs = hours * 60 * 60 * 1e3;
|
|
182221
|
+
this.rotationTimer = setInterval(
|
|
182222
|
+
() => this.rotateAgedSessions(),
|
|
182223
|
+
ROTATION_CHECK_INTERVAL_MS
|
|
182224
|
+
);
|
|
182225
|
+
this.log.info(
|
|
182226
|
+
`Session rotation: max age ${hours}h, check every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
|
|
182227
|
+
);
|
|
182228
|
+
}
|
|
182229
|
+
stopSessionRotation() {
|
|
182230
|
+
if (this.rotationTimer) {
|
|
182231
|
+
clearInterval(this.rotationTimer);
|
|
182232
|
+
this.rotationTimer = null;
|
|
182233
|
+
}
|
|
182234
|
+
}
|
|
182235
|
+
// Resolve session rotation max age. Bridge config wins, then env var,
|
|
182236
|
+
// then built-in default. 0 disables, otherwise clamped to range.
|
|
182237
|
+
readSessionMaxAgeHours() {
|
|
182238
|
+
const { min, max } = SESSION_MAX_AGE_HOURS_RANGE;
|
|
182239
|
+
const fromConfig = this.dataProvider.sessionMaxAgeHours;
|
|
182240
|
+
if (fromConfig != null && Number.isFinite(fromConfig) && fromConfig >= 0) {
|
|
182241
|
+
if (fromConfig === 0) return 0;
|
|
182242
|
+
if (fromConfig < min) return min;
|
|
182243
|
+
if (fromConfig > max) return max;
|
|
182244
|
+
return fromConfig;
|
|
182245
|
+
}
|
|
182246
|
+
const raw = process.env.HAMH_MATTER_SESSION_MAX_AGE_HOURS;
|
|
182247
|
+
const parsed = parseSessionMaxAgeHours(raw);
|
|
182248
|
+
if (parsed == null) {
|
|
182249
|
+
this.log.warn(
|
|
182250
|
+
`Invalid HAMH_MATTER_SESSION_MAX_AGE_HOURS=${raw}, falling back to ${DEFAULT_SESSION_MAX_AGE_HOURS}h`
|
|
182251
|
+
);
|
|
182252
|
+
return DEFAULT_SESSION_MAX_AGE_HOURS;
|
|
182253
|
+
}
|
|
182254
|
+
return parsed;
|
|
182255
|
+
}
|
|
182256
|
+
// Gracefully close sessions older than maxSessionAgeMs so controllers
|
|
182257
|
+
// re-establish CASE and re-subscribe. Stale (0-sub) sessions are handled
|
|
182258
|
+
// by the existing dead-session path, so only rotate ones with subscriptions.
|
|
182259
|
+
rotateAgedSessions() {
|
|
182260
|
+
if (this.maxSessionAgeMs === 0) return;
|
|
182261
|
+
try {
|
|
182262
|
+
const sessionManager = this.server.env.get(SessionManager);
|
|
182263
|
+
const now = Date.now();
|
|
182264
|
+
const closes = [];
|
|
182265
|
+
for (const s of [...sessionManager.sessions]) {
|
|
182266
|
+
const startedAt = this.sessionStartedAt.get(s.id);
|
|
182267
|
+
if (startedAt == null) continue;
|
|
182268
|
+
const ageMs = now - startedAt;
|
|
182269
|
+
if (ageMs < this.maxSessionAgeMs || s.isClosing || s.subscriptions.size === 0) {
|
|
182270
|
+
continue;
|
|
182271
|
+
}
|
|
182272
|
+
const ageMin = Math.round(ageMs / 6e4);
|
|
182273
|
+
this.log.info(
|
|
182274
|
+
`Rotating session ${s.id} (peer ${s.peerNodeId}, age ${ageMin}min, subs ${s.subscriptions.size})`
|
|
182275
|
+
);
|
|
182276
|
+
closes.push(
|
|
182277
|
+
s.initiateClose().catch(() => {
|
|
182278
|
+
return s.initiateForceClose();
|
|
182279
|
+
})
|
|
182280
|
+
);
|
|
182281
|
+
}
|
|
182282
|
+
if (closes.length > 0) {
|
|
182283
|
+
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
182284
|
+
}
|
|
182285
|
+
} catch {
|
|
182286
|
+
}
|
|
181780
182287
|
}
|
|
181781
182288
|
stopAutoForceSync() {
|
|
181782
182289
|
if (this.autoForceSyncTimer) {
|
|
@@ -182099,7 +182606,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
182099
182606
|
}
|
|
182100
182607
|
}
|
|
182101
182608
|
} else {
|
|
182102
|
-
logger205.warn(`${entityId}: No device_id
|
|
182609
|
+
logger205.warn(`${entityId}: No device_id, cannot auto-assign battery`);
|
|
182103
182610
|
}
|
|
182104
182611
|
const payload = {
|
|
182105
182612
|
entity_id: entityId,
|
|
@@ -182155,7 +182662,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
182155
182662
|
* Previous approaches that pushed state through
|
|
182156
182663
|
* HomeAssistantEntityBehavior failed because the reactor writes
|
|
182157
182664
|
* (RvcOperationalStateServer.update()) run inside the postCommit
|
|
182158
|
-
* phase of the HAEntityBehavior transaction
|
|
182665
|
+
* phase of the HAEntityBehavior transaction, those writes are
|
|
182159
182666
|
* buffered but never committed, so no attrsChanged event reaches
|
|
182160
182667
|
* the ServerSubscription.
|
|
182161
182668
|
*
|
|
@@ -182193,6 +182700,11 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
|
|
|
182193
182700
|
* unlike errorStateLabel (id 1) which requires errorStateId 128-191.
|
|
182194
182701
|
*/
|
|
182195
182702
|
async pushKeepalive() {
|
|
182703
|
+
try {
|
|
182704
|
+
await this.construction.ready;
|
|
182705
|
+
} catch {
|
|
182706
|
+
return;
|
|
182707
|
+
}
|
|
182196
182708
|
try {
|
|
182197
182709
|
this.keepaliveCounter++;
|
|
182198
182710
|
const counter = this.keepaliveCounter;
|
|
@@ -182401,11 +182913,11 @@ var ServerModeEndpointManager = class extends Service {
|
|
|
182401
182913
|
}
|
|
182402
182914
|
if (isHeapUnderPressure()) {
|
|
182403
182915
|
this.log.error(
|
|
182404
|
-
"Memory pressure detected
|
|
182916
|
+
"Memory pressure detected, cannot create device endpoint. Reduce entities on other bridges or increase the Node.js heap size (NODE_OPTIONS=--max-old-space-size=1024)."
|
|
182405
182917
|
);
|
|
182406
182918
|
this._failedEntities.push({
|
|
182407
182919
|
entityId,
|
|
182408
|
-
reason: "Skipped due to memory pressure
|
|
182920
|
+
reason: "Skipped due to memory pressure, reduce entities or increase heap size"
|
|
182409
182921
|
});
|
|
182410
182922
|
return;
|
|
182411
182923
|
}
|
|
@@ -182795,7 +183307,7 @@ function patchLevelControlTlv() {
|
|
|
182795
183307
|
);
|
|
182796
183308
|
} else {
|
|
182797
183309
|
logger206.warn(
|
|
182798
|
-
"Failed to patch LevelControl TLV schemas
|
|
183310
|
+
"Failed to patch LevelControl TLV schemas, field definitions not found. Google Home brightness adjustment may not work."
|
|
182799
183311
|
);
|
|
182800
183312
|
}
|
|
182801
183313
|
}
|
|
@@ -182967,6 +183479,10 @@ function startOptionsBuilder(yargs2) {
|
|
|
182967
183479
|
type: "number",
|
|
182968
183480
|
description: "The refresh rate (in seconds) to detect new devices & entities or their configurations",
|
|
182969
183481
|
default: 60
|
|
183482
|
+
}).option("ha-message-timeout", {
|
|
183483
|
+
type: "number",
|
|
183484
|
+
description: "Timeout in milliseconds for individual Home Assistant WebSocket requests (registry fetches, action calls). Raise this if you see 'HA message ... timed out' warnings on a large or slow HA instance.",
|
|
183485
|
+
default: 6e4
|
|
182970
183486
|
}).option("http-auth-username", {
|
|
182971
183487
|
type: "string",
|
|
182972
183488
|
description: "Username for HTTP basic authentication (optional)"
|