@riddix/hamh 2.1.0-alpha.853 → 2.1.0-alpha.855
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
> [!NOTE]
|
|
22
|
-
> 🔀 **Community Fork** - This is
|
|
22
|
+
> 🔀 **Community Fork** - This is the actively maintained fork and successor of the original [t0bst4r/home-assistant-matter-hub](https://github.com/t0bst4r/home-assistant-matter-hub), which was discontinued in January 2026 and has since been archived. We continue active development with bug fixes, new features, and community support. Thank you **[@t0bst4r](https://github.com/t0bst4r)** for the original work! ❤️
|
|
23
23
|
>
|
|
24
|
-
> **📦 Migrating?** See [Migration Guide](#migration-from-t0bst4r) - your paired devices will continue to work!
|
|
24
|
+
> **📦 Migrating?** See [Migration Guide](#migration-from-t0bst4r) - your paired devices will continue to work! For a side-by-side feature list, see the [fork comparison](https://riddix.github.io/home-assistant-matter-hub/getting-started/fork-comparison). The add-on slugs are `hamh` (stable), `hamh-alpha` and `hamh-testing`.
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
package/dist/backend/cli.js
CHANGED
|
@@ -133493,6 +133493,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133493
133493
|
lockPinMaxLength: config8.lockPinMaxLength,
|
|
133494
133494
|
powerEntity: config8.powerEntity,
|
|
133495
133495
|
energyEntity: config8.energyEntity,
|
|
133496
|
+
meterSerialNumber: config8.meterSerialNumber,
|
|
133497
|
+
pointOfDelivery: config8.pointOfDelivery,
|
|
133496
133498
|
voltageEntity: config8.voltageEntity,
|
|
133497
133499
|
currentEntity: config8.currentEntity,
|
|
133498
133500
|
batteryPowerEntity: config8.batteryPowerEntity,
|
|
@@ -133657,6 +133659,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
|
|
|
133657
133659
|
lockPinMaxLength: config8.lockPinMaxLength,
|
|
133658
133660
|
powerEntity: config8.powerEntity,
|
|
133659
133661
|
energyEntity: config8.energyEntity,
|
|
133662
|
+
meterSerialNumber: config8.meterSerialNumber,
|
|
133663
|
+
pointOfDelivery: config8.pointOfDelivery,
|
|
133660
133664
|
voltageEntity: config8.voltageEntity,
|
|
133661
133665
|
currentEntity: config8.currentEntity,
|
|
133662
133666
|
batteryPowerEntity: config8.batteryPowerEntity,
|
|
@@ -134512,6 +134516,8 @@ function entityMappingApi(mappingStorage, identityStorage) {
|
|
|
134512
134516
|
lockPinMaxLength: body.lockPinMaxLength,
|
|
134513
134517
|
powerEntity: body.powerEntity,
|
|
134514
134518
|
energyEntity: body.energyEntity,
|
|
134519
|
+
meterSerialNumber: body.meterSerialNumber,
|
|
134520
|
+
pointOfDelivery: body.pointOfDelivery,
|
|
134515
134521
|
voltageEntity: body.voltageEntity,
|
|
134516
134522
|
currentEntity: body.currentEntity,
|
|
134517
134523
|
batteryPowerEntity: body.batteryPowerEntity,
|
|
@@ -135016,6 +135022,8 @@ function configToProfileEntry(config8) {
|
|
|
135016
135022
|
lockPinMaxLength: config8.lockPinMaxLength,
|
|
135017
135023
|
powerEntity: config8.powerEntity,
|
|
135018
135024
|
energyEntity: config8.energyEntity,
|
|
135025
|
+
meterSerialNumber: config8.meterSerialNumber,
|
|
135026
|
+
pointOfDelivery: config8.pointOfDelivery,
|
|
135019
135027
|
voltageEntity: config8.voltageEntity,
|
|
135020
135028
|
currentEntity: config8.currentEntity,
|
|
135021
135029
|
batteryPowerEntity: config8.batteryPowerEntity,
|
|
@@ -135158,6 +135166,8 @@ function mappingProfileApi(mappingStorage) {
|
|
|
135158
135166
|
lockPinMaxLength: entry.lockPinMaxLength,
|
|
135159
135167
|
powerEntity: entry.powerEntity,
|
|
135160
135168
|
energyEntity: entry.energyEntity,
|
|
135169
|
+
meterSerialNumber: entry.meterSerialNumber,
|
|
135170
|
+
pointOfDelivery: entry.pointOfDelivery,
|
|
135161
135171
|
voltageEntity: entry.voltageEntity,
|
|
135162
135172
|
currentEntity: entry.currentEntity,
|
|
135163
135173
|
batteryPowerEntity: entry.batteryPowerEntity,
|
|
@@ -139936,6 +139946,8 @@ var EntityMappingStorage = class extends Service {
|
|
|
139936
139946
|
lockPinMaxLength: pinLengths.max,
|
|
139937
139947
|
powerEntity: request.powerEntity?.trim() || void 0,
|
|
139938
139948
|
energyEntity: request.energyEntity?.trim() || void 0,
|
|
139949
|
+
meterSerialNumber: request.meterSerialNumber?.trim() || void 0,
|
|
139950
|
+
pointOfDelivery: request.pointOfDelivery?.trim() || void 0,
|
|
139939
139951
|
voltageEntity: request.voltageEntity?.trim() || void 0,
|
|
139940
139952
|
currentEntity: request.currentEntity?.trim() || void 0,
|
|
139941
139953
|
batteryPowerEntity: request.batteryPowerEntity?.trim() || void 0,
|
|
@@ -139971,7 +139983,7 @@ var EntityMappingStorage = class extends Service {
|
|
|
139971
139983
|
composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0,
|
|
139972
139984
|
disableMomentaryFlip: request.disableMomentaryFlip || void 0
|
|
139973
139985
|
};
|
|
139974
|
-
if (!config8.matterDeviceType && !config8.customName && !config8.customProductName && !config8.customVendorName && !config8.customSerialNumber && config8.customVendorId === void 0 && config8.disabled !== true && !config8.filterLifeEntity && !config8.cleaningModeEntity && !config8.temperatureEntity && !config8.humidityEntity && !config8.batteryEntity && !config8.disableBatteryMapping && !config8.chargingStateEntity && !config8.roomEntities && !config8.disableLockPin && !config8.lockUsercodeService && config8.lockUsercodeSlot === void 0 && config8.lockPinMinLength === void 0 && config8.lockPinMaxLength === void 0 && !config8.powerEntity && !config8.energyEntity && !config8.voltageEntity && !config8.currentEntity && !config8.batteryPowerEntity && !config8.batteryEnergyEntity && !config8.chargingSwitchEntity && !config8.currentLimitEntity && !config8.pressureEntity && !config8.suctionLevelEntity && !config8.mopIntensityEntity && (!config8.customServiceAreas || config8.customServiceAreas.length === 0) && (!config8.customFanSpeedTags || Object.keys(config8.customFanSpeedTags).length === 0) && (!config8.fanWindPresets || (config8.fanWindPresets.natural?.length ?? 0) === 0 && (config8.fanWindPresets.sleep?.length ?? 0) === 0) && !config8.fanRestoreSpeedOnPowerOn && !config8.currentRoomEntity && !config8.cleanedAreaEntity && !config8.vacuumAscendingRoomOrder && !config8.disableCustomAreaRoomModes && !config8.valetudoIdentifier && !config8.coverSwapOpenClose && !config8.coverExposeAsDimmableLight && !config8.selectExposeAsSwitch && !config8.selectSwitchOnOption && !config8.selectSwitchOffOption && !config8.coverSliderDebounceMs && !config8.updateThrottleMs && !config8.disableClimateOnOff && !config8.disableClimateFanControl && !config8.climateKeepModeOnIdle && !config8.climateExposeFan && !config8.climateAutoMode && (!config8.composedEntities || config8.composedEntities.length === 0) && !config8.disableMomentaryFlip) {
|
|
139986
|
+
if (!config8.matterDeviceType && !config8.customName && !config8.customProductName && !config8.customVendorName && !config8.customSerialNumber && config8.customVendorId === void 0 && config8.disabled !== true && !config8.filterLifeEntity && !config8.cleaningModeEntity && !config8.temperatureEntity && !config8.humidityEntity && !config8.batteryEntity && !config8.disableBatteryMapping && !config8.chargingStateEntity && !config8.roomEntities && !config8.disableLockPin && !config8.lockUsercodeService && config8.lockUsercodeSlot === void 0 && config8.lockPinMinLength === void 0 && config8.lockPinMaxLength === void 0 && !config8.powerEntity && !config8.energyEntity && !config8.meterSerialNumber && !config8.pointOfDelivery && !config8.voltageEntity && !config8.currentEntity && !config8.batteryPowerEntity && !config8.batteryEnergyEntity && !config8.chargingSwitchEntity && !config8.currentLimitEntity && !config8.pressureEntity && !config8.suctionLevelEntity && !config8.mopIntensityEntity && (!config8.customServiceAreas || config8.customServiceAreas.length === 0) && (!config8.customFanSpeedTags || Object.keys(config8.customFanSpeedTags).length === 0) && (!config8.fanWindPresets || (config8.fanWindPresets.natural?.length ?? 0) === 0 && (config8.fanWindPresets.sleep?.length ?? 0) === 0) && !config8.fanRestoreSpeedOnPowerOn && !config8.currentRoomEntity && !config8.cleanedAreaEntity && !config8.vacuumAscendingRoomOrder && !config8.disableCustomAreaRoomModes && !config8.valetudoIdentifier && !config8.coverSwapOpenClose && !config8.coverExposeAsDimmableLight && !config8.selectExposeAsSwitch && !config8.selectSwitchOnOption && !config8.selectSwitchOffOption && !config8.coverSliderDebounceMs && !config8.updateThrottleMs && !config8.disableClimateOnOff && !config8.disableClimateFanControl && !config8.climateKeepModeOnIdle && !config8.climateExposeFan && !config8.climateAutoMode && (!config8.composedEntities || config8.composedEntities.length === 0) && !config8.disableMomentaryFlip) {
|
|
139975
139987
|
bridgeMap.delete(request.entityId);
|
|
139976
139988
|
} else {
|
|
139977
139989
|
bridgeMap.set(request.entityId, config8);
|
|
@@ -149611,6 +149623,7 @@ var DoorbellDeviceDefinition = MutableEndpoint({
|
|
|
149611
149623
|
behaviors: SupportedBehaviors(DoorbellRequirements.server.mandatory.Identify)
|
|
149612
149624
|
});
|
|
149613
149625
|
Object.freeze(DoorbellDeviceDefinition);
|
|
149626
|
+
var DoorbellDevice = DoorbellDeviceDefinition;
|
|
149614
149627
|
|
|
149615
149628
|
// ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/commodity-price.js
|
|
149616
149629
|
init_ClusterType();
|
|
@@ -149834,6 +149847,7 @@ var ElectricalUtilityMeterDeviceDefinition = MutableEndpoint({
|
|
|
149834
149847
|
behaviors: SupportedBehaviors(ElectricalUtilityMeterRequirements.server.mandatory.MeterIdentification)
|
|
149835
149848
|
});
|
|
149836
149849
|
Object.freeze(ElectricalUtilityMeterDeviceDefinition);
|
|
149850
|
+
var ElectricalUtilityMeterDevice = ElectricalUtilityMeterDeviceDefinition;
|
|
149837
149851
|
|
|
149838
149852
|
// ../../node_modules/.pnpm/@matter+types@0.17.9/node_modules/@matter/types/dist/esm/clusters/energy-evse.js
|
|
149839
149853
|
init_ClusterType();
|
|
@@ -157731,9 +157745,39 @@ function decideWedgeRotation(input) {
|
|
|
157731
157745
|
const imSilenceMs = lastImRequestMsAgo == null ? sessionAgeMs : lastImRequestMsAgo;
|
|
157732
157746
|
return subscriptionCount > 0 && sessionAgeMs > WEDGE_WARMUP_MS && imSilenceMs > WEDGE_IM_SILENCE_MS && (lastRotatedMsAgo == null || lastRotatedMsAgo > WEDGE_MIN_ROTATE_INTERVAL_MS);
|
|
157733
157747
|
}
|
|
157748
|
+
var WEDGE_V2_COMMAND_SILENCE_MS = 45 * 60 * 1e3;
|
|
157749
|
+
var WEDGE_V2_WINDOW_MS = 30 * 60 * 1e3;
|
|
157750
|
+
var WEDGE_V2_MIN_SUBSCRIBES = 3;
|
|
157751
|
+
var WEDGE_V2_MIN_GIVE_UPS = 2;
|
|
157752
|
+
var WEDGE_RING_SIZE = 10;
|
|
157753
|
+
var WEDGE_GIVE_UP_QUIET_MS = 5e3;
|
|
157754
|
+
function inWindow(timesMs, nowMs) {
|
|
157755
|
+
return timesMs.filter((t) => nowMs - t <= WEDGE_V2_WINDOW_MS);
|
|
157756
|
+
}
|
|
157757
|
+
function countRecent(timesMs, nowMs) {
|
|
157758
|
+
return inWindow(timesMs, nowMs).length;
|
|
157759
|
+
}
|
|
157760
|
+
function decideWedgeRotationV2(input) {
|
|
157761
|
+
const silenceMs = input.commandSilenceMs ?? input.sessionAgeMs;
|
|
157762
|
+
if (input.subscriptionCount <= 0) return false;
|
|
157763
|
+
if (input.sessionAgeMs <= WEDGE_WARMUP_MS) return false;
|
|
157764
|
+
if (silenceMs <= WEDGE_V2_COMMAND_SILENCE_MS) return false;
|
|
157765
|
+
const subscribes = inWindow(input.subscribeTimesMs, input.nowMs);
|
|
157766
|
+
const giveUps = inWindow(input.giveUpTimesMs, input.nowMs);
|
|
157767
|
+
if (subscribes.length < WEDGE_V2_MIN_SUBSCRIBES) return false;
|
|
157768
|
+
if (giveUps.length < WEDGE_V2_MIN_GIVE_UPS) return false;
|
|
157769
|
+
if (Math.max(...giveUps) <= Math.min(...subscribes)) return false;
|
|
157770
|
+
return input.lastRotatedMsAgo == null || input.lastRotatedMsAgo > WEDGE_MIN_ROTATE_INTERVAL_MS;
|
|
157771
|
+
}
|
|
157734
157772
|
|
|
157735
157773
|
// src/services/bridges/bridge.ts
|
|
157736
157774
|
var imWrapMarker = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
|
|
157775
|
+
var commandMessageTypes = [
|
|
157776
|
+
MessageType.ReadRequest,
|
|
157777
|
+
MessageType.WriteRequest,
|
|
157778
|
+
MessageType.InvokeRequest,
|
|
157779
|
+
MessageType.TimedRequest
|
|
157780
|
+
];
|
|
157737
157781
|
var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
|
|
157738
157782
|
var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS = 2500;
|
|
157739
157783
|
var MDNS_ADDRESS_CHECK_INTERVAL_MS = 6e4;
|
|
@@ -157787,6 +157831,13 @@ var Bridge = class {
|
|
|
157787
157831
|
lastImRequestAt = /* @__PURE__ */ new WeakMap();
|
|
157788
157832
|
wedgeLastRotatedAt = /* @__PURE__ */ new WeakMap();
|
|
157789
157833
|
wedgeWatchdogTimer = null;
|
|
157834
|
+
// V2 shadow signals (#365): last command-class request per session, plus
|
|
157835
|
+
// rings of subscribe and delivery give-up times (newest WEDGE_RING_SIZE).
|
|
157836
|
+
lastCommandImAt = /* @__PURE__ */ new WeakMap();
|
|
157837
|
+
subscribeTimesMs = /* @__PURE__ */ new WeakMap();
|
|
157838
|
+
giveUpTimesMs = /* @__PURE__ */ new WeakMap();
|
|
157839
|
+
// Sessions whose subscriptions.deleted we already hooked for give-ups.
|
|
157840
|
+
wedgeHookedSessions = /* @__PURE__ */ new WeakSet();
|
|
157790
157841
|
// Watches the advertised interface addresses so a dynamic ISP IPv6 prefix
|
|
157791
157842
|
// change forces a fresh operational announcement (#415).
|
|
157792
157843
|
mdnsAddressTimer = null;
|
|
@@ -157850,6 +157901,20 @@ var Bridge = class {
|
|
|
157850
157901
|
const lastImAt = this.lastImRequestAt.get(s);
|
|
157851
157902
|
const lastImRequestMsAgo = lastImAt != null ? nowMs - lastImAt : null;
|
|
157852
157903
|
const startedAt = this.sessionStartedAt.get(s.id);
|
|
157904
|
+
const lastCmdAt = this.lastCommandImAt.get(s);
|
|
157905
|
+
const lastCommandImRequestMsAgo = lastCmdAt != null ? nowMs - lastCmdAt : null;
|
|
157906
|
+
const subscribeTimes = this.subscribeTimesMs.get(s) ?? [];
|
|
157907
|
+
const giveUpTimes = this.giveUpTimesMs.get(s) ?? [];
|
|
157908
|
+
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
157909
|
+
const wedgeV2WouldRotate = decideWedgeRotationV2({
|
|
157910
|
+
subscriptionCount: subCount,
|
|
157911
|
+
sessionAgeMs: startedAt != null ? nowMs - startedAt : 0,
|
|
157912
|
+
commandSilenceMs: lastCommandImRequestMsAgo,
|
|
157913
|
+
subscribeTimesMs: subscribeTimes,
|
|
157914
|
+
giveUpTimesMs: giveUpTimes,
|
|
157915
|
+
nowMs,
|
|
157916
|
+
lastRotatedMsAgo: lastRotatedAt != null ? nowMs - lastRotatedAt : null
|
|
157917
|
+
});
|
|
157853
157918
|
return {
|
|
157854
157919
|
id: s.id,
|
|
157855
157920
|
peerNodeId: String(s.peerNodeId),
|
|
@@ -157859,6 +157924,10 @@ var Bridge = class {
|
|
|
157859
157924
|
lastActiveMsAgo,
|
|
157860
157925
|
lastAnyActivityMsAgo,
|
|
157861
157926
|
lastImRequestMsAgo,
|
|
157927
|
+
lastCommandImRequestMsAgo,
|
|
157928
|
+
subscribesLast30Min: countRecent(subscribeTimes, nowMs),
|
|
157929
|
+
giveUpsLast30Min: countRecent(giveUpTimes, nowMs),
|
|
157930
|
+
wedgeV2WouldRotate,
|
|
157862
157931
|
isPeerActive: Boolean(s.isPeerActive),
|
|
157863
157932
|
ageMsFromOpen: startedAt != null ? nowMs - startedAt : null
|
|
157864
157933
|
};
|
|
@@ -158088,6 +158157,15 @@ ${e?.toString()}`);
|
|
|
158088
158157
|
try {
|
|
158089
158158
|
const sessionManager = this.server.env.get(SessionManager);
|
|
158090
158159
|
seedExistingSessionStarts(this.sessionStartedAt, sessionManager.sessions);
|
|
158160
|
+
const hookGiveUpsWithPeers = (sess) => this.hookSubscriptionGiveUps(
|
|
158161
|
+
sess,
|
|
158162
|
+
() => [...sessionManager.sessions].filter(
|
|
158163
|
+
(s) => s.peerNodeId === sess.peerNodeId && s.fabric?.fabricIndex === sess.fabric?.fabricIndex
|
|
158164
|
+
)
|
|
158165
|
+
);
|
|
158166
|
+
for (const sess of sessionManager.sessions) {
|
|
158167
|
+
hookGiveUpsWithPeers(sess);
|
|
158168
|
+
}
|
|
158091
158169
|
this.sessionDiagHandler = (session) => {
|
|
158092
158170
|
const sessions = [...sessionManager.sessions];
|
|
158093
158171
|
let totalSubs = 0;
|
|
@@ -158150,6 +158228,7 @@ ${e?.toString()}`);
|
|
|
158150
158228
|
sessionManager.subscriptionsChanged.on(this.sessionDiagHandler);
|
|
158151
158229
|
this.sessionAddedHandler = (newSession) => {
|
|
158152
158230
|
this.sessionStartedAt.set(newSession.id, Date.now());
|
|
158231
|
+
hookGiveUpsWithPeers(newSession);
|
|
158153
158232
|
this.log.info(
|
|
158154
158233
|
`Session opened: id=${newSession.id} peer=${newSession.peerNodeId}`
|
|
158155
158234
|
);
|
|
@@ -158243,7 +158322,14 @@ ${e?.toString()}`);
|
|
|
158243
158322
|
is.onNewExchange = (exchange, message) => {
|
|
158244
158323
|
const session = exchange.session;
|
|
158245
158324
|
if (session) {
|
|
158246
|
-
|
|
158325
|
+
const now = Date.now();
|
|
158326
|
+
this.lastImRequestAt.set(session, now);
|
|
158327
|
+
const type = message?.payloadHeader?.messageType;
|
|
158328
|
+
if (type === MessageType.SubscribeRequest) {
|
|
158329
|
+
this.pushWedgeRing(this.subscribeTimesMs, session, now);
|
|
158330
|
+
} else if (type != null && commandMessageTypes.includes(type)) {
|
|
158331
|
+
this.lastCommandImAt.set(session, now);
|
|
158332
|
+
}
|
|
158247
158333
|
}
|
|
158248
158334
|
return original(exchange, message);
|
|
158249
158335
|
};
|
|
@@ -158251,6 +158337,52 @@ ${e?.toString()}`);
|
|
|
158251
158337
|
} catch {
|
|
158252
158338
|
}
|
|
158253
158339
|
}
|
|
158340
|
+
// Keep only the newest WEDGE_RING_SIZE timestamps per session.
|
|
158341
|
+
pushWedgeRing(ring, session, now) {
|
|
158342
|
+
const times = ring.get(session) ?? [];
|
|
158343
|
+
times.push(now);
|
|
158344
|
+
if (times.length > WEDGE_RING_SIZE) {
|
|
158345
|
+
times.splice(0, times.length - WEDGE_RING_SIZE);
|
|
158346
|
+
}
|
|
158347
|
+
ring.set(session, times);
|
|
158348
|
+
}
|
|
158349
|
+
// Watch this session's subscriptions for server-side delivery give-ups
|
|
158350
|
+
// (#365 v2 shadow). isTerminated true fires both on a peer cancel and on
|
|
158351
|
+
// the 3-strikes delivery give-up; only the give-up arrives without a
|
|
158352
|
+
// coincident inbound IM request, so that is the discriminator.
|
|
158353
|
+
hookSubscriptionGiveUps(session, peerSessions) {
|
|
158354
|
+
const key = session;
|
|
158355
|
+
if (this.wedgeHookedSessions.has(key)) {
|
|
158356
|
+
return;
|
|
158357
|
+
}
|
|
158358
|
+
const deleted = session.subscriptions?.deleted;
|
|
158359
|
+
if (typeof deleted?.on !== "function") {
|
|
158360
|
+
return;
|
|
158361
|
+
}
|
|
158362
|
+
this.wedgeHookedSessions.add(key);
|
|
158363
|
+
deleted.on((sub) => {
|
|
158364
|
+
if (sub?.isTerminated !== true) {
|
|
158365
|
+
return;
|
|
158366
|
+
}
|
|
158367
|
+
const now = Date.now();
|
|
158368
|
+
const fresh = (s) => {
|
|
158369
|
+
const at = this.lastImRequestAt.get(s);
|
|
158370
|
+
return at != null && now - at <= WEDGE_GIVE_UP_QUIET_MS;
|
|
158371
|
+
};
|
|
158372
|
+
if (fresh(key)) {
|
|
158373
|
+
return;
|
|
158374
|
+
}
|
|
158375
|
+
for (const s of peerSessions?.() ?? []) {
|
|
158376
|
+
if (s !== key && fresh(s)) {
|
|
158377
|
+
return;
|
|
158378
|
+
}
|
|
158379
|
+
}
|
|
158380
|
+
this.pushWedgeRing(this.giveUpTimesMs, key, now);
|
|
158381
|
+
this.log.debug(
|
|
158382
|
+
`wedge v2 give-up recorded sub=${sub?.subscriptionId}`
|
|
158383
|
+
);
|
|
158384
|
+
});
|
|
158385
|
+
}
|
|
158254
158386
|
closeStaleSession(sessionId) {
|
|
158255
158387
|
try {
|
|
158256
158388
|
const sessionManager = this.server.env.get(SessionManager);
|
|
@@ -158513,14 +158645,38 @@ ${e?.toString()}`);
|
|
|
158513
158645
|
const lastImRequestMsAgo = lastImAt != null ? now - lastImAt : null;
|
|
158514
158646
|
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
158515
158647
|
const lastRotatedMsAgo = lastRotatedAt != null ? now - lastRotatedAt : null;
|
|
158516
|
-
|
|
158648
|
+
const v1 = decideWedgeRotation({
|
|
158517
158649
|
subscriptionCount: s.subscriptions.size,
|
|
158518
158650
|
sessionAgeMs,
|
|
158519
158651
|
lastImRequestMsAgo,
|
|
158520
158652
|
lastRotatedMsAgo
|
|
158521
|
-
})
|
|
158653
|
+
});
|
|
158654
|
+
const lastCmdAt = this.lastCommandImAt.get(s);
|
|
158655
|
+
const commandSilenceMs = lastCmdAt != null ? now - lastCmdAt : null;
|
|
158656
|
+
const subscribeTimes = this.subscribeTimesMs.get(s) ?? [];
|
|
158657
|
+
const giveUpTimes = this.giveUpTimesMs.get(s) ?? [];
|
|
158658
|
+
const v2 = decideWedgeRotationV2({
|
|
158659
|
+
subscriptionCount: s.subscriptions.size,
|
|
158660
|
+
sessionAgeMs,
|
|
158661
|
+
commandSilenceMs,
|
|
158662
|
+
subscribeTimesMs: subscribeTimes,
|
|
158663
|
+
giveUpTimesMs: giveUpTimes,
|
|
158664
|
+
nowMs: now,
|
|
158665
|
+
lastRotatedMsAgo
|
|
158666
|
+
});
|
|
158667
|
+
const cmdSilenceMin = Math.round(
|
|
158668
|
+
(commandSilenceMs ?? sessionAgeMs) / 6e4
|
|
158669
|
+
);
|
|
158670
|
+
const v2Stats = `cmdSilenceMin=${cmdSilenceMin} subscribes30m=${countRecent(subscribeTimes, now)} giveUps30m=${countRecent(giveUpTimes, now)}`;
|
|
158671
|
+
if (v2 && !v1) {
|
|
158672
|
+
this.log.info(`wedge v2 would rotate session ${s.id}: ${v2Stats}`);
|
|
158673
|
+
}
|
|
158674
|
+
if (!v1) {
|
|
158522
158675
|
continue;
|
|
158523
158676
|
}
|
|
158677
|
+
this.log.info(
|
|
158678
|
+
`wedge v2 session ${s.id}: v1 rotated, v2 agree=${v2} ${v2Stats}`
|
|
158679
|
+
);
|
|
158524
158680
|
const silenceMin = Math.round(
|
|
158525
158681
|
(lastImRequestMsAgo ?? sessionAgeMs) / 6e4
|
|
158526
158682
|
);
|
|
@@ -164991,6 +165147,9 @@ var HaGenericSwitchServer = HaGenericSwitchServerMultiBase.set({
|
|
|
164991
165147
|
multiPressMax: 3
|
|
164992
165148
|
});
|
|
164993
165149
|
|
|
165150
|
+
// src/matter/endpoints/legacy/event/doorbell.ts
|
|
165151
|
+
init_home_assistant_entity_behavior();
|
|
165152
|
+
|
|
164994
165153
|
// src/matter/endpoints/legacy/event/index.ts
|
|
164995
165154
|
init_home_assistant_entity_behavior();
|
|
164996
165155
|
var EventEndpointTypeMulti = GenericSwitchDevice.with(
|
|
@@ -165033,6 +165192,33 @@ function EventDevice(homeAssistantEntity) {
|
|
|
165033
165192
|
});
|
|
165034
165193
|
}
|
|
165035
165194
|
|
|
165195
|
+
// src/matter/endpoints/legacy/event/doorbell.ts
|
|
165196
|
+
var DoorbellEndpointTypeMulti = DoorbellDevice.with(
|
|
165197
|
+
BasicInformationServer2,
|
|
165198
|
+
IdentifyServer2,
|
|
165199
|
+
HomeAssistantEntityBehavior,
|
|
165200
|
+
HaGenericSwitchServer
|
|
165201
|
+
);
|
|
165202
|
+
var DoorbellEndpointTypeSimple = DoorbellDevice.with(
|
|
165203
|
+
BasicInformationServer2,
|
|
165204
|
+
IdentifyServer2,
|
|
165205
|
+
HomeAssistantEntityBehavior,
|
|
165206
|
+
HaGenericSwitchServerSimple
|
|
165207
|
+
);
|
|
165208
|
+
function DoorbellDevice2(homeAssistantEntity) {
|
|
165209
|
+
const attrs = homeAssistantEntity.entity.state.attributes;
|
|
165210
|
+
const multiPressMax = detectMultiPressMax(attrs.event_types ?? []);
|
|
165211
|
+
if (multiPressMax >= 2) {
|
|
165212
|
+
return DoorbellEndpointTypeMulti.set({
|
|
165213
|
+
homeAssistantEntity,
|
|
165214
|
+
switch: { multiPressMax }
|
|
165215
|
+
});
|
|
165216
|
+
}
|
|
165217
|
+
return DoorbellEndpointTypeSimple.set({
|
|
165218
|
+
homeAssistantEntity
|
|
165219
|
+
});
|
|
165220
|
+
}
|
|
165221
|
+
|
|
165036
165222
|
// src/matter/endpoints/legacy/fan/index.ts
|
|
165037
165223
|
init_dist();
|
|
165038
165224
|
init_home_assistant_entity_behavior();
|
|
@@ -168613,6 +168799,38 @@ var ElectricalMeterType = ElectricalMeterDevice.with(
|
|
|
168613
168799
|
HaPowerTopologyServer
|
|
168614
168800
|
);
|
|
168615
168801
|
|
|
168802
|
+
// src/matter/endpoints/legacy/sensor/devices/electrical-utility-meter.ts
|
|
168803
|
+
init_home_assistant_entity_behavior();
|
|
168804
|
+
var HaMeterIdentificationServer = class extends MeterIdentificationServer {
|
|
168805
|
+
async initialize() {
|
|
168806
|
+
await super.initialize();
|
|
168807
|
+
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
168808
|
+
const mapping = homeAssistant.state.mapping;
|
|
168809
|
+
applyPatchState(this.state, {
|
|
168810
|
+
meterSerialNumber: mapping?.meterSerialNumber ?? null,
|
|
168811
|
+
pointOfDelivery: mapping?.pointOfDelivery ?? null
|
|
168812
|
+
});
|
|
168813
|
+
}
|
|
168814
|
+
};
|
|
168815
|
+
((HaMeterIdentificationServer2) => {
|
|
168816
|
+
class State extends MeterIdentificationServer.State {
|
|
168817
|
+
}
|
|
168818
|
+
HaMeterIdentificationServer2.State = State;
|
|
168819
|
+
})(HaMeterIdentificationServer || (HaMeterIdentificationServer = {}));
|
|
168820
|
+
var ElectricalUtilityMeterType = ElectricalUtilityMeterDevice.with(
|
|
168821
|
+
BasicInformationServer2,
|
|
168822
|
+
IdentifyServer2,
|
|
168823
|
+
HomeAssistantEntityBehavior,
|
|
168824
|
+
HaMeterIdentificationServer.set({
|
|
168825
|
+
meterType: null,
|
|
168826
|
+
pointOfDelivery: null,
|
|
168827
|
+
meterSerialNumber: null
|
|
168828
|
+
}),
|
|
168829
|
+
PowerServer,
|
|
168830
|
+
EnergyServer,
|
|
168831
|
+
HaPowerTopologyServer
|
|
168832
|
+
);
|
|
168833
|
+
|
|
168616
168834
|
// src/matter/endpoints/legacy/sensor/devices/energy-evse.ts
|
|
168617
168835
|
init_esm();
|
|
168618
168836
|
init_home_assistant_actions();
|
|
@@ -173433,6 +173651,8 @@ var matterDeviceTypeFactories = {
|
|
|
173433
173651
|
// Legacy SolarPower alias, kept for existing mappings.
|
|
173434
173652
|
electrical_sensor: (ha) => ElectricalSensorType.set({ homeAssistantEntity: ha }),
|
|
173435
173653
|
solar_power: (ha) => ElectricalSensorType.set({ homeAssistantEntity: ha }),
|
|
173654
|
+
// Matter 1.4 utility meter (0x0511), opt-in so existing pairings keep 0x0514.
|
|
173655
|
+
electrical_utility_meter: (ha) => ElectricalUtilityMeterType.set({ homeAssistantEntity: ha }),
|
|
173436
173656
|
evse: (ha) => energyEvseType().set({ homeAssistantEntity: ha }),
|
|
173437
173657
|
contact_sensor: (ha) => ContactSensorType.set({ homeAssistantEntity: ha }),
|
|
173438
173658
|
motion_sensor: (ha) => MotionSensorType.set({ homeAssistantEntity: ha }),
|
|
@@ -173444,6 +173664,7 @@ var matterDeviceTypeFactories = {
|
|
|
173444
173664
|
water_heater: WaterHeaterDevice2,
|
|
173445
173665
|
water_heater_management: WaterHeaterManagementDevice,
|
|
173446
173666
|
generic_switch: EventDevice,
|
|
173667
|
+
doorbell: DoorbellDevice2,
|
|
173447
173668
|
smoke_co_alarm: (ha) => SmokeAlarmType.set({ homeAssistantEntity: ha }),
|
|
173448
173669
|
water_freeze_detector: (ha) => WaterFreezeDetectorType.set({ homeAssistantEntity: ha }),
|
|
173449
173670
|
water_leak_detector: (ha) => WaterLeakDetectorType.set({ homeAssistantEntity: ha })
|
|
@@ -176895,6 +177116,12 @@ init_esm7();
|
|
|
176895
177116
|
import * as os10 from "node:os";
|
|
176896
177117
|
init_diagnostic_event_bus();
|
|
176897
177118
|
var imWrapMarker2 = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
|
|
177119
|
+
var commandMessageTypes2 = [
|
|
177120
|
+
MessageType.ReadRequest,
|
|
177121
|
+
MessageType.WriteRequest,
|
|
177122
|
+
MessageType.InvokeRequest,
|
|
177123
|
+
MessageType.TimedRequest
|
|
177124
|
+
];
|
|
176898
177125
|
var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
|
|
176899
177126
|
var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2 = 2500;
|
|
176900
177127
|
var MDNS_ADDRESS_CHECK_INTERVAL_MS2 = 6e4;
|
|
@@ -176933,6 +177160,13 @@ var ServerModeBridge = class {
|
|
|
176933
177160
|
lastImRequestAt = /* @__PURE__ */ new WeakMap();
|
|
176934
177161
|
wedgeLastRotatedAt = /* @__PURE__ */ new WeakMap();
|
|
176935
177162
|
wedgeWatchdogTimer = null;
|
|
177163
|
+
// V2 shadow signals (#365): last command-class request per session, plus
|
|
177164
|
+
// rings of subscribe and delivery give-up times (newest WEDGE_RING_SIZE).
|
|
177165
|
+
lastCommandImAt = /* @__PURE__ */ new WeakMap();
|
|
177166
|
+
subscribeTimesMs = /* @__PURE__ */ new WeakMap();
|
|
177167
|
+
giveUpTimesMs = /* @__PURE__ */ new WeakMap();
|
|
177168
|
+
// Sessions whose subscriptions.deleted we already hooked for give-ups.
|
|
177169
|
+
wedgeHookedSessions = /* @__PURE__ */ new WeakSet();
|
|
176936
177170
|
// Watches the advertised interface addresses so a dynamic ISP IPv6 prefix
|
|
176937
177171
|
// change forces a fresh operational announcement (#415).
|
|
176938
177172
|
mdnsAddressTimer = null;
|
|
@@ -177013,6 +177247,20 @@ var ServerModeBridge = class {
|
|
|
177013
177247
|
const lastImAt = this.lastImRequestAt.get(s);
|
|
177014
177248
|
const lastImRequestMsAgo = lastImAt != null ? nowMs - lastImAt : null;
|
|
177015
177249
|
const startedAt = this.sessionStartedAt.get(s.id);
|
|
177250
|
+
const lastCmdAt = this.lastCommandImAt.get(s);
|
|
177251
|
+
const lastCommandImRequestMsAgo = lastCmdAt != null ? nowMs - lastCmdAt : null;
|
|
177252
|
+
const subscribeTimes = this.subscribeTimesMs.get(s) ?? [];
|
|
177253
|
+
const giveUpTimes = this.giveUpTimesMs.get(s) ?? [];
|
|
177254
|
+
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
177255
|
+
const wedgeV2WouldRotate = decideWedgeRotationV2({
|
|
177256
|
+
subscriptionCount: subCount,
|
|
177257
|
+
sessionAgeMs: startedAt != null ? nowMs - startedAt : 0,
|
|
177258
|
+
commandSilenceMs: lastCommandImRequestMsAgo,
|
|
177259
|
+
subscribeTimesMs: subscribeTimes,
|
|
177260
|
+
giveUpTimesMs: giveUpTimes,
|
|
177261
|
+
nowMs,
|
|
177262
|
+
lastRotatedMsAgo: lastRotatedAt != null ? nowMs - lastRotatedAt : null
|
|
177263
|
+
});
|
|
177016
177264
|
return {
|
|
177017
177265
|
id: s.id,
|
|
177018
177266
|
peerNodeId: String(s.peerNodeId),
|
|
@@ -177022,6 +177270,10 @@ var ServerModeBridge = class {
|
|
|
177022
177270
|
lastActiveMsAgo,
|
|
177023
177271
|
lastAnyActivityMsAgo,
|
|
177024
177272
|
lastImRequestMsAgo,
|
|
177273
|
+
lastCommandImRequestMsAgo,
|
|
177274
|
+
subscribesLast30Min: countRecent(subscribeTimes, nowMs),
|
|
177275
|
+
giveUpsLast30Min: countRecent(giveUpTimes, nowMs),
|
|
177276
|
+
wedgeV2WouldRotate,
|
|
177025
177277
|
isPeerActive: Boolean(s.isPeerActive),
|
|
177026
177278
|
ageMsFromOpen: startedAt != null ? nowMs - startedAt : null
|
|
177027
177279
|
};
|
|
@@ -177245,6 +177497,7 @@ ${e?.toString()}`);
|
|
|
177245
177497
|
sessionManager.subscriptionsChanged.on(this.sessionDiagHandler);
|
|
177246
177498
|
this.sessionAddedHandler = (newSession) => {
|
|
177247
177499
|
this.sessionStartedAt.set(newSession.id, Date.now());
|
|
177500
|
+
hookGiveUpsWithPeers(newSession);
|
|
177248
177501
|
this.log.info(
|
|
177249
177502
|
`Session opened: id=${newSession.id} peer=${newSession.peerNodeId}`
|
|
177250
177503
|
);
|
|
@@ -177321,6 +177574,15 @@ ${e?.toString()}`);
|
|
|
177321
177574
|
sessionManager.sessions.added.on(this.sessionAddedHandler);
|
|
177322
177575
|
sessionManager.sessions.deleted.on(this.sessionDeletedHandler);
|
|
177323
177576
|
seedExistingSessionStarts(this.sessionStartedAt, sessionManager.sessions);
|
|
177577
|
+
const hookGiveUpsWithPeers = (sess) => this.hookSubscriptionGiveUps(
|
|
177578
|
+
sess,
|
|
177579
|
+
() => [...sessionManager.sessions].filter(
|
|
177580
|
+
(s) => s.peerNodeId === sess.peerNodeId && s.fabric?.fabricIndex === sess.fabric?.fabricIndex
|
|
177581
|
+
)
|
|
177582
|
+
);
|
|
177583
|
+
for (const sess of sessionManager.sessions) {
|
|
177584
|
+
hookGiveUpsWithPeers(sess);
|
|
177585
|
+
}
|
|
177324
177586
|
this.wireImRequestTracking();
|
|
177325
177587
|
} catch {
|
|
177326
177588
|
}
|
|
@@ -177339,7 +177601,14 @@ ${e?.toString()}`);
|
|
|
177339
177601
|
is.onNewExchange = (exchange, message) => {
|
|
177340
177602
|
const session = exchange.session;
|
|
177341
177603
|
if (session) {
|
|
177342
|
-
|
|
177604
|
+
const now = Date.now();
|
|
177605
|
+
this.lastImRequestAt.set(session, now);
|
|
177606
|
+
const type = message?.payloadHeader?.messageType;
|
|
177607
|
+
if (type === MessageType.SubscribeRequest) {
|
|
177608
|
+
this.pushWedgeRing(this.subscribeTimesMs, session, now);
|
|
177609
|
+
} else if (type != null && commandMessageTypes2.includes(type)) {
|
|
177610
|
+
this.lastCommandImAt.set(session, now);
|
|
177611
|
+
}
|
|
177343
177612
|
}
|
|
177344
177613
|
return original(exchange, message);
|
|
177345
177614
|
};
|
|
@@ -177347,6 +177616,52 @@ ${e?.toString()}`);
|
|
|
177347
177616
|
} catch {
|
|
177348
177617
|
}
|
|
177349
177618
|
}
|
|
177619
|
+
// Keep only the newest WEDGE_RING_SIZE timestamps per session.
|
|
177620
|
+
pushWedgeRing(ring, session, now) {
|
|
177621
|
+
const times = ring.get(session) ?? [];
|
|
177622
|
+
times.push(now);
|
|
177623
|
+
if (times.length > WEDGE_RING_SIZE) {
|
|
177624
|
+
times.splice(0, times.length - WEDGE_RING_SIZE);
|
|
177625
|
+
}
|
|
177626
|
+
ring.set(session, times);
|
|
177627
|
+
}
|
|
177628
|
+
// Watch this session's subscriptions for server-side delivery give-ups
|
|
177629
|
+
// (#365 v2 shadow). isTerminated true fires both on a peer cancel and on
|
|
177630
|
+
// the 3-strikes delivery give-up; only the give-up arrives without a
|
|
177631
|
+
// coincident inbound IM request, so that is the discriminator.
|
|
177632
|
+
hookSubscriptionGiveUps(session, peerSessions) {
|
|
177633
|
+
const key = session;
|
|
177634
|
+
if (this.wedgeHookedSessions.has(key)) {
|
|
177635
|
+
return;
|
|
177636
|
+
}
|
|
177637
|
+
const deleted = session.subscriptions?.deleted;
|
|
177638
|
+
if (typeof deleted?.on !== "function") {
|
|
177639
|
+
return;
|
|
177640
|
+
}
|
|
177641
|
+
this.wedgeHookedSessions.add(key);
|
|
177642
|
+
deleted.on((sub) => {
|
|
177643
|
+
if (sub?.isTerminated !== true) {
|
|
177644
|
+
return;
|
|
177645
|
+
}
|
|
177646
|
+
const now = Date.now();
|
|
177647
|
+
const fresh = (s) => {
|
|
177648
|
+
const at = this.lastImRequestAt.get(s);
|
|
177649
|
+
return at != null && now - at <= WEDGE_GIVE_UP_QUIET_MS;
|
|
177650
|
+
};
|
|
177651
|
+
if (fresh(key)) {
|
|
177652
|
+
return;
|
|
177653
|
+
}
|
|
177654
|
+
for (const s of peerSessions?.() ?? []) {
|
|
177655
|
+
if (s !== key && fresh(s)) {
|
|
177656
|
+
return;
|
|
177657
|
+
}
|
|
177658
|
+
}
|
|
177659
|
+
this.pushWedgeRing(this.giveUpTimesMs, key, now);
|
|
177660
|
+
this.log.debug(
|
|
177661
|
+
`wedge v2 give-up recorded sub=${sub?.subscriptionId}`
|
|
177662
|
+
);
|
|
177663
|
+
});
|
|
177664
|
+
}
|
|
177350
177665
|
closeStaleSession(sessionId) {
|
|
177351
177666
|
try {
|
|
177352
177667
|
const sessionManager = this.server.env.get(SessionManager);
|
|
@@ -177598,14 +177913,38 @@ ${e?.toString()}`);
|
|
|
177598
177913
|
const lastImRequestMsAgo = lastImAt != null ? now - lastImAt : null;
|
|
177599
177914
|
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
177600
177915
|
const lastRotatedMsAgo = lastRotatedAt != null ? now - lastRotatedAt : null;
|
|
177601
|
-
|
|
177916
|
+
const v1 = decideWedgeRotation({
|
|
177602
177917
|
subscriptionCount: s.subscriptions.size,
|
|
177603
177918
|
sessionAgeMs,
|
|
177604
177919
|
lastImRequestMsAgo,
|
|
177605
177920
|
lastRotatedMsAgo
|
|
177606
|
-
})
|
|
177921
|
+
});
|
|
177922
|
+
const lastCmdAt = this.lastCommandImAt.get(s);
|
|
177923
|
+
const commandSilenceMs = lastCmdAt != null ? now - lastCmdAt : null;
|
|
177924
|
+
const subscribeTimes = this.subscribeTimesMs.get(s) ?? [];
|
|
177925
|
+
const giveUpTimes = this.giveUpTimesMs.get(s) ?? [];
|
|
177926
|
+
const v2 = decideWedgeRotationV2({
|
|
177927
|
+
subscriptionCount: s.subscriptions.size,
|
|
177928
|
+
sessionAgeMs,
|
|
177929
|
+
commandSilenceMs,
|
|
177930
|
+
subscribeTimesMs: subscribeTimes,
|
|
177931
|
+
giveUpTimesMs: giveUpTimes,
|
|
177932
|
+
nowMs: now,
|
|
177933
|
+
lastRotatedMsAgo
|
|
177934
|
+
});
|
|
177935
|
+
const cmdSilenceMin = Math.round(
|
|
177936
|
+
(commandSilenceMs ?? sessionAgeMs) / 6e4
|
|
177937
|
+
);
|
|
177938
|
+
const v2Stats = `cmdSilenceMin=${cmdSilenceMin} subscribes30m=${countRecent(subscribeTimes, now)} giveUps30m=${countRecent(giveUpTimes, now)}`;
|
|
177939
|
+
if (v2 && !v1) {
|
|
177940
|
+
this.log.info(`wedge v2 would rotate session ${s.id}: ${v2Stats}`);
|
|
177941
|
+
}
|
|
177942
|
+
if (!v1) {
|
|
177607
177943
|
continue;
|
|
177608
177944
|
}
|
|
177945
|
+
this.log.info(
|
|
177946
|
+
`wedge v2 session ${s.id}: v1 rotated, v2 agree=${v2} ${v2Stats}`
|
|
177947
|
+
);
|
|
177609
177948
|
const silenceMin = Math.round(
|
|
177610
177949
|
(lastImRequestMsAgo ?? sessionAgeMs) / 6e4
|
|
177611
177950
|
);
|