@riddix/hamh 2.1.0-alpha.870 → 2.1.0-alpha.872
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/dist/backend/cli.js
CHANGED
|
@@ -110398,9 +110398,9 @@ function inspectEndpoint(endpoint, properties, behindAggregator = false) {
|
|
|
110398
110398
|
properties.supportsEthernet = true;
|
|
110399
110399
|
}
|
|
110400
110400
|
}
|
|
110401
|
-
const
|
|
110402
|
-
if (
|
|
110403
|
-
for (const { type, isOperational } of
|
|
110401
|
+
const networkInterfaces5 = endpoint.maybeStateOf(GeneralDiagnosticsClient)?.networkInterfaces;
|
|
110402
|
+
if (networkInterfaces5 !== void 0) {
|
|
110403
|
+
for (const { type, isOperational } of networkInterfaces5) {
|
|
110404
110404
|
if (type === GeneralDiagnostics3.InterfaceType.WiFi) {
|
|
110405
110405
|
properties.wifiActive = (properties.wifiActive ?? false) || isOperational;
|
|
110406
110406
|
} else if (type === GeneralDiagnostics3.InterfaceType.Ethernet) {
|
|
@@ -137901,8 +137901,8 @@ function systemApi(version2) {
|
|
|
137901
137901
|
}
|
|
137902
137902
|
function getNetworkInterfaces2() {
|
|
137903
137903
|
const interfaces = [];
|
|
137904
|
-
const
|
|
137905
|
-
for (const [name, ifaceList] of Object.entries(
|
|
137904
|
+
const networkInterfaces5 = os4.networkInterfaces();
|
|
137905
|
+
for (const [name, ifaceList] of Object.entries(networkInterfaces5)) {
|
|
137906
137906
|
if (!ifaceList) continue;
|
|
137907
137907
|
for (const iface of ifaceList) {
|
|
137908
137908
|
const family = String(iface.family);
|
|
@@ -157864,8 +157864,6 @@ var PluginManager = class {
|
|
|
157864
157864
|
|
|
157865
157865
|
// src/services/bridges/bridge.ts
|
|
157866
157866
|
init_dist();
|
|
157867
|
-
init_esm7();
|
|
157868
|
-
import * as os9 from "node:os";
|
|
157869
157867
|
init_protocol3();
|
|
157870
157868
|
|
|
157871
157869
|
// src/utils/json/create-bridge-server-config.ts
|
|
@@ -158178,6 +158176,41 @@ var BridgeServerNode = class extends ServerNode {
|
|
|
158178
158176
|
}
|
|
158179
158177
|
};
|
|
158180
158178
|
|
|
158179
|
+
// src/utils/ensure-commissioning-config.ts
|
|
158180
|
+
init_protocol3();
|
|
158181
|
+
var ServerNodeConfigProvider = class extends CommissioningConfigProvider {
|
|
158182
|
+
#node;
|
|
158183
|
+
constructor(node) {
|
|
158184
|
+
super();
|
|
158185
|
+
this.#node = node;
|
|
158186
|
+
}
|
|
158187
|
+
get values() {
|
|
158188
|
+
const { commissioning, productDescription, network } = this.#node.state;
|
|
158189
|
+
return {
|
|
158190
|
+
...commissioning,
|
|
158191
|
+
productDescription,
|
|
158192
|
+
ble: !!network.ble
|
|
158193
|
+
};
|
|
158194
|
+
}
|
|
158195
|
+
};
|
|
158196
|
+
function ensureCommissioningConfig(server) {
|
|
158197
|
+
if (!server.env.has(CommissioningConfigProvider)) {
|
|
158198
|
+
server.env.set(
|
|
158199
|
+
CommissioningConfigProvider,
|
|
158200
|
+
new ServerNodeConfigProvider(server)
|
|
158201
|
+
);
|
|
158202
|
+
}
|
|
158203
|
+
}
|
|
158204
|
+
|
|
158205
|
+
// src/services/bridges/bridge.ts
|
|
158206
|
+
init_diagnostic_event_bus();
|
|
158207
|
+
|
|
158208
|
+
// src/services/bridges/session-supervisor.ts
|
|
158209
|
+
init_dist();
|
|
158210
|
+
init_esm7();
|
|
158211
|
+
import * as os9 from "node:os";
|
|
158212
|
+
init_protocol3();
|
|
158213
|
+
|
|
158181
158214
|
// src/matter/mdns-address-watch.ts
|
|
158182
158215
|
function collectAdvertisedAddresses(interfaces, netInterface, stripGlobalIpv6, ipv4Enabled = true) {
|
|
158183
158216
|
const names = netInterface ? [netInterface] : Object.keys(interfaces);
|
|
@@ -158221,33 +158254,7 @@ async function runMdnsAddressWatchTick(deps) {
|
|
|
158221
158254
|
return next;
|
|
158222
158255
|
}
|
|
158223
158256
|
|
|
158224
|
-
// src/
|
|
158225
|
-
init_protocol3();
|
|
158226
|
-
var ServerNodeConfigProvider = class extends CommissioningConfigProvider {
|
|
158227
|
-
#node;
|
|
158228
|
-
constructor(node) {
|
|
158229
|
-
super();
|
|
158230
|
-
this.#node = node;
|
|
158231
|
-
}
|
|
158232
|
-
get values() {
|
|
158233
|
-
const { commissioning, productDescription, network } = this.#node.state;
|
|
158234
|
-
return {
|
|
158235
|
-
...commissioning,
|
|
158236
|
-
productDescription,
|
|
158237
|
-
ble: !!network.ble
|
|
158238
|
-
};
|
|
158239
|
-
}
|
|
158240
|
-
};
|
|
158241
|
-
function ensureCommissioningConfig(server) {
|
|
158242
|
-
if (!server.env.has(CommissioningConfigProvider)) {
|
|
158243
|
-
server.env.set(
|
|
158244
|
-
CommissioningConfigProvider,
|
|
158245
|
-
new ServerNodeConfigProvider(server)
|
|
158246
|
-
);
|
|
158247
|
-
}
|
|
158248
|
-
}
|
|
158249
|
-
|
|
158250
|
-
// src/services/bridges/bridge.ts
|
|
158257
|
+
// src/services/bridges/session-supervisor.ts
|
|
158251
158258
|
init_diagnostic_event_bus();
|
|
158252
158259
|
|
|
158253
158260
|
// src/services/bridges/session-rotation.ts
|
|
@@ -158408,7 +158415,7 @@ function decideWedgeRotationV2(input) {
|
|
|
158408
158415
|
return input.lastRotatedMsAgo == null || input.lastRotatedMsAgo > WEDGE_MIN_ROTATE_INTERVAL_MS;
|
|
158409
158416
|
}
|
|
158410
158417
|
|
|
158411
|
-
// src/services/bridges/
|
|
158418
|
+
// src/services/bridges/session-supervisor.ts
|
|
158412
158419
|
var imWrapMarker = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
|
|
158413
158420
|
var commandMessageTypes = [
|
|
158414
158421
|
MessageType.ReadRequest,
|
|
@@ -158416,46 +158423,21 @@ var commandMessageTypes = [
|
|
|
158416
158423
|
MessageType.InvokeRequest,
|
|
158417
158424
|
MessageType.TimedRequest
|
|
158418
158425
|
];
|
|
158419
|
-
var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
|
|
158420
158426
|
var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS = 2500;
|
|
158421
158427
|
var MDNS_ADDRESS_CHECK_INTERVAL_MS = 6e4;
|
|
158422
|
-
var
|
|
158423
|
-
constructor(
|
|
158428
|
+
var SessionSupervisor = class {
|
|
158429
|
+
constructor(log, server, dataProvider, bridgeInfo, defaultMaxAgeHours) {
|
|
158430
|
+
this.log = log;
|
|
158431
|
+
this.server = server;
|
|
158424
158432
|
this.dataProvider = dataProvider;
|
|
158425
|
-
this.
|
|
158426
|
-
this.
|
|
158427
|
-
this.log = logger255.get(`Bridge / ${dataProvider.id}`);
|
|
158428
|
-
this.server = new BridgeServerNode(
|
|
158429
|
-
env,
|
|
158430
|
-
this.dataProvider,
|
|
158431
|
-
this.endpointManager.root,
|
|
158432
|
-
this.serverOptions
|
|
158433
|
-
);
|
|
158434
|
-
const { basicInformation } = this.dataProvider;
|
|
158435
|
-
this.log.debugCtx("Root bridge BasicInformation configured", {
|
|
158436
|
-
vendorName: basicInformation.vendorName,
|
|
158437
|
-
productName: basicInformation.productName,
|
|
158438
|
-
productLabel: basicInformation.productLabel,
|
|
158439
|
-
hardwareVersion: basicInformation.hardwareVersion,
|
|
158440
|
-
hardwareVersionString: basicInformation.hardwareVersionString,
|
|
158441
|
-
softwareVersion: basicInformation.softwareVersion,
|
|
158442
|
-
softwareVersionString: basicInformation.softwareVersionString
|
|
158443
|
-
});
|
|
158433
|
+
this.bridgeInfo = bridgeInfo;
|
|
158434
|
+
this.defaultMaxAgeHours = defaultMaxAgeHours;
|
|
158444
158435
|
}
|
|
158445
|
-
dataProvider;
|
|
158446
|
-
endpointManager;
|
|
158447
|
-
serverOptions;
|
|
158448
158436
|
log;
|
|
158449
158437
|
server;
|
|
158450
|
-
|
|
158451
|
-
|
|
158452
|
-
|
|
158453
|
-
};
|
|
158454
|
-
// Called whenever the bridge status changes. Set by BridgeService to
|
|
158455
|
-
// broadcast updates via WebSocket so the frontend sees every transition
|
|
158456
|
-
// (e.g. Stopped → Starting → Running).
|
|
158457
|
-
onStatusChange;
|
|
158458
|
-
autoForceSyncTimer = null;
|
|
158438
|
+
dataProvider;
|
|
158439
|
+
bridgeInfo;
|
|
158440
|
+
defaultMaxAgeHours;
|
|
158459
158441
|
deadSessionTimer = null;
|
|
158460
158442
|
staleSessionTimers = /* @__PURE__ */ new Map();
|
|
158461
158443
|
// Age-based session rotation (#287): track when each session opened so an
|
|
@@ -158484,13 +158466,6 @@ var Bridge = class {
|
|
|
158484
158466
|
// refreshOperationalAdvertisement can't overlap and re-announce off a stale
|
|
158485
158467
|
// snapshot.
|
|
158486
158468
|
mdnsCheckInFlight = false;
|
|
158487
|
-
// Serialize concurrent lifecycle calls so auto-recovery and a manual
|
|
158488
|
-
// restartBridge can't race past each other's Starting/Stopping states.
|
|
158489
|
-
startInFlight;
|
|
158490
|
-
stopInFlight;
|
|
158491
|
-
// Tracks the last synced state JSON per entity to avoid pushing unchanged states.
|
|
158492
|
-
// Key: entity_id, Value: JSON.stringify of entity.state
|
|
158493
|
-
lastSyncedStates = /* @__PURE__ */ new Map();
|
|
158494
158469
|
// Session lifecycle diagnostic handlers (non-destructive, logging only).
|
|
158495
158470
|
// biome-ignore lint/suspicious/noExplicitAny: matter.js internal types
|
|
158496
158471
|
sessionDiagHandler;
|
|
@@ -158501,17 +158476,24 @@ var Bridge = class {
|
|
|
158501
158476
|
// Warns when Alexa pairs on a non-5540 port, the fabric rolls back later (#401).
|
|
158502
158477
|
// biome-ignore lint/suspicious/noExplicitAny: matter.js internal types
|
|
158503
158478
|
fabricAddedHandler;
|
|
158504
|
-
|
|
158505
|
-
|
|
158479
|
+
// after server.start(); wireSessionDiagnostics self-unwires first because
|
|
158480
|
+
// factoryReset restarts without going through stop()
|
|
158481
|
+
start() {
|
|
158482
|
+
this.wireSessionDiagnostics();
|
|
158483
|
+
this.wireFabricWarnings();
|
|
158484
|
+
this.startSessionRotation();
|
|
158485
|
+
this.startWedgeWatchdog();
|
|
158486
|
+
this.startMdnsAddressWatch();
|
|
158506
158487
|
}
|
|
158507
|
-
|
|
158508
|
-
|
|
158509
|
-
|
|
158510
|
-
|
|
158511
|
-
|
|
158512
|
-
|
|
158513
|
-
|
|
158514
|
-
);
|
|
158488
|
+
// before server.cancel(); unwireSessionDiagnostics also stops the timers
|
|
158489
|
+
stop() {
|
|
158490
|
+
this.unwireSessionDiagnostics();
|
|
158491
|
+
this.unwireFabricWarnings();
|
|
158492
|
+
}
|
|
158493
|
+
// bridge config changed while running
|
|
158494
|
+
reconfigure() {
|
|
158495
|
+
this.startSessionRotation();
|
|
158496
|
+
this.startWedgeWatchdog();
|
|
158515
158497
|
}
|
|
158516
158498
|
getSessionInfo() {
|
|
158517
158499
|
try {
|
|
@@ -158590,218 +158572,6 @@ var Bridge = class {
|
|
|
158590
158572
|
};
|
|
158591
158573
|
}
|
|
158592
158574
|
}
|
|
158593
|
-
get aggregator() {
|
|
158594
|
-
return this.endpointManager.root;
|
|
158595
|
-
}
|
|
158596
|
-
/**
|
|
158597
|
-
* The entity id and numeric Matter device type of every exposed endpoint,
|
|
158598
|
-
* walking composed sub-endpoints. Used to warn when a device type is not
|
|
158599
|
-
* supported by a commissioned controller (#365 class).
|
|
158600
|
-
*/
|
|
158601
|
-
getExposedDeviceTypes() {
|
|
158602
|
-
const out = [];
|
|
158603
|
-
const collect = (ep, inheritedEntityId) => {
|
|
158604
|
-
const entityId = ep.entityId ?? inheritedEntityId;
|
|
158605
|
-
const deviceTypeId = ep.type?.deviceType;
|
|
158606
|
-
if (typeof deviceTypeId === "number" && entityId) {
|
|
158607
|
-
out.push({ entityId, deviceTypeId });
|
|
158608
|
-
}
|
|
158609
|
-
for (const child of ep.parts) {
|
|
158610
|
-
collect(child, entityId);
|
|
158611
|
-
}
|
|
158612
|
-
};
|
|
158613
|
-
for (const ep of this.aggregator.parts) {
|
|
158614
|
-
collect(ep);
|
|
158615
|
-
}
|
|
158616
|
-
return out;
|
|
158617
|
-
}
|
|
158618
|
-
get pluginInfo() {
|
|
158619
|
-
return this.endpointManager.getPluginInfo();
|
|
158620
|
-
}
|
|
158621
|
-
async enablePlugin(pluginName) {
|
|
158622
|
-
return await this.endpointManager.enablePlugin(pluginName);
|
|
158623
|
-
}
|
|
158624
|
-
async disablePlugin(pluginName) {
|
|
158625
|
-
return await this.endpointManager.disablePlugin(pluginName);
|
|
158626
|
-
}
|
|
158627
|
-
resetPlugin(pluginName) {
|
|
158628
|
-
this.endpointManager.resetPlugin(pluginName);
|
|
158629
|
-
}
|
|
158630
|
-
getPluginConfigSchema(pluginName) {
|
|
158631
|
-
return this.endpointManager.getPluginConfigSchema(pluginName);
|
|
158632
|
-
}
|
|
158633
|
-
async updatePluginConfig(pluginName, config8) {
|
|
158634
|
-
const result = await this.endpointManager.updatePluginConfig(
|
|
158635
|
-
pluginName,
|
|
158636
|
-
config8
|
|
158637
|
-
);
|
|
158638
|
-
if (result && pluginName === "camera") {
|
|
158639
|
-
await this.applyCameraTcp(config8);
|
|
158640
|
-
}
|
|
158641
|
-
return result;
|
|
158642
|
-
}
|
|
158643
|
-
// #419: cameras need Matter over TCP because the WebRTC offer exceeds the UDP
|
|
158644
|
-
// message size. Match the bridge listener to the saved camera list. Changing
|
|
158645
|
-
// network config takes effect on (re)start, so bounce the bridge if running.
|
|
158646
|
-
async applyCameraTcp(config8) {
|
|
158647
|
-
const want = parseCameraList(config8.cameras).length > 0 || !!this.dataProvider.featureFlags?.enableMatterTcp;
|
|
158648
|
-
const have = !!this.server.state.network.tcp;
|
|
158649
|
-
if (want === have) {
|
|
158650
|
-
return;
|
|
158651
|
-
}
|
|
158652
|
-
this.log.info(
|
|
158653
|
-
want ? "cameras configured, enabling matter over tcp (#419)" : "no cameras left, disabling matter over tcp (#419)"
|
|
158654
|
-
);
|
|
158655
|
-
const running = this.status.code === BridgeStatus.Running;
|
|
158656
|
-
if (running) {
|
|
158657
|
-
await this.stop();
|
|
158658
|
-
}
|
|
158659
|
-
try {
|
|
158660
|
-
await this.server.set({
|
|
158661
|
-
network: { tcp: want ? CAMERA_TCP_CONFIG : void 0 }
|
|
158662
|
-
});
|
|
158663
|
-
} catch (e) {
|
|
158664
|
-
this.log.warn(
|
|
158665
|
-
"Could not apply tcp live, restart matterhub to apply it:",
|
|
158666
|
-
e
|
|
158667
|
-
);
|
|
158668
|
-
}
|
|
158669
|
-
if (running) {
|
|
158670
|
-
await this.start();
|
|
158671
|
-
}
|
|
158672
|
-
}
|
|
158673
|
-
async initialize() {
|
|
158674
|
-
await this.server.construction.ready.then();
|
|
158675
|
-
await this.refreshDevices();
|
|
158676
|
-
}
|
|
158677
|
-
async dispose() {
|
|
158678
|
-
await this.stop();
|
|
158679
|
-
}
|
|
158680
|
-
async refreshDevices() {
|
|
158681
|
-
await this.endpointManager.refreshDevices();
|
|
158682
|
-
const currentEntityIds = new Set(
|
|
158683
|
-
[...this.aggregator.parts].map(
|
|
158684
|
-
(p) => p.entityId
|
|
158685
|
-
)
|
|
158686
|
-
);
|
|
158687
|
-
for (const entityId of this.lastSyncedStates.keys()) {
|
|
158688
|
-
if (!currentEntityIds.has(entityId)) {
|
|
158689
|
-
this.lastSyncedStates.delete(entityId);
|
|
158690
|
-
}
|
|
158691
|
-
}
|
|
158692
|
-
}
|
|
158693
|
-
setStatus(status3) {
|
|
158694
|
-
this.status = status3;
|
|
158695
|
-
this.onStatusChange?.();
|
|
158696
|
-
}
|
|
158697
|
-
async start() {
|
|
158698
|
-
if (this.status.code === BridgeStatus.Running) {
|
|
158699
|
-
return;
|
|
158700
|
-
}
|
|
158701
|
-
if (this.startInFlight) {
|
|
158702
|
-
return this.startInFlight;
|
|
158703
|
-
}
|
|
158704
|
-
this.startInFlight = this.runStart().finally(() => {
|
|
158705
|
-
this.startInFlight = void 0;
|
|
158706
|
-
});
|
|
158707
|
-
return this.startInFlight;
|
|
158708
|
-
}
|
|
158709
|
-
async runStart() {
|
|
158710
|
-
this.lastSyncedStates.clear();
|
|
158711
|
-
try {
|
|
158712
|
-
this.setStatus({
|
|
158713
|
-
code: BridgeStatus.Starting,
|
|
158714
|
-
reason: "The bridge is starting... Please wait."
|
|
158715
|
-
});
|
|
158716
|
-
await this.refreshDevices();
|
|
158717
|
-
logMemoryUsage(
|
|
158718
|
-
this.log,
|
|
158719
|
-
`after refreshDevices (${this.aggregator.parts.size} endpoints)`
|
|
158720
|
-
);
|
|
158721
|
-
this.endpointManager.startObserving();
|
|
158722
|
-
ensureCommissioningConfig(this.server);
|
|
158723
|
-
applyLegacySpecSessionParameters(
|
|
158724
|
-
this.server.env.get(SessionManager),
|
|
158725
|
-
this.dataProvider.featureFlags
|
|
158726
|
-
);
|
|
158727
|
-
await this.server.setStateOf(
|
|
158728
|
-
BasicInformationServer,
|
|
158729
|
-
specVersionValues(this.dataProvider.featureFlags)
|
|
158730
|
-
);
|
|
158731
|
-
await applyTcpFlagBeforeStart(
|
|
158732
|
-
this.server,
|
|
158733
|
-
this.dataProvider.featureFlags
|
|
158734
|
-
);
|
|
158735
|
-
await this.server.start();
|
|
158736
|
-
await this.endpointManager.startPlugins();
|
|
158737
|
-
this.setStatus({ code: BridgeStatus.Running });
|
|
158738
|
-
this.startAutoForceSyncIfEnabled();
|
|
158739
|
-
if (this.dataProvider.featureFlags?.autoForceSync) {
|
|
158740
|
-
this.forceSync().catch((e) => {
|
|
158741
|
-
this.log.warn("Startup force sync failed:", e);
|
|
158742
|
-
});
|
|
158743
|
-
}
|
|
158744
|
-
this.wireSessionDiagnostics();
|
|
158745
|
-
this.wireFabricWarnings();
|
|
158746
|
-
this.startSessionRotation();
|
|
158747
|
-
this.startWedgeWatchdog();
|
|
158748
|
-
this.startMdnsAddressWatch();
|
|
158749
|
-
logMemoryUsage(this.log, "bridge running");
|
|
158750
|
-
diagnosticEventBus.emit("bridge_started", `Bridge started`, {
|
|
158751
|
-
bridgeId: this.id,
|
|
158752
|
-
bridgeName: this.dataProvider.name,
|
|
158753
|
-
details: { deviceCount: this.aggregator.parts.size }
|
|
158754
|
-
});
|
|
158755
|
-
} catch (e) {
|
|
158756
|
-
const reason = "Failed to start bridge due to error:";
|
|
158757
|
-
this.log.error(reason, e);
|
|
158758
|
-
await this.stop(BridgeStatus.Failed, `${reason}
|
|
158759
|
-
${e?.toString()}`);
|
|
158760
|
-
}
|
|
158761
|
-
}
|
|
158762
|
-
async stop(code = BridgeStatus.Stopped, reason = "Manually stopped") {
|
|
158763
|
-
if (this.stopInFlight) {
|
|
158764
|
-
return this.stopInFlight;
|
|
158765
|
-
}
|
|
158766
|
-
this.stopInFlight = this.runStop(code, reason).finally(() => {
|
|
158767
|
-
this.stopInFlight = void 0;
|
|
158768
|
-
});
|
|
158769
|
-
return this.stopInFlight;
|
|
158770
|
-
}
|
|
158771
|
-
async runStop(code, reason) {
|
|
158772
|
-
this.unwireSessionDiagnostics();
|
|
158773
|
-
this.unwireFabricWarnings();
|
|
158774
|
-
this.stopAutoForceSync();
|
|
158775
|
-
await this.closeActiveSessions();
|
|
158776
|
-
await this.endpointManager.stopPlugins();
|
|
158777
|
-
this.endpointManager.stopObserving();
|
|
158778
|
-
try {
|
|
158779
|
-
await this.server.cancel();
|
|
158780
|
-
} catch (e) {
|
|
158781
|
-
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
158782
|
-
if (!errorMessage.includes("mutex-closed")) {
|
|
158783
|
-
this.log.warn("Error stopping bridge server:", e);
|
|
158784
|
-
}
|
|
158785
|
-
}
|
|
158786
|
-
this.setStatus({ code, reason });
|
|
158787
|
-
diagnosticEventBus.emit("bridge_stopped", `Bridge stopped: ${reason}`, {
|
|
158788
|
-
bridgeId: this.id,
|
|
158789
|
-
bridgeName: this.dataProvider.name
|
|
158790
|
-
});
|
|
158791
|
-
}
|
|
158792
|
-
startAutoForceSyncIfEnabled() {
|
|
158793
|
-
this.stopAutoForceSync();
|
|
158794
|
-
const forceSyncEnabled = this.dataProvider.featureFlags?.autoForceSync ?? false;
|
|
158795
|
-
if (!forceSyncEnabled) {
|
|
158796
|
-
return;
|
|
158797
|
-
}
|
|
158798
|
-
this.autoForceSyncTimer = setInterval(() => {
|
|
158799
|
-
this.forceSync().catch((e) => {
|
|
158800
|
-
this.log.warn("Auto force sync failed:", e);
|
|
158801
|
-
});
|
|
158802
|
-
}, AUTO_FORCE_SYNC_INTERVAL_MS);
|
|
158803
|
-
this.log.info(`Force sync: every ${AUTO_FORCE_SYNC_INTERVAL_MS / 1e3}s`);
|
|
158804
|
-
}
|
|
158805
158575
|
wireSessionDiagnostics() {
|
|
158806
158576
|
this.unwireSessionDiagnostics();
|
|
158807
158577
|
try {
|
|
@@ -158829,8 +158599,8 @@ ${e?.toString()}`);
|
|
|
158829
158599
|
"subscription_changed",
|
|
158830
158600
|
`Session ${session.id}: ${session.subscriptions.size} subs (total ${totalSubs})`,
|
|
158831
158601
|
{
|
|
158832
|
-
bridgeId: this.
|
|
158833
|
-
bridgeName: this.
|
|
158602
|
+
bridgeId: this.bridgeInfo().id,
|
|
158603
|
+
bridgeName: this.bridgeInfo().name,
|
|
158834
158604
|
details: {
|
|
158835
158605
|
sessionId: session.id,
|
|
158836
158606
|
sessionSubs: session.subscriptions.size,
|
|
@@ -158886,8 +158656,8 @@ ${e?.toString()}`);
|
|
|
158886
158656
|
"session_opened",
|
|
158887
158657
|
`Session ${newSession.id} opened (peer ${newSession.peerNodeId})`,
|
|
158888
158658
|
{
|
|
158889
|
-
bridgeId: this.
|
|
158890
|
-
bridgeName: this.
|
|
158659
|
+
bridgeId: this.bridgeInfo().id,
|
|
158660
|
+
bridgeName: this.bridgeInfo().name,
|
|
158891
158661
|
details: { sessionId: newSession.id }
|
|
158892
158662
|
}
|
|
158893
158663
|
);
|
|
@@ -158954,8 +158724,8 @@ ${e?.toString()}`);
|
|
|
158954
158724
|
"session_closed",
|
|
158955
158725
|
`Session ${session.id} closed (peer ${session.peerNodeId})`,
|
|
158956
158726
|
{
|
|
158957
|
-
bridgeId: this.
|
|
158958
|
-
bridgeName: this.
|
|
158727
|
+
bridgeId: this.bridgeInfo().id,
|
|
158728
|
+
bridgeName: this.bridgeInfo().name,
|
|
158959
158729
|
details: {
|
|
158960
158730
|
sessionId: session.id,
|
|
158961
158731
|
remainingSessions: sessions.length
|
|
@@ -159242,12 +159012,6 @@ ${e?.toString()}`);
|
|
|
159242
159012
|
}
|
|
159243
159013
|
this.fabricAddedHandler = void 0;
|
|
159244
159014
|
}
|
|
159245
|
-
stopAutoForceSync() {
|
|
159246
|
-
if (this.autoForceSyncTimer) {
|
|
159247
|
-
clearInterval(this.autoForceSyncTimer);
|
|
159248
|
-
this.autoForceSyncTimer = null;
|
|
159249
|
-
}
|
|
159250
|
-
}
|
|
159251
159015
|
// Start periodic age-based session rotation (#287). Aging out a controller's
|
|
159252
159016
|
// session forces it to re-establish and re-subscribe, recovering a wedged
|
|
159253
159017
|
// Alexa subscription that would otherwise stay stuck until a restart.
|
|
@@ -159429,10 +159193,9 @@ ${e?.toString()}`);
|
|
|
159429
159193
|
}
|
|
159430
159194
|
});
|
|
159431
159195
|
}
|
|
159432
|
-
//
|
|
159433
|
-
// aggregator bridge
|
|
159434
|
-
//
|
|
159435
|
-
// here: it stays disabled unless set via config or the env var.
|
|
159196
|
+
// Env var unset or garbage falls back to defaultMaxAgeHours: 0 on the
|
|
159197
|
+
// aggregator bridge (rotation opt-in, one controller session holds many
|
|
159198
|
+
// devices), DEFAULT_SESSION_MAX_AGE_HOURS in server mode.
|
|
159436
159199
|
readSessionMaxAgeHours() {
|
|
159437
159200
|
const { min, max } = SESSION_MAX_AGE_HOURS_RANGE;
|
|
159438
159201
|
const fromConfig = this.dataProvider.sessionMaxAgeHours;
|
|
@@ -159444,14 +159207,14 @@ ${e?.toString()}`);
|
|
|
159444
159207
|
}
|
|
159445
159208
|
const raw = process.env.HAMH_MATTER_SESSION_MAX_AGE_HOURS;
|
|
159446
159209
|
if (raw == null || raw === "") {
|
|
159447
|
-
return
|
|
159210
|
+
return this.defaultMaxAgeHours;
|
|
159448
159211
|
}
|
|
159449
159212
|
const parsed = parseSessionMaxAgeHours(raw);
|
|
159450
159213
|
if (parsed == null) {
|
|
159451
159214
|
this.log.warn(
|
|
159452
|
-
`Invalid HAMH_MATTER_SESSION_MAX_AGE_HOURS=${raw}, disabling session rotation`
|
|
159215
|
+
`Invalid HAMH_MATTER_SESSION_MAX_AGE_HOURS=${raw}, ${this.defaultMaxAgeHours === 0 ? "disabling session rotation" : `falling back to ${this.defaultMaxAgeHours}h`}`
|
|
159453
159216
|
);
|
|
159454
|
-
return
|
|
159217
|
+
return this.defaultMaxAgeHours;
|
|
159455
159218
|
}
|
|
159456
159219
|
return parsed;
|
|
159457
159220
|
}
|
|
@@ -159489,14 +159252,297 @@ ${e?.toString()}`);
|
|
|
159489
159252
|
} catch {
|
|
159490
159253
|
}
|
|
159491
159254
|
}
|
|
159255
|
+
};
|
|
159256
|
+
|
|
159257
|
+
// src/services/bridges/bridge.ts
|
|
159258
|
+
var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
|
|
159259
|
+
var Bridge = class {
|
|
159260
|
+
constructor(env, logger255, dataProvider, endpointManager, serverOptions) {
|
|
159261
|
+
this.dataProvider = dataProvider;
|
|
159262
|
+
this.endpointManager = endpointManager;
|
|
159263
|
+
this.serverOptions = serverOptions;
|
|
159264
|
+
this.log = logger255.get(`Bridge / ${dataProvider.id}`);
|
|
159265
|
+
this.server = new BridgeServerNode(
|
|
159266
|
+
env,
|
|
159267
|
+
this.dataProvider,
|
|
159268
|
+
this.endpointManager.root,
|
|
159269
|
+
this.serverOptions
|
|
159270
|
+
);
|
|
159271
|
+
this.sessions = new SessionSupervisor(
|
|
159272
|
+
this.log,
|
|
159273
|
+
this.server,
|
|
159274
|
+
this.dataProvider,
|
|
159275
|
+
() => this.data,
|
|
159276
|
+
0
|
|
159277
|
+
);
|
|
159278
|
+
const { basicInformation } = this.dataProvider;
|
|
159279
|
+
this.log.debugCtx("Root bridge BasicInformation configured", {
|
|
159280
|
+
vendorName: basicInformation.vendorName,
|
|
159281
|
+
productName: basicInformation.productName,
|
|
159282
|
+
productLabel: basicInformation.productLabel,
|
|
159283
|
+
hardwareVersion: basicInformation.hardwareVersion,
|
|
159284
|
+
hardwareVersionString: basicInformation.hardwareVersionString,
|
|
159285
|
+
softwareVersion: basicInformation.softwareVersion,
|
|
159286
|
+
softwareVersionString: basicInformation.softwareVersionString
|
|
159287
|
+
});
|
|
159288
|
+
}
|
|
159289
|
+
dataProvider;
|
|
159290
|
+
endpointManager;
|
|
159291
|
+
serverOptions;
|
|
159292
|
+
log;
|
|
159293
|
+
server;
|
|
159294
|
+
sessions;
|
|
159295
|
+
status = {
|
|
159296
|
+
code: BridgeStatus.Stopped,
|
|
159297
|
+
reason: void 0
|
|
159298
|
+
};
|
|
159299
|
+
// Called whenever the bridge status changes. Set by BridgeService to
|
|
159300
|
+
// broadcast updates via WebSocket so the frontend sees every transition
|
|
159301
|
+
// (e.g. Stopped → Starting → Running).
|
|
159302
|
+
onStatusChange;
|
|
159303
|
+
autoForceSyncTimer = null;
|
|
159304
|
+
// Serialize concurrent lifecycle calls so auto-recovery and a manual
|
|
159305
|
+
// restartBridge can't race past each other's Starting/Stopping states.
|
|
159306
|
+
startInFlight;
|
|
159307
|
+
stopInFlight;
|
|
159308
|
+
// Tracks the last synced state JSON per entity to avoid pushing unchanged states.
|
|
159309
|
+
// Key: entity_id, Value: JSON.stringify of entity.state
|
|
159310
|
+
lastSyncedStates = /* @__PURE__ */ new Map();
|
|
159311
|
+
get id() {
|
|
159312
|
+
return this.dataProvider.id;
|
|
159313
|
+
}
|
|
159314
|
+
get data() {
|
|
159315
|
+
return this.dataProvider.withMetadata(
|
|
159316
|
+
this.status,
|
|
159317
|
+
this.server,
|
|
159318
|
+
this.aggregator.parts.size,
|
|
159319
|
+
this.endpointManager.failedEntities,
|
|
159320
|
+
this.getExposedDeviceTypes()
|
|
159321
|
+
);
|
|
159322
|
+
}
|
|
159323
|
+
get aggregator() {
|
|
159324
|
+
return this.endpointManager.root;
|
|
159325
|
+
}
|
|
159326
|
+
/**
|
|
159327
|
+
* The entity id and numeric Matter device type of every exposed endpoint,
|
|
159328
|
+
* walking composed sub-endpoints. Used to warn when a device type is not
|
|
159329
|
+
* supported by a commissioned controller (#365 class).
|
|
159330
|
+
*/
|
|
159331
|
+
getExposedDeviceTypes() {
|
|
159332
|
+
const out = [];
|
|
159333
|
+
const collect = (ep, inheritedEntityId) => {
|
|
159334
|
+
const entityId = ep.entityId ?? inheritedEntityId;
|
|
159335
|
+
const deviceTypeId = ep.type?.deviceType;
|
|
159336
|
+
if (typeof deviceTypeId === "number" && entityId) {
|
|
159337
|
+
out.push({ entityId, deviceTypeId });
|
|
159338
|
+
}
|
|
159339
|
+
for (const child of ep.parts) {
|
|
159340
|
+
collect(child, entityId);
|
|
159341
|
+
}
|
|
159342
|
+
};
|
|
159343
|
+
for (const ep of this.aggregator.parts) {
|
|
159344
|
+
collect(ep);
|
|
159345
|
+
}
|
|
159346
|
+
return out;
|
|
159347
|
+
}
|
|
159348
|
+
getSessionInfo() {
|
|
159349
|
+
return this.sessions.getSessionInfo();
|
|
159350
|
+
}
|
|
159351
|
+
get pluginInfo() {
|
|
159352
|
+
return this.endpointManager.getPluginInfo();
|
|
159353
|
+
}
|
|
159354
|
+
async enablePlugin(pluginName) {
|
|
159355
|
+
return await this.endpointManager.enablePlugin(pluginName);
|
|
159356
|
+
}
|
|
159357
|
+
async disablePlugin(pluginName) {
|
|
159358
|
+
return await this.endpointManager.disablePlugin(pluginName);
|
|
159359
|
+
}
|
|
159360
|
+
resetPlugin(pluginName) {
|
|
159361
|
+
this.endpointManager.resetPlugin(pluginName);
|
|
159362
|
+
}
|
|
159363
|
+
getPluginConfigSchema(pluginName) {
|
|
159364
|
+
return this.endpointManager.getPluginConfigSchema(pluginName);
|
|
159365
|
+
}
|
|
159366
|
+
async updatePluginConfig(pluginName, config8) {
|
|
159367
|
+
const result = await this.endpointManager.updatePluginConfig(
|
|
159368
|
+
pluginName,
|
|
159369
|
+
config8
|
|
159370
|
+
);
|
|
159371
|
+
if (result && pluginName === "camera") {
|
|
159372
|
+
await this.applyCameraTcp(config8);
|
|
159373
|
+
}
|
|
159374
|
+
return result;
|
|
159375
|
+
}
|
|
159376
|
+
// #419: cameras need Matter over TCP because the WebRTC offer exceeds the UDP
|
|
159377
|
+
// message size. Match the bridge listener to the saved camera list. Changing
|
|
159378
|
+
// network config takes effect on (re)start, so bounce the bridge if running.
|
|
159379
|
+
async applyCameraTcp(config8) {
|
|
159380
|
+
const want = parseCameraList(config8.cameras).length > 0 || !!this.dataProvider.featureFlags?.enableMatterTcp;
|
|
159381
|
+
const have = !!this.server.state.network.tcp;
|
|
159382
|
+
if (want === have) {
|
|
159383
|
+
return;
|
|
159384
|
+
}
|
|
159385
|
+
this.log.info(
|
|
159386
|
+
want ? "cameras configured, enabling matter over tcp (#419)" : "no cameras left, disabling matter over tcp (#419)"
|
|
159387
|
+
);
|
|
159388
|
+
const running = this.status.code === BridgeStatus.Running;
|
|
159389
|
+
if (running) {
|
|
159390
|
+
await this.stop();
|
|
159391
|
+
}
|
|
159392
|
+
try {
|
|
159393
|
+
await this.server.set({
|
|
159394
|
+
network: { tcp: want ? CAMERA_TCP_CONFIG : void 0 }
|
|
159395
|
+
});
|
|
159396
|
+
} catch (e) {
|
|
159397
|
+
this.log.warn(
|
|
159398
|
+
"Could not apply tcp live, restart matterhub to apply it:",
|
|
159399
|
+
e
|
|
159400
|
+
);
|
|
159401
|
+
}
|
|
159402
|
+
if (running) {
|
|
159403
|
+
await this.start();
|
|
159404
|
+
}
|
|
159405
|
+
}
|
|
159406
|
+
async initialize() {
|
|
159407
|
+
await this.server.construction.ready.then();
|
|
159408
|
+
await this.refreshDevices();
|
|
159409
|
+
}
|
|
159410
|
+
async dispose() {
|
|
159411
|
+
await this.stop();
|
|
159412
|
+
}
|
|
159413
|
+
async refreshDevices() {
|
|
159414
|
+
await this.endpointManager.refreshDevices();
|
|
159415
|
+
const currentEntityIds = new Set(
|
|
159416
|
+
[...this.aggregator.parts].map(
|
|
159417
|
+
(p) => p.entityId
|
|
159418
|
+
)
|
|
159419
|
+
);
|
|
159420
|
+
for (const entityId of this.lastSyncedStates.keys()) {
|
|
159421
|
+
if (!currentEntityIds.has(entityId)) {
|
|
159422
|
+
this.lastSyncedStates.delete(entityId);
|
|
159423
|
+
}
|
|
159424
|
+
}
|
|
159425
|
+
}
|
|
159426
|
+
setStatus(status3) {
|
|
159427
|
+
this.status = status3;
|
|
159428
|
+
this.onStatusChange?.();
|
|
159429
|
+
}
|
|
159430
|
+
async start() {
|
|
159431
|
+
if (this.status.code === BridgeStatus.Running) {
|
|
159432
|
+
return;
|
|
159433
|
+
}
|
|
159434
|
+
if (this.startInFlight) {
|
|
159435
|
+
return this.startInFlight;
|
|
159436
|
+
}
|
|
159437
|
+
this.startInFlight = this.runStart().finally(() => {
|
|
159438
|
+
this.startInFlight = void 0;
|
|
159439
|
+
});
|
|
159440
|
+
return this.startInFlight;
|
|
159441
|
+
}
|
|
159442
|
+
async runStart() {
|
|
159443
|
+
this.lastSyncedStates.clear();
|
|
159444
|
+
try {
|
|
159445
|
+
this.setStatus({
|
|
159446
|
+
code: BridgeStatus.Starting,
|
|
159447
|
+
reason: "The bridge is starting... Please wait."
|
|
159448
|
+
});
|
|
159449
|
+
await this.refreshDevices();
|
|
159450
|
+
logMemoryUsage(
|
|
159451
|
+
this.log,
|
|
159452
|
+
`after refreshDevices (${this.aggregator.parts.size} endpoints)`
|
|
159453
|
+
);
|
|
159454
|
+
this.endpointManager.startObserving();
|
|
159455
|
+
ensureCommissioningConfig(this.server);
|
|
159456
|
+
applyLegacySpecSessionParameters(
|
|
159457
|
+
this.server.env.get(SessionManager),
|
|
159458
|
+
this.dataProvider.featureFlags
|
|
159459
|
+
);
|
|
159460
|
+
await this.server.setStateOf(
|
|
159461
|
+
BasicInformationServer,
|
|
159462
|
+
specVersionValues(this.dataProvider.featureFlags)
|
|
159463
|
+
);
|
|
159464
|
+
await applyTcpFlagBeforeStart(
|
|
159465
|
+
this.server,
|
|
159466
|
+
this.dataProvider.featureFlags
|
|
159467
|
+
);
|
|
159468
|
+
await this.server.start();
|
|
159469
|
+
await this.endpointManager.startPlugins();
|
|
159470
|
+
this.setStatus({ code: BridgeStatus.Running });
|
|
159471
|
+
this.startAutoForceSyncIfEnabled();
|
|
159472
|
+
if (this.dataProvider.featureFlags?.autoForceSync) {
|
|
159473
|
+
this.forceSync().catch((e) => {
|
|
159474
|
+
this.log.warn("Startup force sync failed:", e);
|
|
159475
|
+
});
|
|
159476
|
+
}
|
|
159477
|
+
this.sessions.start();
|
|
159478
|
+
logMemoryUsage(this.log, "bridge running");
|
|
159479
|
+
diagnosticEventBus.emit("bridge_started", `Bridge started`, {
|
|
159480
|
+
bridgeId: this.id,
|
|
159481
|
+
bridgeName: this.dataProvider.name,
|
|
159482
|
+
details: { deviceCount: this.aggregator.parts.size }
|
|
159483
|
+
});
|
|
159484
|
+
} catch (e) {
|
|
159485
|
+
const reason = "Failed to start bridge due to error:";
|
|
159486
|
+
this.log.error(reason, e);
|
|
159487
|
+
await this.stop(BridgeStatus.Failed, `${reason}
|
|
159488
|
+
${e?.toString()}`);
|
|
159489
|
+
}
|
|
159490
|
+
}
|
|
159491
|
+
async stop(code = BridgeStatus.Stopped, reason = "Manually stopped") {
|
|
159492
|
+
if (this.stopInFlight) {
|
|
159493
|
+
return this.stopInFlight;
|
|
159494
|
+
}
|
|
159495
|
+
this.stopInFlight = this.runStop(code, reason).finally(() => {
|
|
159496
|
+
this.stopInFlight = void 0;
|
|
159497
|
+
});
|
|
159498
|
+
return this.stopInFlight;
|
|
159499
|
+
}
|
|
159500
|
+
async runStop(code, reason) {
|
|
159501
|
+
this.sessions.stop();
|
|
159502
|
+
this.stopAutoForceSync();
|
|
159503
|
+
await this.sessions.closeActiveSessions();
|
|
159504
|
+
await this.endpointManager.stopPlugins();
|
|
159505
|
+
this.endpointManager.stopObserving();
|
|
159506
|
+
try {
|
|
159507
|
+
await this.server.cancel();
|
|
159508
|
+
} catch (e) {
|
|
159509
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
159510
|
+
if (!errorMessage.includes("mutex-closed")) {
|
|
159511
|
+
this.log.warn("Error stopping bridge server:", e);
|
|
159512
|
+
}
|
|
159513
|
+
}
|
|
159514
|
+
this.setStatus({ code, reason });
|
|
159515
|
+
diagnosticEventBus.emit("bridge_stopped", `Bridge stopped: ${reason}`, {
|
|
159516
|
+
bridgeId: this.id,
|
|
159517
|
+
bridgeName: this.dataProvider.name
|
|
159518
|
+
});
|
|
159519
|
+
}
|
|
159520
|
+
startAutoForceSyncIfEnabled() {
|
|
159521
|
+
this.stopAutoForceSync();
|
|
159522
|
+
const forceSyncEnabled = this.dataProvider.featureFlags?.autoForceSync ?? false;
|
|
159523
|
+
if (!forceSyncEnabled) {
|
|
159524
|
+
return;
|
|
159525
|
+
}
|
|
159526
|
+
this.autoForceSyncTimer = setInterval(() => {
|
|
159527
|
+
this.forceSync().catch((e) => {
|
|
159528
|
+
this.log.warn("Auto force sync failed:", e);
|
|
159529
|
+
});
|
|
159530
|
+
}, AUTO_FORCE_SYNC_INTERVAL_MS);
|
|
159531
|
+
this.log.info(`Force sync: every ${AUTO_FORCE_SYNC_INTERVAL_MS / 1e3}s`);
|
|
159532
|
+
}
|
|
159533
|
+
stopAutoForceSync() {
|
|
159534
|
+
if (this.autoForceSyncTimer) {
|
|
159535
|
+
clearInterval(this.autoForceSyncTimer);
|
|
159536
|
+
this.autoForceSyncTimer = null;
|
|
159537
|
+
}
|
|
159538
|
+
}
|
|
159492
159539
|
async update(update) {
|
|
159493
159540
|
try {
|
|
159494
159541
|
this.dataProvider.update(update);
|
|
159495
159542
|
await this.refreshDevices();
|
|
159496
159543
|
if (this.status.code === BridgeStatus.Running) {
|
|
159497
159544
|
this.startAutoForceSyncIfEnabled();
|
|
159498
|
-
this.
|
|
159499
|
-
this.startWedgeWatchdog();
|
|
159545
|
+
this.sessions.reconfigure();
|
|
159500
159546
|
}
|
|
159501
159547
|
} catch (e) {
|
|
159502
159548
|
const reason = "Failed to update bridge due to error:";
|
|
@@ -162452,17 +162498,20 @@ var ThermostatServerBase = class extends FullFeaturedBase {
|
|
|
162452
162498
|
controlSequence = this.clampControlSequence(controlSequence);
|
|
162453
162499
|
this.internal.controlSequenceOfOperation = controlSequence;
|
|
162454
162500
|
applyPatchState(this.state, {
|
|
162501
|
+
// abs limits BEFORE user limits: on a widening update the user limit
|
|
162502
|
+
// must never transiently sit outside the still-narrow abs limit, the
|
|
162503
|
+
// reactor asserts against abs on every write (#454 hardening)
|
|
162455
162504
|
...this.features.heating ? {
|
|
162456
|
-
minHeatSetpointLimit: minHeatLimit,
|
|
162457
|
-
maxHeatSetpointLimit: maxHeatLimit,
|
|
162458
162505
|
absMinHeatSetpointLimit: minHeatLimit,
|
|
162459
|
-
absMaxHeatSetpointLimit: maxHeatLimit
|
|
162506
|
+
absMaxHeatSetpointLimit: maxHeatLimit,
|
|
162507
|
+
minHeatSetpointLimit: minHeatLimit,
|
|
162508
|
+
maxHeatSetpointLimit: maxHeatLimit
|
|
162460
162509
|
} : {},
|
|
162461
162510
|
...this.features.cooling ? {
|
|
162462
|
-
minCoolSetpointLimit: minCoolLimit,
|
|
162463
|
-
maxCoolSetpointLimit: maxCoolLimit,
|
|
162464
162511
|
absMinCoolSetpointLimit: minCoolLimit,
|
|
162465
|
-
absMaxCoolSetpointLimit: maxCoolLimit
|
|
162512
|
+
absMaxCoolSetpointLimit: maxCoolLimit,
|
|
162513
|
+
minCoolSetpointLimit: minCoolLimit,
|
|
162514
|
+
maxCoolSetpointLimit: maxCoolLimit
|
|
162466
162515
|
} : {},
|
|
162467
162516
|
localTemperature,
|
|
162468
162517
|
controlSequenceOfOperation: controlSequence,
|
|
@@ -167470,14 +167519,12 @@ var DimmableLightWithBatteryType = DimmableLightDevice.with(
|
|
|
167470
167519
|
|
|
167471
167520
|
// src/matter/endpoints/legacy/light/devices/extended-color-light.ts
|
|
167472
167521
|
init_home_assistant_entity_behavior();
|
|
167473
|
-
var ExtendedColorLightType = (supportsColorControl,
|
|
167522
|
+
var ExtendedColorLightType = (supportsColorControl, _supportsTemperature, hasBattery = false) => {
|
|
167474
167523
|
const features = /* @__PURE__ */ new Set();
|
|
167524
|
+
features.add("Xy");
|
|
167525
|
+
features.add("ColorTemperature");
|
|
167475
167526
|
if (supportsColorControl) {
|
|
167476
167527
|
features.add("HueSaturation");
|
|
167477
|
-
features.add("Xy");
|
|
167478
|
-
}
|
|
167479
|
-
if (supportsTemperature) {
|
|
167480
|
-
features.add("ColorTemperature");
|
|
167481
167528
|
}
|
|
167482
167529
|
if (hasBattery) {
|
|
167483
167530
|
return ExtendedColorLightDevice.with(
|
|
@@ -169352,6 +169399,99 @@ var CarbonMonoxideSensorType = AirQualitySensorDevice.with(
|
|
|
169352
169399
|
CarbonMonoxideConcentrationMeasurementServer2
|
|
169353
169400
|
);
|
|
169354
169401
|
|
|
169402
|
+
// src/matter/behaviors/carbon-dioxide-concentration-measurement-server.ts
|
|
169403
|
+
init_home_assistant_entity_behavior();
|
|
169404
|
+
var CarbonDioxideConcentrationMeasurementServerBase = CarbonDioxideConcentrationMeasurementServer.with(
|
|
169405
|
+
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
169406
|
+
);
|
|
169407
|
+
var CarbonDioxideConcentrationMeasurementServer2 = class extends CarbonDioxideConcentrationMeasurementServerBase {
|
|
169408
|
+
async initialize() {
|
|
169409
|
+
if (this.state.measuredValue === void 0) {
|
|
169410
|
+
this.state.measuredValue = null;
|
|
169411
|
+
}
|
|
169412
|
+
if (this.state.minMeasuredValue === void 0) {
|
|
169413
|
+
this.state.minMeasuredValue = null;
|
|
169414
|
+
}
|
|
169415
|
+
if (this.state.maxMeasuredValue === void 0) {
|
|
169416
|
+
this.state.maxMeasuredValue = null;
|
|
169417
|
+
}
|
|
169418
|
+
if (this.state.uncertainty === void 0) {
|
|
169419
|
+
this.state.uncertainty = 0;
|
|
169420
|
+
}
|
|
169421
|
+
if (this.state.measurementUnit === void 0) {
|
|
169422
|
+
this.state.measurementUnit = ConcentrationMeasurement3.MeasurementUnit.Ppm;
|
|
169423
|
+
}
|
|
169424
|
+
if (this.state.measurementMedium === void 0) {
|
|
169425
|
+
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
169426
|
+
}
|
|
169427
|
+
await super.initialize();
|
|
169428
|
+
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
169429
|
+
this.update(homeAssistant.entity);
|
|
169430
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
169431
|
+
}
|
|
169432
|
+
update(entity) {
|
|
169433
|
+
if (!entity.state || !entity.state.attributes) {
|
|
169434
|
+
return;
|
|
169435
|
+
}
|
|
169436
|
+
const state = entity.state.state;
|
|
169437
|
+
let measuredValue = null;
|
|
169438
|
+
if (state != null && !Number.isNaN(+state)) {
|
|
169439
|
+
measuredValue = +state;
|
|
169440
|
+
}
|
|
169441
|
+
applyPatchState(this.state, {
|
|
169442
|
+
measuredValue
|
|
169443
|
+
});
|
|
169444
|
+
}
|
|
169445
|
+
};
|
|
169446
|
+
|
|
169447
|
+
// src/matter/endpoints/legacy/sensor/devices/co2-sensor.ts
|
|
169448
|
+
init_home_assistant_entity_behavior();
|
|
169449
|
+
var Co2AirQualityServerBase = AirQualityServer.with(
|
|
169450
|
+
AirQuality3.Feature.Fair,
|
|
169451
|
+
AirQuality3.Feature.Moderate,
|
|
169452
|
+
AirQuality3.Feature.VeryPoor,
|
|
169453
|
+
AirQuality3.Feature.ExtremelyPoor
|
|
169454
|
+
);
|
|
169455
|
+
var Co2AirQualityServer = class extends Co2AirQualityServerBase {
|
|
169456
|
+
async initialize() {
|
|
169457
|
+
if (this.state.airQuality === void 0) {
|
|
169458
|
+
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
169459
|
+
}
|
|
169460
|
+
await super.initialize();
|
|
169461
|
+
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
169462
|
+
this.update(homeAssistant.entity);
|
|
169463
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
169464
|
+
}
|
|
169465
|
+
update(entity) {
|
|
169466
|
+
const state = entity.state.state;
|
|
169467
|
+
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
169468
|
+
if (state != null && !Number.isNaN(+state)) {
|
|
169469
|
+
const value = +state;
|
|
169470
|
+
if (value <= 800) {
|
|
169471
|
+
airQuality = AirQuality3.AirQualityEnum.Good;
|
|
169472
|
+
} else if (value <= 1e3) {
|
|
169473
|
+
airQuality = AirQuality3.AirQualityEnum.Fair;
|
|
169474
|
+
} else if (value <= 1400) {
|
|
169475
|
+
airQuality = AirQuality3.AirQualityEnum.Moderate;
|
|
169476
|
+
} else if (value <= 2e3) {
|
|
169477
|
+
airQuality = AirQuality3.AirQualityEnum.Poor;
|
|
169478
|
+
} else if (value <= 5e3) {
|
|
169479
|
+
airQuality = AirQuality3.AirQualityEnum.VeryPoor;
|
|
169480
|
+
} else {
|
|
169481
|
+
airQuality = AirQuality3.AirQualityEnum.ExtremelyPoor;
|
|
169482
|
+
}
|
|
169483
|
+
}
|
|
169484
|
+
applyPatchState(this.state, { airQuality });
|
|
169485
|
+
}
|
|
169486
|
+
};
|
|
169487
|
+
var Co2SensorType = AirQualitySensorDevice.with(
|
|
169488
|
+
BasicInformationServer2,
|
|
169489
|
+
IdentifyServer2,
|
|
169490
|
+
HomeAssistantEntityBehavior,
|
|
169491
|
+
Co2AirQualityServer,
|
|
169492
|
+
CarbonDioxideConcentrationMeasurementServer2
|
|
169493
|
+
);
|
|
169494
|
+
|
|
169355
169495
|
// src/matter/endpoints/legacy/sensor/devices/electrical-meter.ts
|
|
169356
169496
|
init_home_assistant_entity_behavior();
|
|
169357
169497
|
|
|
@@ -170371,6 +170511,208 @@ var Pm1SensorType = AirQualitySensorDevice.with(
|
|
|
170371
170511
|
Pm1ConcentrationMeasurementServer2
|
|
170372
170512
|
);
|
|
170373
170513
|
|
|
170514
|
+
// src/matter/endpoints/legacy/sensor/devices/pm10-sensor.ts
|
|
170515
|
+
init_home_assistant_entity_behavior();
|
|
170516
|
+
|
|
170517
|
+
// src/matter/behaviors/pm10-concentration-measurement-server.ts
|
|
170518
|
+
init_home_assistant_entity_behavior();
|
|
170519
|
+
var Pm10ConcentrationMeasurementServerBase = Pm10ConcentrationMeasurementServer.with(
|
|
170520
|
+
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
170521
|
+
);
|
|
170522
|
+
var Pm10ConcentrationMeasurementServer2 = class extends Pm10ConcentrationMeasurementServerBase {
|
|
170523
|
+
async initialize() {
|
|
170524
|
+
if (this.state.measuredValue === void 0) {
|
|
170525
|
+
this.state.measuredValue = null;
|
|
170526
|
+
}
|
|
170527
|
+
if (this.state.minMeasuredValue === void 0) {
|
|
170528
|
+
this.state.minMeasuredValue = null;
|
|
170529
|
+
}
|
|
170530
|
+
if (this.state.maxMeasuredValue === void 0) {
|
|
170531
|
+
this.state.maxMeasuredValue = null;
|
|
170532
|
+
}
|
|
170533
|
+
if (this.state.uncertainty === void 0) {
|
|
170534
|
+
this.state.uncertainty = 0;
|
|
170535
|
+
}
|
|
170536
|
+
if (this.state.measurementUnit === void 0) {
|
|
170537
|
+
this.state.measurementUnit = ConcentrationMeasurement3.MeasurementUnit.Ugm3;
|
|
170538
|
+
}
|
|
170539
|
+
if (this.state.measurementMedium === void 0) {
|
|
170540
|
+
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
170541
|
+
}
|
|
170542
|
+
await super.initialize();
|
|
170543
|
+
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170544
|
+
this.update(homeAssistant.entity);
|
|
170545
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170546
|
+
}
|
|
170547
|
+
update(entity) {
|
|
170548
|
+
if (!entity.state || !entity.state.attributes) {
|
|
170549
|
+
return;
|
|
170550
|
+
}
|
|
170551
|
+
const state = entity.state.state;
|
|
170552
|
+
let measuredValue = null;
|
|
170553
|
+
if (state != null && !Number.isNaN(+state)) {
|
|
170554
|
+
measuredValue = +state;
|
|
170555
|
+
}
|
|
170556
|
+
applyPatchState(this.state, {
|
|
170557
|
+
measuredValue
|
|
170558
|
+
});
|
|
170559
|
+
}
|
|
170560
|
+
};
|
|
170561
|
+
|
|
170562
|
+
// src/matter/endpoints/legacy/sensor/devices/pm10-sensor.ts
|
|
170563
|
+
var Pm10AirQualityServerBase = AirQualityServer.with(
|
|
170564
|
+
AirQuality3.Feature.Fair,
|
|
170565
|
+
AirQuality3.Feature.Moderate,
|
|
170566
|
+
AirQuality3.Feature.VeryPoor,
|
|
170567
|
+
AirQuality3.Feature.ExtremelyPoor
|
|
170568
|
+
);
|
|
170569
|
+
var Pm10AirQualityServer = class extends Pm10AirQualityServerBase {
|
|
170570
|
+
async initialize() {
|
|
170571
|
+
if (this.state.airQuality === void 0) {
|
|
170572
|
+
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170573
|
+
}
|
|
170574
|
+
await super.initialize();
|
|
170575
|
+
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170576
|
+
this.update(homeAssistant.entity);
|
|
170577
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170578
|
+
}
|
|
170579
|
+
update(entity) {
|
|
170580
|
+
const state = entity.state.state;
|
|
170581
|
+
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170582
|
+
if (state != null && !Number.isNaN(+state)) {
|
|
170583
|
+
const value = +state;
|
|
170584
|
+
if (value <= 54) {
|
|
170585
|
+
airQuality = AirQuality3.AirQualityEnum.Good;
|
|
170586
|
+
} else if (value <= 154) {
|
|
170587
|
+
airQuality = AirQuality3.AirQualityEnum.Fair;
|
|
170588
|
+
} else if (value <= 254) {
|
|
170589
|
+
airQuality = AirQuality3.AirQualityEnum.Moderate;
|
|
170590
|
+
} else if (value <= 354) {
|
|
170591
|
+
airQuality = AirQuality3.AirQualityEnum.Poor;
|
|
170592
|
+
} else if (value <= 424) {
|
|
170593
|
+
airQuality = AirQuality3.AirQualityEnum.VeryPoor;
|
|
170594
|
+
} else {
|
|
170595
|
+
airQuality = AirQuality3.AirQualityEnum.ExtremelyPoor;
|
|
170596
|
+
}
|
|
170597
|
+
}
|
|
170598
|
+
applyPatchState(this.state, { airQuality });
|
|
170599
|
+
}
|
|
170600
|
+
};
|
|
170601
|
+
var Pm10SensorType = AirQualitySensorDevice.with(
|
|
170602
|
+
BasicInformationServer2,
|
|
170603
|
+
IdentifyServer2,
|
|
170604
|
+
HomeAssistantEntityBehavior,
|
|
170605
|
+
Pm10AirQualityServer,
|
|
170606
|
+
Pm10ConcentrationMeasurementServer2
|
|
170607
|
+
);
|
|
170608
|
+
|
|
170609
|
+
// src/matter/endpoints/legacy/sensor/devices/pm25-sensor.ts
|
|
170610
|
+
init_esm();
|
|
170611
|
+
init_home_assistant_entity_behavior();
|
|
170612
|
+
|
|
170613
|
+
// src/matter/behaviors/pm25-concentration-measurement-server.ts
|
|
170614
|
+
init_esm();
|
|
170615
|
+
init_home_assistant_entity_behavior();
|
|
170616
|
+
var logger232 = Logger.get("Pm25ConcentrationMeasurementServer");
|
|
170617
|
+
var Pm25ConcentrationMeasurementServerBase = Pm25ConcentrationMeasurementServer.with(
|
|
170618
|
+
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
170619
|
+
);
|
|
170620
|
+
var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasurementServerBase {
|
|
170621
|
+
async initialize() {
|
|
170622
|
+
if (this.state.measuredValue === void 0) {
|
|
170623
|
+
this.state.measuredValue = null;
|
|
170624
|
+
}
|
|
170625
|
+
if (this.state.minMeasuredValue === void 0) {
|
|
170626
|
+
this.state.minMeasuredValue = null;
|
|
170627
|
+
}
|
|
170628
|
+
if (this.state.maxMeasuredValue === void 0) {
|
|
170629
|
+
this.state.maxMeasuredValue = null;
|
|
170630
|
+
}
|
|
170631
|
+
if (this.state.uncertainty === void 0) {
|
|
170632
|
+
this.state.uncertainty = 0;
|
|
170633
|
+
}
|
|
170634
|
+
if (this.state.measurementUnit === void 0) {
|
|
170635
|
+
this.state.measurementUnit = ConcentrationMeasurement3.MeasurementUnit.Ugm3;
|
|
170636
|
+
}
|
|
170637
|
+
if (this.state.measurementMedium === void 0) {
|
|
170638
|
+
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
170639
|
+
}
|
|
170640
|
+
logger232.debug(
|
|
170641
|
+
"Pm25ConcentrationMeasurementServer: before super.initialize()"
|
|
170642
|
+
);
|
|
170643
|
+
await super.initialize();
|
|
170644
|
+
logger232.debug(
|
|
170645
|
+
"Pm25ConcentrationMeasurementServer: after super.initialize()"
|
|
170646
|
+
);
|
|
170647
|
+
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170648
|
+
this.update(homeAssistant.entity);
|
|
170649
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170650
|
+
}
|
|
170651
|
+
update(entity) {
|
|
170652
|
+
if (!entity.state || !entity.state.attributes) {
|
|
170653
|
+
return;
|
|
170654
|
+
}
|
|
170655
|
+
const state = entity.state.state;
|
|
170656
|
+
let measuredValue = null;
|
|
170657
|
+
if (state != null && !Number.isNaN(+state)) {
|
|
170658
|
+
measuredValue = +state;
|
|
170659
|
+
}
|
|
170660
|
+
applyPatchState(this.state, {
|
|
170661
|
+
measuredValue
|
|
170662
|
+
});
|
|
170663
|
+
}
|
|
170664
|
+
};
|
|
170665
|
+
|
|
170666
|
+
// src/matter/endpoints/legacy/sensor/devices/pm25-sensor.ts
|
|
170667
|
+
var logger233 = Logger.get("Pm25AirQualityServer");
|
|
170668
|
+
var Pm25AirQualityServerBase = AirQualityServer.with(
|
|
170669
|
+
AirQuality3.Feature.Fair,
|
|
170670
|
+
AirQuality3.Feature.Moderate,
|
|
170671
|
+
AirQuality3.Feature.VeryPoor,
|
|
170672
|
+
AirQuality3.Feature.ExtremelyPoor
|
|
170673
|
+
);
|
|
170674
|
+
var Pm25AirQualityServer = class extends Pm25AirQualityServerBase {
|
|
170675
|
+
async initialize() {
|
|
170676
|
+
if (this.state.airQuality === void 0) {
|
|
170677
|
+
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170678
|
+
}
|
|
170679
|
+
logger233.debug("Pm25AirQualityServer: before super.initialize()");
|
|
170680
|
+
await super.initialize();
|
|
170681
|
+
logger233.debug("Pm25AirQualityServer: after super.initialize()");
|
|
170682
|
+
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170683
|
+
this.update(homeAssistant.entity);
|
|
170684
|
+
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170685
|
+
}
|
|
170686
|
+
update(entity) {
|
|
170687
|
+
const state = entity.state.state;
|
|
170688
|
+
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170689
|
+
if (state != null && !Number.isNaN(+state)) {
|
|
170690
|
+
const value = +state;
|
|
170691
|
+
if (value <= 12) {
|
|
170692
|
+
airQuality = AirQuality3.AirQualityEnum.Good;
|
|
170693
|
+
} else if (value <= 35) {
|
|
170694
|
+
airQuality = AirQuality3.AirQualityEnum.Fair;
|
|
170695
|
+
} else if (value <= 55) {
|
|
170696
|
+
airQuality = AirQuality3.AirQualityEnum.Moderate;
|
|
170697
|
+
} else if (value <= 150) {
|
|
170698
|
+
airQuality = AirQuality3.AirQualityEnum.Poor;
|
|
170699
|
+
} else if (value <= 250) {
|
|
170700
|
+
airQuality = AirQuality3.AirQualityEnum.VeryPoor;
|
|
170701
|
+
} else {
|
|
170702
|
+
airQuality = AirQuality3.AirQualityEnum.ExtremelyPoor;
|
|
170703
|
+
}
|
|
170704
|
+
}
|
|
170705
|
+
applyPatchState(this.state, { airQuality });
|
|
170706
|
+
}
|
|
170707
|
+
};
|
|
170708
|
+
var Pm25SensorType = AirQualitySensorDevice.with(
|
|
170709
|
+
BasicInformationServer2,
|
|
170710
|
+
IdentifyServer2,
|
|
170711
|
+
HomeAssistantEntityBehavior,
|
|
170712
|
+
Pm25AirQualityServer,
|
|
170713
|
+
Pm25ConcentrationMeasurementServer2
|
|
170714
|
+
);
|
|
170715
|
+
|
|
170374
170716
|
// src/matter/endpoints/legacy/sensor/devices/pressure-sensor.ts
|
|
170375
170717
|
init_home_assistant_entity_behavior();
|
|
170376
170718
|
var pressureSensorConfig = {
|
|
@@ -170589,7 +170931,7 @@ var TvocConcentrationMeasurementServer = class extends TvocConcentrationMeasurem
|
|
|
170589
170931
|
};
|
|
170590
170932
|
|
|
170591
170933
|
// src/matter/endpoints/legacy/sensor/devices/tvoc-sensor.ts
|
|
170592
|
-
var
|
|
170934
|
+
var logger234 = Logger.get("TvocSensor");
|
|
170593
170935
|
function airQualityFromUgm3(value) {
|
|
170594
170936
|
if (value <= 300) return AirQuality3.AirQualityEnum.Good;
|
|
170595
170937
|
if (value <= 1e3) return AirQuality3.AirQualityEnum.Fair;
|
|
@@ -170630,17 +170972,17 @@ var TvocAirQualityServer = class extends TvocAirQualityServerBase {
|
|
|
170630
170972
|
const attributes9 = entity.state.attributes;
|
|
170631
170973
|
const deviceClass = attributes9.device_class;
|
|
170632
170974
|
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170633
|
-
|
|
170975
|
+
logger234.debug(
|
|
170634
170976
|
`[${entity.entity_id}] TVOC update: state="${state}", device_class="${deviceClass}"`
|
|
170635
170977
|
);
|
|
170636
170978
|
if (state != null && !Number.isNaN(+state)) {
|
|
170637
170979
|
const value = +state;
|
|
170638
170980
|
airQuality = deviceClass === SensorDeviceClass.volatile_organic_compounds ? airQualityFromUgm3(value) : airQualityFromPpb(value);
|
|
170639
|
-
|
|
170981
|
+
logger234.debug(
|
|
170640
170982
|
`[${entity.entity_id}] TVOC value=${value} (${deviceClass}) -> airQuality=${AirQuality3.AirQualityEnum[airQuality]}`
|
|
170641
170983
|
);
|
|
170642
170984
|
} else {
|
|
170643
|
-
|
|
170985
|
+
logger234.warn(
|
|
170644
170986
|
`[${entity.entity_id}] TVOC state not a valid number: "${state}"`
|
|
170645
170987
|
);
|
|
170646
170988
|
}
|
|
@@ -170660,301 +171002,6 @@ init_dist();
|
|
|
170660
171002
|
init_types2();
|
|
170661
171003
|
init_diagnostic_event_bus();
|
|
170662
171004
|
|
|
170663
|
-
// src/matter/behaviors/carbon-dioxide-concentration-measurement-server.ts
|
|
170664
|
-
init_home_assistant_entity_behavior();
|
|
170665
|
-
var CarbonDioxideConcentrationMeasurementServerBase = CarbonDioxideConcentrationMeasurementServer.with(
|
|
170666
|
-
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
170667
|
-
);
|
|
170668
|
-
var CarbonDioxideConcentrationMeasurementServer2 = class extends CarbonDioxideConcentrationMeasurementServerBase {
|
|
170669
|
-
async initialize() {
|
|
170670
|
-
if (this.state.measuredValue === void 0) {
|
|
170671
|
-
this.state.measuredValue = null;
|
|
170672
|
-
}
|
|
170673
|
-
if (this.state.minMeasuredValue === void 0) {
|
|
170674
|
-
this.state.minMeasuredValue = null;
|
|
170675
|
-
}
|
|
170676
|
-
if (this.state.maxMeasuredValue === void 0) {
|
|
170677
|
-
this.state.maxMeasuredValue = null;
|
|
170678
|
-
}
|
|
170679
|
-
if (this.state.uncertainty === void 0) {
|
|
170680
|
-
this.state.uncertainty = 0;
|
|
170681
|
-
}
|
|
170682
|
-
if (this.state.measurementUnit === void 0) {
|
|
170683
|
-
this.state.measurementUnit = ConcentrationMeasurement3.MeasurementUnit.Ppm;
|
|
170684
|
-
}
|
|
170685
|
-
if (this.state.measurementMedium === void 0) {
|
|
170686
|
-
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
170687
|
-
}
|
|
170688
|
-
await super.initialize();
|
|
170689
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170690
|
-
this.update(homeAssistant.entity);
|
|
170691
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170692
|
-
}
|
|
170693
|
-
update(entity) {
|
|
170694
|
-
if (!entity.state || !entity.state.attributes) {
|
|
170695
|
-
return;
|
|
170696
|
-
}
|
|
170697
|
-
const state = entity.state.state;
|
|
170698
|
-
let measuredValue = null;
|
|
170699
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170700
|
-
measuredValue = +state;
|
|
170701
|
-
}
|
|
170702
|
-
applyPatchState(this.state, {
|
|
170703
|
-
measuredValue
|
|
170704
|
-
});
|
|
170705
|
-
}
|
|
170706
|
-
};
|
|
170707
|
-
|
|
170708
|
-
// src/matter/endpoints/legacy/sensor/devices/co2-sensor.ts
|
|
170709
|
-
init_home_assistant_entity_behavior();
|
|
170710
|
-
var Co2AirQualityServerBase = AirQualityServer.with(
|
|
170711
|
-
AirQuality3.Feature.Fair,
|
|
170712
|
-
AirQuality3.Feature.Moderate,
|
|
170713
|
-
AirQuality3.Feature.VeryPoor,
|
|
170714
|
-
AirQuality3.Feature.ExtremelyPoor
|
|
170715
|
-
);
|
|
170716
|
-
var Co2AirQualityServer = class extends Co2AirQualityServerBase {
|
|
170717
|
-
async initialize() {
|
|
170718
|
-
if (this.state.airQuality === void 0) {
|
|
170719
|
-
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170720
|
-
}
|
|
170721
|
-
await super.initialize();
|
|
170722
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170723
|
-
this.update(homeAssistant.entity);
|
|
170724
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170725
|
-
}
|
|
170726
|
-
update(entity) {
|
|
170727
|
-
const state = entity.state.state;
|
|
170728
|
-
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170729
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170730
|
-
const value = +state;
|
|
170731
|
-
if (value <= 800) {
|
|
170732
|
-
airQuality = AirQuality3.AirQualityEnum.Good;
|
|
170733
|
-
} else if (value <= 1e3) {
|
|
170734
|
-
airQuality = AirQuality3.AirQualityEnum.Fair;
|
|
170735
|
-
} else if (value <= 1400) {
|
|
170736
|
-
airQuality = AirQuality3.AirQualityEnum.Moderate;
|
|
170737
|
-
} else if (value <= 2e3) {
|
|
170738
|
-
airQuality = AirQuality3.AirQualityEnum.Poor;
|
|
170739
|
-
} else if (value <= 5e3) {
|
|
170740
|
-
airQuality = AirQuality3.AirQualityEnum.VeryPoor;
|
|
170741
|
-
} else {
|
|
170742
|
-
airQuality = AirQuality3.AirQualityEnum.ExtremelyPoor;
|
|
170743
|
-
}
|
|
170744
|
-
}
|
|
170745
|
-
applyPatchState(this.state, { airQuality });
|
|
170746
|
-
}
|
|
170747
|
-
};
|
|
170748
|
-
var Co2SensorType = AirQualitySensorDevice.with(
|
|
170749
|
-
BasicInformationServer2,
|
|
170750
|
-
IdentifyServer2,
|
|
170751
|
-
HomeAssistantEntityBehavior,
|
|
170752
|
-
Co2AirQualityServer,
|
|
170753
|
-
CarbonDioxideConcentrationMeasurementServer2
|
|
170754
|
-
);
|
|
170755
|
-
|
|
170756
|
-
// src/matter/endpoints/legacy/sensor/devices/pm10-sensor.ts
|
|
170757
|
-
init_home_assistant_entity_behavior();
|
|
170758
|
-
|
|
170759
|
-
// src/matter/behaviors/pm10-concentration-measurement-server.ts
|
|
170760
|
-
init_home_assistant_entity_behavior();
|
|
170761
|
-
var Pm10ConcentrationMeasurementServerBase = Pm10ConcentrationMeasurementServer.with(
|
|
170762
|
-
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
170763
|
-
);
|
|
170764
|
-
var Pm10ConcentrationMeasurementServer2 = class extends Pm10ConcentrationMeasurementServerBase {
|
|
170765
|
-
async initialize() {
|
|
170766
|
-
if (this.state.measuredValue === void 0) {
|
|
170767
|
-
this.state.measuredValue = null;
|
|
170768
|
-
}
|
|
170769
|
-
if (this.state.minMeasuredValue === void 0) {
|
|
170770
|
-
this.state.minMeasuredValue = null;
|
|
170771
|
-
}
|
|
170772
|
-
if (this.state.maxMeasuredValue === void 0) {
|
|
170773
|
-
this.state.maxMeasuredValue = null;
|
|
170774
|
-
}
|
|
170775
|
-
if (this.state.uncertainty === void 0) {
|
|
170776
|
-
this.state.uncertainty = 0;
|
|
170777
|
-
}
|
|
170778
|
-
if (this.state.measurementUnit === void 0) {
|
|
170779
|
-
this.state.measurementUnit = ConcentrationMeasurement3.MeasurementUnit.Ugm3;
|
|
170780
|
-
}
|
|
170781
|
-
if (this.state.measurementMedium === void 0) {
|
|
170782
|
-
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
170783
|
-
}
|
|
170784
|
-
await super.initialize();
|
|
170785
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170786
|
-
this.update(homeAssistant.entity);
|
|
170787
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170788
|
-
}
|
|
170789
|
-
update(entity) {
|
|
170790
|
-
if (!entity.state || !entity.state.attributes) {
|
|
170791
|
-
return;
|
|
170792
|
-
}
|
|
170793
|
-
const state = entity.state.state;
|
|
170794
|
-
let measuredValue = null;
|
|
170795
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170796
|
-
measuredValue = +state;
|
|
170797
|
-
}
|
|
170798
|
-
applyPatchState(this.state, {
|
|
170799
|
-
measuredValue
|
|
170800
|
-
});
|
|
170801
|
-
}
|
|
170802
|
-
};
|
|
170803
|
-
|
|
170804
|
-
// src/matter/endpoints/legacy/sensor/devices/pm10-sensor.ts
|
|
170805
|
-
var Pm10AirQualityServerBase = AirQualityServer.with(
|
|
170806
|
-
AirQuality3.Feature.Fair,
|
|
170807
|
-
AirQuality3.Feature.Moderate,
|
|
170808
|
-
AirQuality3.Feature.VeryPoor,
|
|
170809
|
-
AirQuality3.Feature.ExtremelyPoor
|
|
170810
|
-
);
|
|
170811
|
-
var Pm10AirQualityServer = class extends Pm10AirQualityServerBase {
|
|
170812
|
-
async initialize() {
|
|
170813
|
-
if (this.state.airQuality === void 0) {
|
|
170814
|
-
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170815
|
-
}
|
|
170816
|
-
await super.initialize();
|
|
170817
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170818
|
-
this.update(homeAssistant.entity);
|
|
170819
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170820
|
-
}
|
|
170821
|
-
update(entity) {
|
|
170822
|
-
const state = entity.state.state;
|
|
170823
|
-
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170824
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170825
|
-
const value = +state;
|
|
170826
|
-
if (value <= 54) {
|
|
170827
|
-
airQuality = AirQuality3.AirQualityEnum.Good;
|
|
170828
|
-
} else if (value <= 154) {
|
|
170829
|
-
airQuality = AirQuality3.AirQualityEnum.Fair;
|
|
170830
|
-
} else if (value <= 254) {
|
|
170831
|
-
airQuality = AirQuality3.AirQualityEnum.Moderate;
|
|
170832
|
-
} else if (value <= 354) {
|
|
170833
|
-
airQuality = AirQuality3.AirQualityEnum.Poor;
|
|
170834
|
-
} else if (value <= 424) {
|
|
170835
|
-
airQuality = AirQuality3.AirQualityEnum.VeryPoor;
|
|
170836
|
-
} else {
|
|
170837
|
-
airQuality = AirQuality3.AirQualityEnum.ExtremelyPoor;
|
|
170838
|
-
}
|
|
170839
|
-
}
|
|
170840
|
-
applyPatchState(this.state, { airQuality });
|
|
170841
|
-
}
|
|
170842
|
-
};
|
|
170843
|
-
var Pm10SensorType = AirQualitySensorDevice.with(
|
|
170844
|
-
BasicInformationServer2,
|
|
170845
|
-
IdentifyServer2,
|
|
170846
|
-
HomeAssistantEntityBehavior,
|
|
170847
|
-
Pm10AirQualityServer,
|
|
170848
|
-
Pm10ConcentrationMeasurementServer2
|
|
170849
|
-
);
|
|
170850
|
-
|
|
170851
|
-
// src/matter/endpoints/legacy/sensor/devices/pm25-sensor.ts
|
|
170852
|
-
init_esm();
|
|
170853
|
-
init_home_assistant_entity_behavior();
|
|
170854
|
-
|
|
170855
|
-
// src/matter/behaviors/pm25-concentration-measurement-server.ts
|
|
170856
|
-
init_esm();
|
|
170857
|
-
init_home_assistant_entity_behavior();
|
|
170858
|
-
var logger233 = Logger.get("Pm25ConcentrationMeasurementServer");
|
|
170859
|
-
var Pm25ConcentrationMeasurementServerBase = Pm25ConcentrationMeasurementServer.with(
|
|
170860
|
-
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
170861
|
-
);
|
|
170862
|
-
var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasurementServerBase {
|
|
170863
|
-
async initialize() {
|
|
170864
|
-
if (this.state.measuredValue === void 0) {
|
|
170865
|
-
this.state.measuredValue = null;
|
|
170866
|
-
}
|
|
170867
|
-
if (this.state.minMeasuredValue === void 0) {
|
|
170868
|
-
this.state.minMeasuredValue = null;
|
|
170869
|
-
}
|
|
170870
|
-
if (this.state.maxMeasuredValue === void 0) {
|
|
170871
|
-
this.state.maxMeasuredValue = null;
|
|
170872
|
-
}
|
|
170873
|
-
if (this.state.uncertainty === void 0) {
|
|
170874
|
-
this.state.uncertainty = 0;
|
|
170875
|
-
}
|
|
170876
|
-
if (this.state.measurementUnit === void 0) {
|
|
170877
|
-
this.state.measurementUnit = ConcentrationMeasurement3.MeasurementUnit.Ugm3;
|
|
170878
|
-
}
|
|
170879
|
-
if (this.state.measurementMedium === void 0) {
|
|
170880
|
-
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
170881
|
-
}
|
|
170882
|
-
logger233.debug(
|
|
170883
|
-
"Pm25ConcentrationMeasurementServer: before super.initialize()"
|
|
170884
|
-
);
|
|
170885
|
-
await super.initialize();
|
|
170886
|
-
logger233.debug(
|
|
170887
|
-
"Pm25ConcentrationMeasurementServer: after super.initialize()"
|
|
170888
|
-
);
|
|
170889
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170890
|
-
this.update(homeAssistant.entity);
|
|
170891
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170892
|
-
}
|
|
170893
|
-
update(entity) {
|
|
170894
|
-
if (!entity.state || !entity.state.attributes) {
|
|
170895
|
-
return;
|
|
170896
|
-
}
|
|
170897
|
-
const state = entity.state.state;
|
|
170898
|
-
let measuredValue = null;
|
|
170899
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170900
|
-
measuredValue = +state;
|
|
170901
|
-
}
|
|
170902
|
-
applyPatchState(this.state, {
|
|
170903
|
-
measuredValue
|
|
170904
|
-
});
|
|
170905
|
-
}
|
|
170906
|
-
};
|
|
170907
|
-
|
|
170908
|
-
// src/matter/endpoints/legacy/sensor/devices/pm25-sensor.ts
|
|
170909
|
-
var logger234 = Logger.get("Pm25AirQualityServer");
|
|
170910
|
-
var Pm25AirQualityServerBase = AirQualityServer.with(
|
|
170911
|
-
AirQuality3.Feature.Fair,
|
|
170912
|
-
AirQuality3.Feature.Moderate,
|
|
170913
|
-
AirQuality3.Feature.VeryPoor,
|
|
170914
|
-
AirQuality3.Feature.ExtremelyPoor
|
|
170915
|
-
);
|
|
170916
|
-
var Pm25AirQualityServer = class extends Pm25AirQualityServerBase {
|
|
170917
|
-
async initialize() {
|
|
170918
|
-
if (this.state.airQuality === void 0) {
|
|
170919
|
-
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170920
|
-
}
|
|
170921
|
-
logger234.debug("Pm25AirQualityServer: before super.initialize()");
|
|
170922
|
-
await super.initialize();
|
|
170923
|
-
logger234.debug("Pm25AirQualityServer: after super.initialize()");
|
|
170924
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170925
|
-
this.update(homeAssistant.entity);
|
|
170926
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170927
|
-
}
|
|
170928
|
-
update(entity) {
|
|
170929
|
-
const state = entity.state.state;
|
|
170930
|
-
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170931
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170932
|
-
const value = +state;
|
|
170933
|
-
if (value <= 12) {
|
|
170934
|
-
airQuality = AirQuality3.AirQualityEnum.Good;
|
|
170935
|
-
} else if (value <= 35) {
|
|
170936
|
-
airQuality = AirQuality3.AirQualityEnum.Fair;
|
|
170937
|
-
} else if (value <= 55) {
|
|
170938
|
-
airQuality = AirQuality3.AirQualityEnum.Moderate;
|
|
170939
|
-
} else if (value <= 150) {
|
|
170940
|
-
airQuality = AirQuality3.AirQualityEnum.Poor;
|
|
170941
|
-
} else if (value <= 250) {
|
|
170942
|
-
airQuality = AirQuality3.AirQualityEnum.VeryPoor;
|
|
170943
|
-
} else {
|
|
170944
|
-
airQuality = AirQuality3.AirQualityEnum.ExtremelyPoor;
|
|
170945
|
-
}
|
|
170946
|
-
}
|
|
170947
|
-
applyPatchState(this.state, { airQuality });
|
|
170948
|
-
}
|
|
170949
|
-
};
|
|
170950
|
-
var Pm25SensorType = AirQualitySensorDevice.with(
|
|
170951
|
-
BasicInformationServer2,
|
|
170952
|
-
IdentifyServer2,
|
|
170953
|
-
HomeAssistantEntityBehavior,
|
|
170954
|
-
Pm25AirQualityServer,
|
|
170955
|
-
Pm25ConcentrationMeasurementServer2
|
|
170956
|
-
);
|
|
170957
|
-
|
|
170958
171005
|
// src/matter/endpoints/legacy/sensor/devices/temperature-humidity-sensor.ts
|
|
170959
171006
|
init_home_assistant_entity_behavior();
|
|
170960
171007
|
var temperatureConfig3 = {
|
|
@@ -174409,6 +174456,9 @@ var matterDeviceTypeFactories = {
|
|
|
174409
174456
|
formaldehyde_sensor: (ha) => FormaldehydeSensorType.set({ homeAssistantEntity: ha }),
|
|
174410
174457
|
radon_sensor: (ha) => RadonSensorType.set({ homeAssistantEntity: ha }),
|
|
174411
174458
|
pm1_sensor: (ha) => Pm1SensorType.set({ homeAssistantEntity: ha }),
|
|
174459
|
+
pm25_sensor: (ha) => Pm25SensorType.set({ homeAssistantEntity: ha }),
|
|
174460
|
+
pm10_sensor: (ha) => Pm10SensorType.set({ homeAssistantEntity: ha }),
|
|
174461
|
+
carbon_dioxide_sensor: (ha) => Co2SensorType.set({ homeAssistantEntity: ha }),
|
|
174412
174462
|
electrical_meter: (ha) => ElectricalMeterType.set({ homeAssistantEntity: ha }),
|
|
174413
174463
|
// Legacy SolarPower alias, kept for existing mappings.
|
|
174414
174464
|
electrical_sensor: (ha) => ElectricalSensorType.set({ homeAssistantEntity: ha }),
|
|
@@ -179289,20 +179339,9 @@ var BridgeEndpointManager = class extends Service {
|
|
|
179289
179339
|
|
|
179290
179340
|
// src/services/bridges/server-mode-bridge.ts
|
|
179291
179341
|
init_dist();
|
|
179292
|
-
init_esm7();
|
|
179293
|
-
import * as os10 from "node:os";
|
|
179294
179342
|
init_protocol3();
|
|
179295
179343
|
init_diagnostic_event_bus();
|
|
179296
|
-
var imWrapMarker2 = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
|
|
179297
|
-
var commandMessageTypes2 = [
|
|
179298
|
-
MessageType.ReadRequest,
|
|
179299
|
-
MessageType.WriteRequest,
|
|
179300
|
-
MessageType.InvokeRequest,
|
|
179301
|
-
MessageType.TimedRequest
|
|
179302
|
-
];
|
|
179303
179344
|
var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
|
|
179304
|
-
var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2 = 2500;
|
|
179305
|
-
var MDNS_ADDRESS_CHECK_INTERVAL_MS2 = 6e4;
|
|
179306
179345
|
function makeWarmStartState(state, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
179307
179346
|
return { ...state, last_updated: now };
|
|
179308
179347
|
}
|
|
@@ -179312,11 +179351,19 @@ var ServerModeBridge = class {
|
|
|
179312
179351
|
this.endpointManager = endpointManager;
|
|
179313
179352
|
this.server = server;
|
|
179314
179353
|
this.log = logger255.get(`ServerModeBridge / ${dataProvider.id}`);
|
|
179354
|
+
this.sessions = new SessionSupervisor(
|
|
179355
|
+
this.log,
|
|
179356
|
+
this.server,
|
|
179357
|
+
this.dataProvider,
|
|
179358
|
+
() => this.data,
|
|
179359
|
+
DEFAULT_SESSION_MAX_AGE_HOURS
|
|
179360
|
+
);
|
|
179315
179361
|
}
|
|
179316
179362
|
dataProvider;
|
|
179317
179363
|
endpointManager;
|
|
179318
179364
|
server;
|
|
179319
179365
|
log;
|
|
179366
|
+
sessions;
|
|
179320
179367
|
status = {
|
|
179321
179368
|
code: BridgeStatus.Stopped,
|
|
179322
179369
|
reason: void 0
|
|
@@ -179325,46 +179372,9 @@ var ServerModeBridge = class {
|
|
|
179325
179372
|
// broadcast updates via WebSocket so the frontend sees every transition.
|
|
179326
179373
|
onStatusChange;
|
|
179327
179374
|
autoForceSyncTimer = null;
|
|
179328
|
-
deadSessionTimer = null;
|
|
179329
|
-
staleSessionTimers = /* @__PURE__ */ new Map();
|
|
179330
179375
|
warmStartTimer = null;
|
|
179331
|
-
// Tracks when each session opened, used for age-based rotation (#287).
|
|
179332
|
-
sessionStartedAt = /* @__PURE__ */ new Map();
|
|
179333
|
-
rotationTimer = null;
|
|
179334
|
-
maxSessionAgeMs = 0;
|
|
179335
|
-
// Wedge watchdog: last inbound Interaction Model request time per session and
|
|
179336
|
-
// last time the watchdog rotated it, both keyed by the long-lived session
|
|
179337
|
-
// object so they clear when the session goes away.
|
|
179338
|
-
lastImRequestAt = /* @__PURE__ */ new WeakMap();
|
|
179339
|
-
wedgeLastRotatedAt = /* @__PURE__ */ new WeakMap();
|
|
179340
|
-
wedgeWatchdogTimer = null;
|
|
179341
|
-
// V2 shadow signals (#365): last command-class request per session, plus
|
|
179342
|
-
// rings of subscribe and delivery give-up times (newest WEDGE_RING_SIZE).
|
|
179343
|
-
lastCommandImAt = /* @__PURE__ */ new WeakMap();
|
|
179344
|
-
subscribeTimesMs = /* @__PURE__ */ new WeakMap();
|
|
179345
|
-
giveUpTimesMs = /* @__PURE__ */ new WeakMap();
|
|
179346
|
-
// Sessions whose subscriptions.deleted we already hooked for give-ups.
|
|
179347
|
-
wedgeHookedSessions = /* @__PURE__ */ new WeakSet();
|
|
179348
|
-
// Watches the advertised interface addresses so a dynamic ISP IPv6 prefix
|
|
179349
|
-
// change forces a fresh operational announcement (#415).
|
|
179350
|
-
mdnsAddressTimer = null;
|
|
179351
|
-
advertisedAddressSnapshot = [];
|
|
179352
|
-
// Skip a tick while the previous one is still awaiting refresh, so a slow
|
|
179353
|
-
// refreshOperationalAdvertisement can't overlap and re-announce off a stale
|
|
179354
|
-
// snapshot.
|
|
179355
|
-
mdnsCheckInFlight = false;
|
|
179356
179376
|
// Tracks the last synced state JSON per entity to avoid pushing unchanged states.
|
|
179357
179377
|
lastSyncedStates = /* @__PURE__ */ new Map();
|
|
179358
|
-
// Session lifecycle diagnostic handlers (non-destructive, logging only).
|
|
179359
|
-
// biome-ignore lint/suspicious/noExplicitAny: matter.js internal types
|
|
179360
|
-
sessionDiagHandler;
|
|
179361
|
-
// biome-ignore lint/suspicious/noExplicitAny: matter.js internal types
|
|
179362
|
-
sessionAddedHandler;
|
|
179363
|
-
// biome-ignore lint/suspicious/noExplicitAny: matter.js internal types
|
|
179364
|
-
sessionDeletedHandler;
|
|
179365
|
-
// Warns when Alexa pairs on a non-5540 port, the fabric rolls back later (#401).
|
|
179366
|
-
// biome-ignore lint/suspicious/noExplicitAny: matter.js internal types
|
|
179367
|
-
fabricAddedHandler;
|
|
179368
179378
|
get id() {
|
|
179369
179379
|
return this.dataProvider.id;
|
|
179370
179380
|
}
|
|
@@ -179400,81 +179410,7 @@ var ServerModeBridge = class {
|
|
|
179400
179410
|
return out;
|
|
179401
179411
|
}
|
|
179402
179412
|
getSessionInfo() {
|
|
179403
|
-
|
|
179404
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
179405
|
-
const sessions = [...sessionManager.sessions];
|
|
179406
|
-
let totalSubscriptions = 0;
|
|
179407
|
-
const fabricMap = /* @__PURE__ */ new Map();
|
|
179408
|
-
const sessionList = sessions.map((s) => {
|
|
179409
|
-
const subCount = s.subscriptions.size;
|
|
179410
|
-
totalSubscriptions += subCount;
|
|
179411
|
-
const subscriptions = summarizeSubscriptions(s.subscriptions);
|
|
179412
|
-
const fi = typeof s.fabric?.fabricIndex === "number" ? s.fabric.fabricIndex : null;
|
|
179413
|
-
if (fi !== null) {
|
|
179414
|
-
const existing = fabricMap.get(fi) ?? {
|
|
179415
|
-
sessions: 0,
|
|
179416
|
-
subscriptions: 0
|
|
179417
|
-
};
|
|
179418
|
-
existing.sessions++;
|
|
179419
|
-
existing.subscriptions += subCount;
|
|
179420
|
-
fabricMap.set(fi, existing);
|
|
179421
|
-
}
|
|
179422
|
-
const nowMs = Date.now();
|
|
179423
|
-
const lastActiveMsAgo = typeof s.activeTimestamp === "number" && s.activeTimestamp > 0 ? nowMs - s.activeTimestamp : null;
|
|
179424
|
-
const lastAnyActivityMsAgo = typeof s.timestamp === "number" ? nowMs - s.timestamp : null;
|
|
179425
|
-
const lastImAt = this.lastImRequestAt.get(s);
|
|
179426
|
-
const lastImRequestMsAgo = lastImAt != null ? nowMs - lastImAt : null;
|
|
179427
|
-
const startedAt = this.sessionStartedAt.get(s.id);
|
|
179428
|
-
const lastCmdAt = this.lastCommandImAt.get(s);
|
|
179429
|
-
const lastCommandImRequestMsAgo = lastCmdAt != null ? nowMs - lastCmdAt : null;
|
|
179430
|
-
const subscribeTimes = this.subscribeTimesMs.get(s) ?? [];
|
|
179431
|
-
const giveUpTimes = this.giveUpTimesMs.get(s) ?? [];
|
|
179432
|
-
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
179433
|
-
const wedgeV2WouldRotate = decideWedgeRotationV2({
|
|
179434
|
-
subscriptionCount: subCount,
|
|
179435
|
-
sessionAgeMs: startedAt != null ? nowMs - startedAt : 0,
|
|
179436
|
-
commandSilenceMs: lastCommandImRequestMsAgo,
|
|
179437
|
-
subscribeTimesMs: subscribeTimes,
|
|
179438
|
-
giveUpTimesMs: giveUpTimes,
|
|
179439
|
-
nowMs,
|
|
179440
|
-
lastRotatedMsAgo: lastRotatedAt != null ? nowMs - lastRotatedAt : null
|
|
179441
|
-
});
|
|
179442
|
-
return {
|
|
179443
|
-
id: s.id,
|
|
179444
|
-
peerNodeId: String(s.peerNodeId),
|
|
179445
|
-
fabricIndex: fi,
|
|
179446
|
-
subscriptionCount: subCount,
|
|
179447
|
-
subscriptions,
|
|
179448
|
-
lastActiveMsAgo,
|
|
179449
|
-
lastAnyActivityMsAgo,
|
|
179450
|
-
lastImRequestMsAgo,
|
|
179451
|
-
lastCommandImRequestMsAgo,
|
|
179452
|
-
subscribesLast30Min: countRecent(subscribeTimes, nowMs),
|
|
179453
|
-
giveUpsLast30Min: countRecent(giveUpTimes, nowMs),
|
|
179454
|
-
wedgeV2WouldRotate,
|
|
179455
|
-
isPeerActive: Boolean(s.isPeerActive),
|
|
179456
|
-
ageMsFromOpen: startedAt != null ? nowMs - startedAt : null
|
|
179457
|
-
};
|
|
179458
|
-
});
|
|
179459
|
-
const fabrics = [...fabricMap.entries()].map(([fabricIndex, data]) => ({
|
|
179460
|
-
fabricIndex,
|
|
179461
|
-
sessions: data.sessions,
|
|
179462
|
-
subscriptions: data.subscriptions
|
|
179463
|
-
}));
|
|
179464
|
-
return {
|
|
179465
|
-
sessions: sessionList,
|
|
179466
|
-
totalSessions: sessions.length,
|
|
179467
|
-
totalSubscriptions,
|
|
179468
|
-
fabrics
|
|
179469
|
-
};
|
|
179470
|
-
} catch {
|
|
179471
|
-
return {
|
|
179472
|
-
sessions: [],
|
|
179473
|
-
totalSessions: 0,
|
|
179474
|
-
totalSubscriptions: 0,
|
|
179475
|
-
fabrics: []
|
|
179476
|
-
};
|
|
179477
|
-
}
|
|
179413
|
+
return this.sessions.getSessionInfo();
|
|
179478
179414
|
}
|
|
179479
179415
|
async initialize() {
|
|
179480
179416
|
await this.server.construction.ready.then();
|
|
@@ -179524,11 +179460,7 @@ var ServerModeBridge = class {
|
|
|
179524
179460
|
this.log.warn("Startup force sync failed:", e);
|
|
179525
179461
|
});
|
|
179526
179462
|
}
|
|
179527
|
-
this.
|
|
179528
|
-
this.wireFabricWarnings();
|
|
179529
|
-
this.startSessionRotation();
|
|
179530
|
-
this.startWedgeWatchdog();
|
|
179531
|
-
this.startMdnsAddressWatch();
|
|
179463
|
+
this.sessions.start();
|
|
179532
179464
|
this.scheduleWarmStart();
|
|
179533
179465
|
logMemoryUsage(this.log, "server mode bridge running");
|
|
179534
179466
|
this.log.info("Server mode bridge started");
|
|
@@ -179544,14 +179476,10 @@ ${e?.toString()}`);
|
|
|
179544
179476
|
}
|
|
179545
179477
|
}
|
|
179546
179478
|
async stop(code = BridgeStatus.Stopped, reason = "Manually stopped") {
|
|
179547
|
-
this.
|
|
179548
|
-
this.stopWedgeWatchdog();
|
|
179549
|
-
this.stopMdnsAddressWatch();
|
|
179550
|
-
this.unwireSessionDiagnostics();
|
|
179551
|
-
this.unwireFabricWarnings();
|
|
179479
|
+
this.sessions.stop();
|
|
179552
179480
|
this.cancelWarmStart();
|
|
179553
179481
|
this.stopAutoForceSync();
|
|
179554
|
-
await this.closeActiveSessions();
|
|
179482
|
+
await this.sessions.closeActiveSessions();
|
|
179555
179483
|
this.endpointManager.stopObserving();
|
|
179556
179484
|
try {
|
|
179557
179485
|
await this.server.cancel();
|
|
@@ -179577,8 +179505,7 @@ ${e?.toString()}`);
|
|
|
179577
179505
|
await this.refreshDevices();
|
|
179578
179506
|
if (this.status.code === BridgeStatus.Running) {
|
|
179579
179507
|
this.startAutoForceSyncIfEnabled();
|
|
179580
|
-
this.
|
|
179581
|
-
this.startWedgeWatchdog();
|
|
179508
|
+
this.sessions.reconfigure();
|
|
179582
179509
|
}
|
|
179583
179510
|
} catch (e) {
|
|
179584
179511
|
const reason = "Failed to update server mode bridge due to error:";
|
|
@@ -179621,677 +179548,6 @@ ${e?.toString()}`);
|
|
|
179621
179548
|
}, AUTO_FORCE_SYNC_INTERVAL_MS2);
|
|
179622
179549
|
this.log.info(`Force sync: every ${AUTO_FORCE_SYNC_INTERVAL_MS2 / 1e3}s`);
|
|
179623
179550
|
}
|
|
179624
|
-
wireSessionDiagnostics() {
|
|
179625
|
-
this.unwireSessionDiagnostics();
|
|
179626
|
-
try {
|
|
179627
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
179628
|
-
this.sessionDiagHandler = (session) => {
|
|
179629
|
-
const sessions = [...sessionManager.sessions];
|
|
179630
|
-
let totalSubs = 0;
|
|
179631
|
-
for (const s of sessions) {
|
|
179632
|
-
totalSubs += s.subscriptions.size;
|
|
179633
|
-
}
|
|
179634
|
-
this.log.debug(
|
|
179635
|
-
`Session ${session.id} (peer ${session.peerNodeId}): subscriptions=${session.subscriptions.size} | total: sessions=${sessions.length} subscriptions=${totalSubs}`
|
|
179636
|
-
);
|
|
179637
|
-
diagnosticEventBus.emit(
|
|
179638
|
-
"subscription_changed",
|
|
179639
|
-
`Session ${session.id}: ${session.subscriptions.size} subs (total ${totalSubs})`,
|
|
179640
|
-
{
|
|
179641
|
-
bridgeId: this.data.id,
|
|
179642
|
-
bridgeName: this.data.name,
|
|
179643
|
-
details: {
|
|
179644
|
-
sessionId: session.id,
|
|
179645
|
-
sessionSubs: session.subscriptions.size,
|
|
179646
|
-
totalSessions: sessions.length,
|
|
179647
|
-
totalSubs
|
|
179648
|
-
}
|
|
179649
|
-
}
|
|
179650
|
-
);
|
|
179651
|
-
if (totalSubs === 0 && sessions.length > 0) {
|
|
179652
|
-
this.log.warn(
|
|
179653
|
-
`All subscriptions lost, ${sessions.length} session(s) still active, waiting for controller to re-subscribe`
|
|
179654
|
-
);
|
|
179655
|
-
if (!this.deadSessionTimer) {
|
|
179656
|
-
const timeoutMs = deadSessionTimeoutMs(
|
|
179657
|
-
this.dataProvider.featureFlags
|
|
179658
|
-
);
|
|
179659
|
-
this.deadSessionTimer = setTimeout(() => {
|
|
179660
|
-
this.deadSessionTimer = null;
|
|
179661
|
-
this.closeDeadSessions();
|
|
179662
|
-
}, timeoutMs);
|
|
179663
|
-
this.log.info(
|
|
179664
|
-
`Scheduled dead session cleanup in ${timeoutMs / 1e3}s`
|
|
179665
|
-
);
|
|
179666
|
-
}
|
|
179667
|
-
} else if (totalSubs > 0 && this.deadSessionTimer) {
|
|
179668
|
-
clearTimeout(this.deadSessionTimer);
|
|
179669
|
-
this.deadSessionTimer = null;
|
|
179670
|
-
this.log.info(
|
|
179671
|
-
"Subscriptions recovered, canceled dead session cleanup"
|
|
179672
|
-
);
|
|
179673
|
-
}
|
|
179674
|
-
if (session.subscriptions.size === 0 && !this.staleSessionTimers.has(session.id)) {
|
|
179675
|
-
this.staleSessionTimers.set(
|
|
179676
|
-
session.id,
|
|
179677
|
-
setTimeout(() => {
|
|
179678
|
-
this.staleSessionTimers.delete(session.id);
|
|
179679
|
-
this.closeStaleSession(session.id);
|
|
179680
|
-
}, deadSessionTimeoutMs(this.dataProvider.featureFlags))
|
|
179681
|
-
);
|
|
179682
|
-
} else if (session.subscriptions.size > 0 && this.staleSessionTimers.has(session.id)) {
|
|
179683
|
-
clearTimeout(this.staleSessionTimers.get(session.id));
|
|
179684
|
-
this.staleSessionTimers.delete(session.id);
|
|
179685
|
-
}
|
|
179686
|
-
};
|
|
179687
|
-
sessionManager.subscriptionsChanged.on(this.sessionDiagHandler);
|
|
179688
|
-
this.sessionAddedHandler = (newSession) => {
|
|
179689
|
-
this.sessionStartedAt.set(newSession.id, Date.now());
|
|
179690
|
-
hookGiveUpsWithPeers(newSession);
|
|
179691
|
-
this.log.info(
|
|
179692
|
-
`Session opened: id=${newSession.id} peer=${newSession.peerNodeId}`
|
|
179693
|
-
);
|
|
179694
|
-
diagnosticEventBus.emit(
|
|
179695
|
-
"session_opened",
|
|
179696
|
-
`Session ${newSession.id} opened (peer ${newSession.peerNodeId})`,
|
|
179697
|
-
{
|
|
179698
|
-
bridgeId: this.data.id,
|
|
179699
|
-
bridgeName: this.data.name,
|
|
179700
|
-
details: { sessionId: newSession.id }
|
|
179701
|
-
}
|
|
179702
|
-
);
|
|
179703
|
-
for (const s of [...sessionManager.sessions]) {
|
|
179704
|
-
if (s !== newSession && !s.isClosing && s.peerNodeId === newSession.peerNodeId && s.fabric?.fabricIndex === newSession.fabric?.fabricIndex && s.subscriptions.size === 0) {
|
|
179705
|
-
this.log.info(
|
|
179706
|
-
`Session ${s.id} (peer ${s.peerNodeId}, 0 subs) replaced by session ${newSession.id}, closing it once it goes quiet`
|
|
179707
|
-
);
|
|
179708
|
-
const staleId = s.id;
|
|
179709
|
-
const armed = this.staleSessionTimers.get(staleId);
|
|
179710
|
-
if (armed) clearTimeout(armed);
|
|
179711
|
-
this.staleSessionTimers.set(
|
|
179712
|
-
staleId,
|
|
179713
|
-
setTimeout(() => {
|
|
179714
|
-
this.staleSessionTimers.delete(staleId);
|
|
179715
|
-
this.closeStaleSession(staleId, PRIMING_GRACE_MS);
|
|
179716
|
-
}, replacedSessionTimeoutMs(this.dataProvider.featureFlags))
|
|
179717
|
-
);
|
|
179718
|
-
}
|
|
179719
|
-
}
|
|
179720
|
-
const quietWindowMs = staleSessionQuietWindowMs(
|
|
179721
|
-
this.dataProvider.featureFlags
|
|
179722
|
-
);
|
|
179723
|
-
const now = Date.now();
|
|
179724
|
-
const supersededIds = new Set(
|
|
179725
|
-
selectSupersededSessions(
|
|
179726
|
-
[...sessionManager.sessions],
|
|
179727
|
-
newSession,
|
|
179728
|
-
quietWindowMs,
|
|
179729
|
-
now
|
|
179730
|
-
)
|
|
179731
|
-
);
|
|
179732
|
-
const closes = [];
|
|
179733
|
-
for (const s of [...sessionManager.sessions]) {
|
|
179734
|
-
if (!supersededIds.has(s.id) || s.subscriptions.size === 0) continue;
|
|
179735
|
-
const quietSec = Math.round((now - s.timestamp) / 1e3);
|
|
179736
|
-
this.log.info(
|
|
179737
|
-
`Closing superseded session ${s.id} (peer ${s.peerNodeId}, ${s.subscriptions.size} subs, quiet ${quietSec}s), replaced by session ${newSession.id}`
|
|
179738
|
-
);
|
|
179739
|
-
closes.push(
|
|
179740
|
-
s.initiateClose().catch(
|
|
179741
|
-
() => s.initiateForceClose({
|
|
179742
|
-
cause: new Error("superseded session, forcing")
|
|
179743
|
-
})
|
|
179744
|
-
)
|
|
179745
|
-
);
|
|
179746
|
-
}
|
|
179747
|
-
if (closes.length > 0) {
|
|
179748
|
-
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
179749
|
-
}
|
|
179750
|
-
};
|
|
179751
|
-
this.sessionDeletedHandler = (session) => {
|
|
179752
|
-
this.sessionStartedAt.delete(session.id);
|
|
179753
|
-
const armed = this.staleSessionTimers.get(session.id);
|
|
179754
|
-
if (armed) {
|
|
179755
|
-
clearTimeout(armed);
|
|
179756
|
-
this.staleSessionTimers.delete(session.id);
|
|
179757
|
-
}
|
|
179758
|
-
const sessions = [...sessionManager.sessions];
|
|
179759
|
-
this.log.warn(
|
|
179760
|
-
`Session closed: id=${session.id} peer=${session.peerNodeId} | remaining sessions=${sessions.length}`
|
|
179761
|
-
);
|
|
179762
|
-
diagnosticEventBus.emit(
|
|
179763
|
-
"session_closed",
|
|
179764
|
-
`Session ${session.id} closed (peer ${session.peerNodeId})`,
|
|
179765
|
-
{
|
|
179766
|
-
bridgeId: this.data.id,
|
|
179767
|
-
bridgeName: this.data.name,
|
|
179768
|
-
details: {
|
|
179769
|
-
sessionId: session.id,
|
|
179770
|
-
remainingSessions: sessions.length
|
|
179771
|
-
}
|
|
179772
|
-
}
|
|
179773
|
-
);
|
|
179774
|
-
};
|
|
179775
|
-
sessionManager.sessions.added.on(this.sessionAddedHandler);
|
|
179776
|
-
sessionManager.sessions.deleted.on(this.sessionDeletedHandler);
|
|
179777
|
-
seedExistingSessionStarts(this.sessionStartedAt, sessionManager.sessions);
|
|
179778
|
-
const hookGiveUpsWithPeers = (sess) => this.hookSubscriptionGiveUps(
|
|
179779
|
-
sess,
|
|
179780
|
-
() => [...sessionManager.sessions].filter(
|
|
179781
|
-
(s) => s.peerNodeId === sess.peerNodeId && s.fabric?.fabricIndex === sess.fabric?.fabricIndex
|
|
179782
|
-
)
|
|
179783
|
-
);
|
|
179784
|
-
for (const sess of sessionManager.sessions) {
|
|
179785
|
-
hookGiveUpsWithPeers(sess);
|
|
179786
|
-
}
|
|
179787
|
-
this.wireImRequestTracking();
|
|
179788
|
-
} catch {
|
|
179789
|
-
}
|
|
179790
|
-
}
|
|
179791
|
-
// Stamp the time of every inbound Interaction Model request per session by
|
|
179792
|
-
// wrapping InteractionServer.onNewExchange. The wedge watchdog reads these to
|
|
179793
|
-
// tell a live-but-consuming controller from one that only keeps acking.
|
|
179794
|
-
wireImRequestTracking() {
|
|
179795
|
-
try {
|
|
179796
|
-
const is = this.server.env.get(InteractionServer);
|
|
179797
|
-
const marked = is;
|
|
179798
|
-
if (marked[imWrapMarker2]) {
|
|
179799
|
-
return;
|
|
179800
|
-
}
|
|
179801
|
-
const original = is.onNewExchange.bind(is);
|
|
179802
|
-
is.onNewExchange = (exchange, message) => {
|
|
179803
|
-
const session = exchange.session;
|
|
179804
|
-
if (session) {
|
|
179805
|
-
const now = Date.now();
|
|
179806
|
-
this.lastImRequestAt.set(session, now);
|
|
179807
|
-
const type = message?.payloadHeader?.messageType;
|
|
179808
|
-
if (type === MessageType.SubscribeRequest) {
|
|
179809
|
-
this.pushWedgeRing(this.subscribeTimesMs, session, now);
|
|
179810
|
-
} else if (type != null && commandMessageTypes2.includes(type)) {
|
|
179811
|
-
this.lastCommandImAt.set(session, now);
|
|
179812
|
-
}
|
|
179813
|
-
}
|
|
179814
|
-
return original(exchange, message);
|
|
179815
|
-
};
|
|
179816
|
-
marked[imWrapMarker2] = true;
|
|
179817
|
-
} catch {
|
|
179818
|
-
}
|
|
179819
|
-
}
|
|
179820
|
-
// Keep only the newest WEDGE_RING_SIZE timestamps per session.
|
|
179821
|
-
pushWedgeRing(ring, session, now) {
|
|
179822
|
-
const times = ring.get(session) ?? [];
|
|
179823
|
-
times.push(now);
|
|
179824
|
-
if (times.length > WEDGE_RING_SIZE) {
|
|
179825
|
-
times.splice(0, times.length - WEDGE_RING_SIZE);
|
|
179826
|
-
}
|
|
179827
|
-
ring.set(session, times);
|
|
179828
|
-
}
|
|
179829
|
-
// Watch this session's subscriptions for server-side delivery give-ups
|
|
179830
|
-
// (#365 v2 shadow). isTerminated true fires both on a peer cancel and on
|
|
179831
|
-
// the 3-strikes delivery give-up; only the give-up arrives without a
|
|
179832
|
-
// coincident inbound IM request, so that is the discriminator.
|
|
179833
|
-
hookSubscriptionGiveUps(session, peerSessions) {
|
|
179834
|
-
const key = session;
|
|
179835
|
-
if (this.wedgeHookedSessions.has(key)) {
|
|
179836
|
-
return;
|
|
179837
|
-
}
|
|
179838
|
-
const deleted = session.subscriptions?.deleted;
|
|
179839
|
-
if (typeof deleted?.on !== "function") {
|
|
179840
|
-
return;
|
|
179841
|
-
}
|
|
179842
|
-
this.wedgeHookedSessions.add(key);
|
|
179843
|
-
deleted.on((sub) => {
|
|
179844
|
-
if (sub?.isTerminated !== true) {
|
|
179845
|
-
return;
|
|
179846
|
-
}
|
|
179847
|
-
const now = Date.now();
|
|
179848
|
-
const fresh = (s) => {
|
|
179849
|
-
const at = this.lastImRequestAt.get(s);
|
|
179850
|
-
return at != null && now - at <= WEDGE_GIVE_UP_QUIET_MS;
|
|
179851
|
-
};
|
|
179852
|
-
if (fresh(key)) {
|
|
179853
|
-
return;
|
|
179854
|
-
}
|
|
179855
|
-
for (const s of peerSessions?.() ?? []) {
|
|
179856
|
-
if (s !== key && fresh(s)) {
|
|
179857
|
-
return;
|
|
179858
|
-
}
|
|
179859
|
-
}
|
|
179860
|
-
this.pushWedgeRing(this.giveUpTimesMs, key, now);
|
|
179861
|
-
this.log.debug(
|
|
179862
|
-
`wedge v2 give-up recorded sub=${sub?.subscriptionId}`
|
|
179863
|
-
);
|
|
179864
|
-
});
|
|
179865
|
-
}
|
|
179866
|
-
closeStaleSession(sessionId, minQuietMs = 0) {
|
|
179867
|
-
try {
|
|
179868
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
179869
|
-
for (const s of [...sessionManager.sessions]) {
|
|
179870
|
-
if (s.id !== sessionId || s.isClosing || s.subscriptions.size > 0) {
|
|
179871
|
-
continue;
|
|
179872
|
-
}
|
|
179873
|
-
const idleSec = Math.round((Date.now() - s.timestamp) / 1e3);
|
|
179874
|
-
if (!staleSessionShouldClose(
|
|
179875
|
-
s,
|
|
179876
|
-
Math.max(
|
|
179877
|
-
staleSessionQuietWindowMs(this.dataProvider.featureFlags),
|
|
179878
|
-
minQuietMs
|
|
179879
|
-
)
|
|
179880
|
-
)) {
|
|
179881
|
-
this.log.info(
|
|
179882
|
-
`Keeping session ${s.id} (peer ${s.peerNodeId}, 0 subs, last traffic ${idleSec}s ago)`
|
|
179883
|
-
);
|
|
179884
|
-
this.staleSessionTimers.set(
|
|
179885
|
-
sessionId,
|
|
179886
|
-
setTimeout(() => {
|
|
179887
|
-
this.staleSessionTimers.delete(sessionId);
|
|
179888
|
-
this.closeStaleSession(sessionId, minQuietMs);
|
|
179889
|
-
}, deadSessionTimeoutMs(this.dataProvider.featureFlags))
|
|
179890
|
-
);
|
|
179891
|
-
break;
|
|
179892
|
-
}
|
|
179893
|
-
this.log.warn(
|
|
179894
|
-
`Closing stale session ${s.id} (peer ${s.peerNodeId}, no subscriptions for ${deadSessionTimeoutMs(this.dataProvider.featureFlags) / 1e3}s, no traffic for ${idleSec}s)`
|
|
179895
|
-
);
|
|
179896
|
-
s.initiateClose().catch(() => {
|
|
179897
|
-
return s.initiateForceClose({
|
|
179898
|
-
cause: new Error("graceful close failed, forcing")
|
|
179899
|
-
});
|
|
179900
|
-
}).catch(() => {
|
|
179901
|
-
}).finally(() => this.triggerMdnsReAnnounce());
|
|
179902
|
-
break;
|
|
179903
|
-
}
|
|
179904
|
-
} catch {
|
|
179905
|
-
}
|
|
179906
|
-
}
|
|
179907
|
-
closeDeadSessions() {
|
|
179908
|
-
try {
|
|
179909
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
179910
|
-
const sessions = [...sessionManager.sessions];
|
|
179911
|
-
const closes = [];
|
|
179912
|
-
let kept = 0;
|
|
179913
|
-
for (const s of sessions) {
|
|
179914
|
-
if (s.isClosing || s.subscriptions.size > 0) {
|
|
179915
|
-
continue;
|
|
179916
|
-
}
|
|
179917
|
-
if (this.staleSessionTimers.has(s.id)) {
|
|
179918
|
-
continue;
|
|
179919
|
-
}
|
|
179920
|
-
const idleSec = Math.round((Date.now() - s.timestamp) / 1e3);
|
|
179921
|
-
if (!staleSessionShouldClose(
|
|
179922
|
-
s,
|
|
179923
|
-
staleSessionQuietWindowMs(this.dataProvider.featureFlags)
|
|
179924
|
-
)) {
|
|
179925
|
-
this.log.info(
|
|
179926
|
-
`Keeping session ${s.id} (peer ${s.peerNodeId}, 0 subs, last traffic ${idleSec}s ago)`
|
|
179927
|
-
);
|
|
179928
|
-
kept++;
|
|
179929
|
-
continue;
|
|
179930
|
-
}
|
|
179931
|
-
this.log.warn(
|
|
179932
|
-
`Closing dead session ${s.id} (peer ${s.peerNodeId}, no subscriptions for ${deadSessionTimeoutMs(this.dataProvider.featureFlags) / 1e3}s, no traffic for ${idleSec}s)`
|
|
179933
|
-
);
|
|
179934
|
-
closes.push(
|
|
179935
|
-
s.initiateClose().catch(() => {
|
|
179936
|
-
return s.initiateForceClose({
|
|
179937
|
-
cause: new Error("graceful close failed, forcing")
|
|
179938
|
-
});
|
|
179939
|
-
})
|
|
179940
|
-
);
|
|
179941
|
-
}
|
|
179942
|
-
if (kept > 0 && !this.deadSessionTimer) {
|
|
179943
|
-
this.deadSessionTimer = setTimeout(() => {
|
|
179944
|
-
this.deadSessionTimer = null;
|
|
179945
|
-
this.closeDeadSessions();
|
|
179946
|
-
}, deadSessionTimeoutMs(this.dataProvider.featureFlags));
|
|
179947
|
-
}
|
|
179948
|
-
if (closes.length > 0) {
|
|
179949
|
-
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
179950
|
-
}
|
|
179951
|
-
} catch {
|
|
179952
|
-
}
|
|
179953
|
-
}
|
|
179954
|
-
// Close every active session on shutdown so each controller is told to drop
|
|
179955
|
-
// its CASE session instead of being left with a stale one. Mirrors the
|
|
179956
|
-
// dead-session close, but covers all sessions and waits (capped) so the
|
|
179957
|
-
// close actually reaches the peer before the server is canceled.
|
|
179958
|
-
async closeActiveSessions() {
|
|
179959
|
-
try {
|
|
179960
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
179961
|
-
const closes = [];
|
|
179962
|
-
for (const s of [...sessionManager.sessions]) {
|
|
179963
|
-
if (s.isClosing) {
|
|
179964
|
-
continue;
|
|
179965
|
-
}
|
|
179966
|
-
closes.push(
|
|
179967
|
-
s.initiateClose().catch(() => {
|
|
179968
|
-
return s.initiateForceClose({
|
|
179969
|
-
cause: new Error("graceful close failed, forcing")
|
|
179970
|
-
});
|
|
179971
|
-
})
|
|
179972
|
-
);
|
|
179973
|
-
}
|
|
179974
|
-
if (closes.length === 0) {
|
|
179975
|
-
return;
|
|
179976
|
-
}
|
|
179977
|
-
this.log.info(`Closing ${closes.length} active session(s) on shutdown`);
|
|
179978
|
-
let timer;
|
|
179979
|
-
const timeout = new Promise((resolve11) => {
|
|
179980
|
-
timer = setTimeout(resolve11, SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2);
|
|
179981
|
-
});
|
|
179982
|
-
try {
|
|
179983
|
-
await Promise.race([Promise.allSettled(closes), timeout]);
|
|
179984
|
-
} finally {
|
|
179985
|
-
clearTimeout(timer);
|
|
179986
|
-
}
|
|
179987
|
-
} catch {
|
|
179988
|
-
}
|
|
179989
|
-
}
|
|
179990
|
-
/**
|
|
179991
|
-
* Force a fresh mDNS operational advertisement after session cleanup.
|
|
179992
|
-
* matter.js DeviceAdvertiser only re-announces when a subscription is
|
|
179993
|
-
* canceled BY THE PEER. When the server cancels after 3 delivery
|
|
179994
|
-
* timeouts, no re-announcement happens and the controller may not
|
|
179995
|
-
* realize it should reconnect (#266).
|
|
179996
|
-
*/
|
|
179997
|
-
triggerMdnsReAnnounce() {
|
|
179998
|
-
try {
|
|
179999
|
-
const advertiser = this.server.env.get(DeviceAdvertiser);
|
|
180000
|
-
advertiser.restartAdvertisement();
|
|
180001
|
-
this.log.info("Triggered mDNS re-announcement after session cleanup");
|
|
180002
|
-
} catch {
|
|
180003
|
-
}
|
|
180004
|
-
}
|
|
180005
|
-
unwireSessionDiagnostics() {
|
|
180006
|
-
try {
|
|
180007
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
180008
|
-
if (this.sessionDiagHandler) {
|
|
180009
|
-
sessionManager.subscriptionsChanged.off(this.sessionDiagHandler);
|
|
180010
|
-
}
|
|
180011
|
-
if (this.sessionAddedHandler) {
|
|
180012
|
-
sessionManager.sessions.added.off(this.sessionAddedHandler);
|
|
180013
|
-
}
|
|
180014
|
-
if (this.sessionDeletedHandler) {
|
|
180015
|
-
sessionManager.sessions.deleted.off(this.sessionDeletedHandler);
|
|
180016
|
-
}
|
|
180017
|
-
} catch {
|
|
180018
|
-
}
|
|
180019
|
-
this.sessionDiagHandler = void 0;
|
|
180020
|
-
this.sessionAddedHandler = void 0;
|
|
180021
|
-
this.sessionDeletedHandler = void 0;
|
|
180022
|
-
if (this.deadSessionTimer) {
|
|
180023
|
-
clearTimeout(this.deadSessionTimer);
|
|
180024
|
-
this.deadSessionTimer = null;
|
|
180025
|
-
}
|
|
180026
|
-
for (const timer of this.staleSessionTimers.values()) {
|
|
180027
|
-
clearTimeout(timer);
|
|
180028
|
-
}
|
|
180029
|
-
this.staleSessionTimers.clear();
|
|
180030
|
-
this.sessionStartedAt.clear();
|
|
180031
|
-
}
|
|
180032
|
-
// Warn at the fabric-added event when Alexa pairs on a non-5540 port. Alexa
|
|
180033
|
-
// completes AddNOC but never CASEs, so the fabric rolls back on failsafe
|
|
180034
|
-
// expiry ~20s later and the committed-fabric warning path never sees it (#401).
|
|
180035
|
-
wireFabricWarnings() {
|
|
180036
|
-
this.unwireFabricWarnings();
|
|
180037
|
-
try {
|
|
180038
|
-
const fabrics = this.server.env.get(FabricManager);
|
|
180039
|
-
this.fabricAddedHandler = (fabric) => {
|
|
180040
|
-
const port = this.dataProvider.port;
|
|
180041
|
-
if (alexaPairingPortProblem(fabric.rootVendorId, port)) {
|
|
180042
|
-
this.log.warn(
|
|
180043
|
-
`Fabric added by Amazon Alexa (vendor ${fabric.rootVendorId}) on port ${port}. Alexa only completes pairing on port 5540, this attempt will roll back about 20s after AddNOC. Recreate the bridge on port 5540 (#401)`
|
|
180044
|
-
);
|
|
180045
|
-
}
|
|
180046
|
-
};
|
|
180047
|
-
fabrics.events.added.on(this.fabricAddedHandler);
|
|
180048
|
-
} catch {
|
|
180049
|
-
}
|
|
180050
|
-
}
|
|
180051
|
-
unwireFabricWarnings() {
|
|
180052
|
-
try {
|
|
180053
|
-
const fabrics = this.server.env.get(FabricManager);
|
|
180054
|
-
if (this.fabricAddedHandler) {
|
|
180055
|
-
fabrics.events.added.off(this.fabricAddedHandler);
|
|
180056
|
-
}
|
|
180057
|
-
} catch {
|
|
180058
|
-
}
|
|
180059
|
-
this.fabricAddedHandler = void 0;
|
|
180060
|
-
}
|
|
180061
|
-
// Start the periodic age-based session rotation (#287).
|
|
180062
|
-
startSessionRotation() {
|
|
180063
|
-
this.stopSessionRotation();
|
|
180064
|
-
const hours = this.readSessionMaxAgeHours();
|
|
180065
|
-
if (hours === 0) {
|
|
180066
|
-
this.log.info(
|
|
180067
|
-
"Session rotation disabled (HAMH_MATTER_SESSION_MAX_AGE_HOURS=0)"
|
|
180068
|
-
);
|
|
180069
|
-
return;
|
|
180070
|
-
}
|
|
180071
|
-
this.maxSessionAgeMs = hours * 60 * 60 * 1e3;
|
|
180072
|
-
this.rotationTimer = setInterval(
|
|
180073
|
-
() => this.rotateAgedSessions(),
|
|
180074
|
-
ROTATION_CHECK_INTERVAL_MS
|
|
180075
|
-
);
|
|
180076
|
-
this.log.info(
|
|
180077
|
-
`Session rotation: max age ${hours}h, check every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
|
|
180078
|
-
);
|
|
180079
|
-
}
|
|
180080
|
-
stopSessionRotation() {
|
|
180081
|
-
if (this.rotationTimer) {
|
|
180082
|
-
clearInterval(this.rotationTimer);
|
|
180083
|
-
this.rotationTimer = null;
|
|
180084
|
-
}
|
|
180085
|
-
}
|
|
180086
|
-
// Opt-in wedge watchdog: reuse the rotation check cadence (5min) to look for
|
|
180087
|
-
// the one session wedged on "Updating" and rotate just that one.
|
|
180088
|
-
startWedgeWatchdog() {
|
|
180089
|
-
this.stopWedgeWatchdog();
|
|
180090
|
-
if (!this.dataProvider.featureFlags?.wedgeWatchdog) {
|
|
180091
|
-
return;
|
|
180092
|
-
}
|
|
180093
|
-
this.wedgeWatchdogTimer = setInterval(
|
|
180094
|
-
() => this.runWedgeWatchdogCheck(),
|
|
180095
|
-
ROTATION_CHECK_INTERVAL_MS
|
|
180096
|
-
);
|
|
180097
|
-
this.log.info(
|
|
180098
|
-
`Wedge watchdog: checking every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
|
|
180099
|
-
);
|
|
180100
|
-
}
|
|
180101
|
-
stopWedgeWatchdog() {
|
|
180102
|
-
if (this.wedgeWatchdogTimer) {
|
|
180103
|
-
clearInterval(this.wedgeWatchdogTimer);
|
|
180104
|
-
this.wedgeWatchdogTimer = null;
|
|
180105
|
-
}
|
|
180106
|
-
}
|
|
180107
|
-
// Rotate exactly the sessions the pure rule flags as wedged. Closing is the
|
|
180108
|
-
// same graceful-then-force path age rotation uses, so a false positive just
|
|
180109
|
-
// re-CASEs the controller.
|
|
180110
|
-
runWedgeWatchdogCheck() {
|
|
180111
|
-
try {
|
|
180112
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
180113
|
-
const now = Date.now();
|
|
180114
|
-
const closes = [];
|
|
180115
|
-
for (const s of [...sessionManager.sessions]) {
|
|
180116
|
-
if (s.isClosing) continue;
|
|
180117
|
-
const startedAt = this.sessionStartedAt.get(s.id);
|
|
180118
|
-
const sessionAgeMs = startedAt != null ? now - startedAt : 0;
|
|
180119
|
-
const lastImAt = this.lastImRequestAt.get(s);
|
|
180120
|
-
const lastImRequestMsAgo = lastImAt != null ? now - lastImAt : null;
|
|
180121
|
-
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
180122
|
-
const lastRotatedMsAgo = lastRotatedAt != null ? now - lastRotatedAt : null;
|
|
180123
|
-
const v1 = decideWedgeRotation({
|
|
180124
|
-
subscriptionCount: s.subscriptions.size,
|
|
180125
|
-
sessionAgeMs,
|
|
180126
|
-
lastImRequestMsAgo,
|
|
180127
|
-
lastRotatedMsAgo
|
|
180128
|
-
});
|
|
180129
|
-
const lastCmdAt = this.lastCommandImAt.get(s);
|
|
180130
|
-
const commandSilenceMs = lastCmdAt != null ? now - lastCmdAt : null;
|
|
180131
|
-
const subscribeTimes = this.subscribeTimesMs.get(s) ?? [];
|
|
180132
|
-
const giveUpTimes = this.giveUpTimesMs.get(s) ?? [];
|
|
180133
|
-
const v2 = decideWedgeRotationV2({
|
|
180134
|
-
subscriptionCount: s.subscriptions.size,
|
|
180135
|
-
sessionAgeMs,
|
|
180136
|
-
commandSilenceMs,
|
|
180137
|
-
subscribeTimesMs: subscribeTimes,
|
|
180138
|
-
giveUpTimesMs: giveUpTimes,
|
|
180139
|
-
nowMs: now,
|
|
180140
|
-
lastRotatedMsAgo
|
|
180141
|
-
});
|
|
180142
|
-
const cmdSilenceMin = Math.round(
|
|
180143
|
-
(commandSilenceMs ?? sessionAgeMs) / 6e4
|
|
180144
|
-
);
|
|
180145
|
-
const v2Stats = `cmdSilenceMin=${cmdSilenceMin} subscribes30m=${countRecent(subscribeTimes, now)} giveUps30m=${countRecent(giveUpTimes, now)}`;
|
|
180146
|
-
if (v2 && !v1) {
|
|
180147
|
-
this.log.info(`wedge v2 would rotate session ${s.id}: ${v2Stats}`);
|
|
180148
|
-
}
|
|
180149
|
-
if (!v1) {
|
|
180150
|
-
continue;
|
|
180151
|
-
}
|
|
180152
|
-
this.log.info(
|
|
180153
|
-
`wedge v2 session ${s.id}: v1 rotated, v2 agree=${v2} ${v2Stats}`
|
|
180154
|
-
);
|
|
180155
|
-
const silenceMin = Math.round(
|
|
180156
|
-
(lastImRequestMsAgo ?? sessionAgeMs) / 6e4
|
|
180157
|
-
);
|
|
180158
|
-
this.log.info(
|
|
180159
|
-
`Wedge watchdog: rotating session ${s.id}, no inbound interaction for ${silenceMin}min`
|
|
180160
|
-
);
|
|
180161
|
-
this.wedgeLastRotatedAt.set(s, now);
|
|
180162
|
-
closes.push(
|
|
180163
|
-
s.initiateClose().catch(() => {
|
|
180164
|
-
return s.initiateForceClose({
|
|
180165
|
-
cause: new Error("wedge watchdog, forcing")
|
|
180166
|
-
});
|
|
180167
|
-
})
|
|
180168
|
-
);
|
|
180169
|
-
}
|
|
180170
|
-
if (closes.length > 0) {
|
|
180171
|
-
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
180172
|
-
}
|
|
180173
|
-
} catch {
|
|
180174
|
-
}
|
|
180175
|
-
}
|
|
180176
|
-
// Poll the interface addresses mDNS advertises and re-announce when they
|
|
180177
|
-
// change. matter.js caches the operational records at first announcement, so
|
|
180178
|
-
// a dynamic ISP IPv6 prefix change keeps advertising the dead global address
|
|
180179
|
-
// until a restart (#415). refreshOperationalAdvertisement re-runs the lookup.
|
|
180180
|
-
startMdnsAddressWatch() {
|
|
180181
|
-
this.stopMdnsAddressWatch();
|
|
180182
|
-
const { netInterface, stripGlobalIpv6, ipv4Enabled } = this.readMdnsWatchSettings();
|
|
180183
|
-
this.advertisedAddressSnapshot = collectAdvertisedAddresses(
|
|
180184
|
-
os10.networkInterfaces(),
|
|
180185
|
-
netInterface,
|
|
180186
|
-
stripGlobalIpv6,
|
|
180187
|
-
ipv4Enabled
|
|
180188
|
-
);
|
|
180189
|
-
this.mdnsAddressTimer = setInterval(() => {
|
|
180190
|
-
if (this.mdnsCheckInFlight) return;
|
|
180191
|
-
this.mdnsCheckInFlight = true;
|
|
180192
|
-
this.checkAdvertisedAddresses().catch((e) => {
|
|
180193
|
-
this.log.warn("mDNS address check failed:", e);
|
|
180194
|
-
}).finally(() => {
|
|
180195
|
-
this.mdnsCheckInFlight = false;
|
|
180196
|
-
});
|
|
180197
|
-
}, MDNS_ADDRESS_CHECK_INTERVAL_MS2);
|
|
180198
|
-
}
|
|
180199
|
-
stopMdnsAddressWatch() {
|
|
180200
|
-
if (this.mdnsAddressTimer) {
|
|
180201
|
-
clearInterval(this.mdnsAddressTimer);
|
|
180202
|
-
this.mdnsAddressTimer = null;
|
|
180203
|
-
}
|
|
180204
|
-
}
|
|
180205
|
-
// Read the mDNS settings the shared setup applied. MdnsService lives on the
|
|
180206
|
-
// root env and stripGlobalIpv6 swaps in a FilteredNetwork, so both are
|
|
180207
|
-
// visible from the bridge env.
|
|
180208
|
-
readMdnsWatchSettings() {
|
|
180209
|
-
try {
|
|
180210
|
-
const mdns2 = this.server.env.get(MdnsService);
|
|
180211
|
-
const stripGlobalIpv6 = this.server.env.get(Network) instanceof FilteredNetwork;
|
|
180212
|
-
return {
|
|
180213
|
-
netInterface: mdns2.limitedToNetInterface,
|
|
180214
|
-
stripGlobalIpv6,
|
|
180215
|
-
ipv4Enabled: mdns2.enableIpv4
|
|
180216
|
-
};
|
|
180217
|
-
} catch {
|
|
180218
|
-
return { stripGlobalIpv6: false, ipv4Enabled: true };
|
|
180219
|
-
}
|
|
180220
|
-
}
|
|
180221
|
-
async checkAdvertisedAddresses() {
|
|
180222
|
-
const { netInterface, stripGlobalIpv6, ipv4Enabled } = this.readMdnsWatchSettings();
|
|
180223
|
-
const advertiser = this.server.env.get(DeviceAdvertiser);
|
|
180224
|
-
const fabrics = this.server.env.get(FabricManager);
|
|
180225
|
-
this.advertisedAddressSnapshot = await runMdnsAddressWatchTick({
|
|
180226
|
-
readInterfaces: () => os10.networkInterfaces(),
|
|
180227
|
-
netInterface,
|
|
180228
|
-
stripGlobalIpv6,
|
|
180229
|
-
ipv4Enabled,
|
|
180230
|
-
currentSnapshot: this.advertisedAddressSnapshot,
|
|
180231
|
-
fabrics: () => fabrics.fabrics,
|
|
180232
|
-
refresh: (fabric) => advertiser.refreshOperationalAdvertisement(fabric),
|
|
180233
|
-
onChange: (prev, curr) => {
|
|
180234
|
-
this.log.warn(
|
|
180235
|
-
`Advertised address set changed (${prev.length} -> ${curr.length} addresses), re-announcing operational mDNS (#415)`
|
|
180236
|
-
);
|
|
180237
|
-
}
|
|
180238
|
-
});
|
|
180239
|
-
}
|
|
180240
|
-
// Resolve session rotation max age. Bridge config wins, then env var,
|
|
180241
|
-
// then built-in default. 0 disables, otherwise clamped to range.
|
|
180242
|
-
readSessionMaxAgeHours() {
|
|
180243
|
-
const { min, max } = SESSION_MAX_AGE_HOURS_RANGE;
|
|
180244
|
-
const fromConfig = this.dataProvider.sessionMaxAgeHours;
|
|
180245
|
-
if (fromConfig != null && Number.isFinite(fromConfig) && fromConfig >= 0) {
|
|
180246
|
-
if (fromConfig === 0) return 0;
|
|
180247
|
-
if (fromConfig < min) return min;
|
|
180248
|
-
if (fromConfig > max) return max;
|
|
180249
|
-
return fromConfig;
|
|
180250
|
-
}
|
|
180251
|
-
const raw = process.env.HAMH_MATTER_SESSION_MAX_AGE_HOURS;
|
|
180252
|
-
const parsed = parseSessionMaxAgeHours(raw);
|
|
180253
|
-
if (parsed == null) {
|
|
180254
|
-
this.log.warn(
|
|
180255
|
-
`Invalid HAMH_MATTER_SESSION_MAX_AGE_HOURS=${raw}, falling back to ${DEFAULT_SESSION_MAX_AGE_HOURS}h`
|
|
180256
|
-
);
|
|
180257
|
-
return DEFAULT_SESSION_MAX_AGE_HOURS;
|
|
180258
|
-
}
|
|
180259
|
-
return parsed;
|
|
180260
|
-
}
|
|
180261
|
-
// Gracefully close sessions older than maxSessionAgeMs so controllers
|
|
180262
|
-
// re-establish CASE and re-subscribe. Stale (0-sub) sessions are handled
|
|
180263
|
-
// by the existing dead-session path, so only rotate ones with subscriptions.
|
|
180264
|
-
rotateAgedSessions() {
|
|
180265
|
-
if (this.maxSessionAgeMs === 0) return;
|
|
180266
|
-
try {
|
|
180267
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
180268
|
-
const now = Date.now();
|
|
180269
|
-
const closes = [];
|
|
180270
|
-
for (const s of [...sessionManager.sessions]) {
|
|
180271
|
-
const startedAt = this.sessionStartedAt.get(s.id);
|
|
180272
|
-
if (startedAt == null) continue;
|
|
180273
|
-
const ageMs = now - startedAt;
|
|
180274
|
-
if (ageMs < this.maxSessionAgeMs || s.isClosing || s.subscriptions.size === 0) {
|
|
180275
|
-
continue;
|
|
180276
|
-
}
|
|
180277
|
-
const ageMin = Math.round(ageMs / 6e4);
|
|
180278
|
-
this.log.info(
|
|
180279
|
-
`Rotating session ${s.id} (peer ${s.peerNodeId}, age ${ageMin}min, subs ${s.subscriptions.size})`
|
|
180280
|
-
);
|
|
180281
|
-
closes.push(
|
|
180282
|
-
s.initiateClose().catch(() => {
|
|
180283
|
-
return s.initiateForceClose({
|
|
180284
|
-
cause: new Error("session rotation, forcing")
|
|
180285
|
-
});
|
|
180286
|
-
})
|
|
180287
|
-
);
|
|
180288
|
-
}
|
|
180289
|
-
if (closes.length > 0) {
|
|
180290
|
-
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
180291
|
-
}
|
|
180292
|
-
} catch {
|
|
180293
|
-
}
|
|
180294
|
-
}
|
|
180295
179551
|
stopAutoForceSync() {
|
|
180296
179552
|
if (this.autoForceSyncTimer) {
|
|
180297
179553
|
clearInterval(this.autoForceSyncTimer);
|