@riddix/hamh 2.1.0-alpha.871 → 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:";
|
|
@@ -169353,6 +169399,99 @@ var CarbonMonoxideSensorType = AirQualitySensorDevice.with(
|
|
|
169353
169399
|
CarbonMonoxideConcentrationMeasurementServer2
|
|
169354
169400
|
);
|
|
169355
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
|
+
|
|
169356
169495
|
// src/matter/endpoints/legacy/sensor/devices/electrical-meter.ts
|
|
169357
169496
|
init_home_assistant_entity_behavior();
|
|
169358
169497
|
|
|
@@ -170372,6 +170511,208 @@ var Pm1SensorType = AirQualitySensorDevice.with(
|
|
|
170372
170511
|
Pm1ConcentrationMeasurementServer2
|
|
170373
170512
|
);
|
|
170374
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
|
+
|
|
170375
170716
|
// src/matter/endpoints/legacy/sensor/devices/pressure-sensor.ts
|
|
170376
170717
|
init_home_assistant_entity_behavior();
|
|
170377
170718
|
var pressureSensorConfig = {
|
|
@@ -170590,7 +170931,7 @@ var TvocConcentrationMeasurementServer = class extends TvocConcentrationMeasurem
|
|
|
170590
170931
|
};
|
|
170591
170932
|
|
|
170592
170933
|
// src/matter/endpoints/legacy/sensor/devices/tvoc-sensor.ts
|
|
170593
|
-
var
|
|
170934
|
+
var logger234 = Logger.get("TvocSensor");
|
|
170594
170935
|
function airQualityFromUgm3(value) {
|
|
170595
170936
|
if (value <= 300) return AirQuality3.AirQualityEnum.Good;
|
|
170596
170937
|
if (value <= 1e3) return AirQuality3.AirQualityEnum.Fair;
|
|
@@ -170631,17 +170972,17 @@ var TvocAirQualityServer = class extends TvocAirQualityServerBase {
|
|
|
170631
170972
|
const attributes9 = entity.state.attributes;
|
|
170632
170973
|
const deviceClass = attributes9.device_class;
|
|
170633
170974
|
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170634
|
-
|
|
170975
|
+
logger234.debug(
|
|
170635
170976
|
`[${entity.entity_id}] TVOC update: state="${state}", device_class="${deviceClass}"`
|
|
170636
170977
|
);
|
|
170637
170978
|
if (state != null && !Number.isNaN(+state)) {
|
|
170638
170979
|
const value = +state;
|
|
170639
170980
|
airQuality = deviceClass === SensorDeviceClass.volatile_organic_compounds ? airQualityFromUgm3(value) : airQualityFromPpb(value);
|
|
170640
|
-
|
|
170981
|
+
logger234.debug(
|
|
170641
170982
|
`[${entity.entity_id}] TVOC value=${value} (${deviceClass}) -> airQuality=${AirQuality3.AirQualityEnum[airQuality]}`
|
|
170642
170983
|
);
|
|
170643
170984
|
} else {
|
|
170644
|
-
|
|
170985
|
+
logger234.warn(
|
|
170645
170986
|
`[${entity.entity_id}] TVOC state not a valid number: "${state}"`
|
|
170646
170987
|
);
|
|
170647
170988
|
}
|
|
@@ -170661,301 +171002,6 @@ init_dist();
|
|
|
170661
171002
|
init_types2();
|
|
170662
171003
|
init_diagnostic_event_bus();
|
|
170663
171004
|
|
|
170664
|
-
// src/matter/behaviors/carbon-dioxide-concentration-measurement-server.ts
|
|
170665
|
-
init_home_assistant_entity_behavior();
|
|
170666
|
-
var CarbonDioxideConcentrationMeasurementServerBase = CarbonDioxideConcentrationMeasurementServer.with(
|
|
170667
|
-
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
170668
|
-
);
|
|
170669
|
-
var CarbonDioxideConcentrationMeasurementServer2 = class extends CarbonDioxideConcentrationMeasurementServerBase {
|
|
170670
|
-
async initialize() {
|
|
170671
|
-
if (this.state.measuredValue === void 0) {
|
|
170672
|
-
this.state.measuredValue = null;
|
|
170673
|
-
}
|
|
170674
|
-
if (this.state.minMeasuredValue === void 0) {
|
|
170675
|
-
this.state.minMeasuredValue = null;
|
|
170676
|
-
}
|
|
170677
|
-
if (this.state.maxMeasuredValue === void 0) {
|
|
170678
|
-
this.state.maxMeasuredValue = null;
|
|
170679
|
-
}
|
|
170680
|
-
if (this.state.uncertainty === void 0) {
|
|
170681
|
-
this.state.uncertainty = 0;
|
|
170682
|
-
}
|
|
170683
|
-
if (this.state.measurementUnit === void 0) {
|
|
170684
|
-
this.state.measurementUnit = ConcentrationMeasurement3.MeasurementUnit.Ppm;
|
|
170685
|
-
}
|
|
170686
|
-
if (this.state.measurementMedium === void 0) {
|
|
170687
|
-
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
170688
|
-
}
|
|
170689
|
-
await super.initialize();
|
|
170690
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170691
|
-
this.update(homeAssistant.entity);
|
|
170692
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170693
|
-
}
|
|
170694
|
-
update(entity) {
|
|
170695
|
-
if (!entity.state || !entity.state.attributes) {
|
|
170696
|
-
return;
|
|
170697
|
-
}
|
|
170698
|
-
const state = entity.state.state;
|
|
170699
|
-
let measuredValue = null;
|
|
170700
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170701
|
-
measuredValue = +state;
|
|
170702
|
-
}
|
|
170703
|
-
applyPatchState(this.state, {
|
|
170704
|
-
measuredValue
|
|
170705
|
-
});
|
|
170706
|
-
}
|
|
170707
|
-
};
|
|
170708
|
-
|
|
170709
|
-
// src/matter/endpoints/legacy/sensor/devices/co2-sensor.ts
|
|
170710
|
-
init_home_assistant_entity_behavior();
|
|
170711
|
-
var Co2AirQualityServerBase = AirQualityServer.with(
|
|
170712
|
-
AirQuality3.Feature.Fair,
|
|
170713
|
-
AirQuality3.Feature.Moderate,
|
|
170714
|
-
AirQuality3.Feature.VeryPoor,
|
|
170715
|
-
AirQuality3.Feature.ExtremelyPoor
|
|
170716
|
-
);
|
|
170717
|
-
var Co2AirQualityServer = class extends Co2AirQualityServerBase {
|
|
170718
|
-
async initialize() {
|
|
170719
|
-
if (this.state.airQuality === void 0) {
|
|
170720
|
-
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170721
|
-
}
|
|
170722
|
-
await super.initialize();
|
|
170723
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170724
|
-
this.update(homeAssistant.entity);
|
|
170725
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170726
|
-
}
|
|
170727
|
-
update(entity) {
|
|
170728
|
-
const state = entity.state.state;
|
|
170729
|
-
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170730
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170731
|
-
const value = +state;
|
|
170732
|
-
if (value <= 800) {
|
|
170733
|
-
airQuality = AirQuality3.AirQualityEnum.Good;
|
|
170734
|
-
} else if (value <= 1e3) {
|
|
170735
|
-
airQuality = AirQuality3.AirQualityEnum.Fair;
|
|
170736
|
-
} else if (value <= 1400) {
|
|
170737
|
-
airQuality = AirQuality3.AirQualityEnum.Moderate;
|
|
170738
|
-
} else if (value <= 2e3) {
|
|
170739
|
-
airQuality = AirQuality3.AirQualityEnum.Poor;
|
|
170740
|
-
} else if (value <= 5e3) {
|
|
170741
|
-
airQuality = AirQuality3.AirQualityEnum.VeryPoor;
|
|
170742
|
-
} else {
|
|
170743
|
-
airQuality = AirQuality3.AirQualityEnum.ExtremelyPoor;
|
|
170744
|
-
}
|
|
170745
|
-
}
|
|
170746
|
-
applyPatchState(this.state, { airQuality });
|
|
170747
|
-
}
|
|
170748
|
-
};
|
|
170749
|
-
var Co2SensorType = AirQualitySensorDevice.with(
|
|
170750
|
-
BasicInformationServer2,
|
|
170751
|
-
IdentifyServer2,
|
|
170752
|
-
HomeAssistantEntityBehavior,
|
|
170753
|
-
Co2AirQualityServer,
|
|
170754
|
-
CarbonDioxideConcentrationMeasurementServer2
|
|
170755
|
-
);
|
|
170756
|
-
|
|
170757
|
-
// src/matter/endpoints/legacy/sensor/devices/pm10-sensor.ts
|
|
170758
|
-
init_home_assistant_entity_behavior();
|
|
170759
|
-
|
|
170760
|
-
// src/matter/behaviors/pm10-concentration-measurement-server.ts
|
|
170761
|
-
init_home_assistant_entity_behavior();
|
|
170762
|
-
var Pm10ConcentrationMeasurementServerBase = Pm10ConcentrationMeasurementServer.with(
|
|
170763
|
-
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
170764
|
-
);
|
|
170765
|
-
var Pm10ConcentrationMeasurementServer2 = class extends Pm10ConcentrationMeasurementServerBase {
|
|
170766
|
-
async initialize() {
|
|
170767
|
-
if (this.state.measuredValue === void 0) {
|
|
170768
|
-
this.state.measuredValue = null;
|
|
170769
|
-
}
|
|
170770
|
-
if (this.state.minMeasuredValue === void 0) {
|
|
170771
|
-
this.state.minMeasuredValue = null;
|
|
170772
|
-
}
|
|
170773
|
-
if (this.state.maxMeasuredValue === void 0) {
|
|
170774
|
-
this.state.maxMeasuredValue = null;
|
|
170775
|
-
}
|
|
170776
|
-
if (this.state.uncertainty === void 0) {
|
|
170777
|
-
this.state.uncertainty = 0;
|
|
170778
|
-
}
|
|
170779
|
-
if (this.state.measurementUnit === void 0) {
|
|
170780
|
-
this.state.measurementUnit = ConcentrationMeasurement3.MeasurementUnit.Ugm3;
|
|
170781
|
-
}
|
|
170782
|
-
if (this.state.measurementMedium === void 0) {
|
|
170783
|
-
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
170784
|
-
}
|
|
170785
|
-
await super.initialize();
|
|
170786
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170787
|
-
this.update(homeAssistant.entity);
|
|
170788
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170789
|
-
}
|
|
170790
|
-
update(entity) {
|
|
170791
|
-
if (!entity.state || !entity.state.attributes) {
|
|
170792
|
-
return;
|
|
170793
|
-
}
|
|
170794
|
-
const state = entity.state.state;
|
|
170795
|
-
let measuredValue = null;
|
|
170796
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170797
|
-
measuredValue = +state;
|
|
170798
|
-
}
|
|
170799
|
-
applyPatchState(this.state, {
|
|
170800
|
-
measuredValue
|
|
170801
|
-
});
|
|
170802
|
-
}
|
|
170803
|
-
};
|
|
170804
|
-
|
|
170805
|
-
// src/matter/endpoints/legacy/sensor/devices/pm10-sensor.ts
|
|
170806
|
-
var Pm10AirQualityServerBase = AirQualityServer.with(
|
|
170807
|
-
AirQuality3.Feature.Fair,
|
|
170808
|
-
AirQuality3.Feature.Moderate,
|
|
170809
|
-
AirQuality3.Feature.VeryPoor,
|
|
170810
|
-
AirQuality3.Feature.ExtremelyPoor
|
|
170811
|
-
);
|
|
170812
|
-
var Pm10AirQualityServer = class extends Pm10AirQualityServerBase {
|
|
170813
|
-
async initialize() {
|
|
170814
|
-
if (this.state.airQuality === void 0) {
|
|
170815
|
-
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170816
|
-
}
|
|
170817
|
-
await super.initialize();
|
|
170818
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170819
|
-
this.update(homeAssistant.entity);
|
|
170820
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170821
|
-
}
|
|
170822
|
-
update(entity) {
|
|
170823
|
-
const state = entity.state.state;
|
|
170824
|
-
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170825
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170826
|
-
const value = +state;
|
|
170827
|
-
if (value <= 54) {
|
|
170828
|
-
airQuality = AirQuality3.AirQualityEnum.Good;
|
|
170829
|
-
} else if (value <= 154) {
|
|
170830
|
-
airQuality = AirQuality3.AirQualityEnum.Fair;
|
|
170831
|
-
} else if (value <= 254) {
|
|
170832
|
-
airQuality = AirQuality3.AirQualityEnum.Moderate;
|
|
170833
|
-
} else if (value <= 354) {
|
|
170834
|
-
airQuality = AirQuality3.AirQualityEnum.Poor;
|
|
170835
|
-
} else if (value <= 424) {
|
|
170836
|
-
airQuality = AirQuality3.AirQualityEnum.VeryPoor;
|
|
170837
|
-
} else {
|
|
170838
|
-
airQuality = AirQuality3.AirQualityEnum.ExtremelyPoor;
|
|
170839
|
-
}
|
|
170840
|
-
}
|
|
170841
|
-
applyPatchState(this.state, { airQuality });
|
|
170842
|
-
}
|
|
170843
|
-
};
|
|
170844
|
-
var Pm10SensorType = AirQualitySensorDevice.with(
|
|
170845
|
-
BasicInformationServer2,
|
|
170846
|
-
IdentifyServer2,
|
|
170847
|
-
HomeAssistantEntityBehavior,
|
|
170848
|
-
Pm10AirQualityServer,
|
|
170849
|
-
Pm10ConcentrationMeasurementServer2
|
|
170850
|
-
);
|
|
170851
|
-
|
|
170852
|
-
// src/matter/endpoints/legacy/sensor/devices/pm25-sensor.ts
|
|
170853
|
-
init_esm();
|
|
170854
|
-
init_home_assistant_entity_behavior();
|
|
170855
|
-
|
|
170856
|
-
// src/matter/behaviors/pm25-concentration-measurement-server.ts
|
|
170857
|
-
init_esm();
|
|
170858
|
-
init_home_assistant_entity_behavior();
|
|
170859
|
-
var logger233 = Logger.get("Pm25ConcentrationMeasurementServer");
|
|
170860
|
-
var Pm25ConcentrationMeasurementServerBase = Pm25ConcentrationMeasurementServer.with(
|
|
170861
|
-
ConcentrationMeasurement3.Feature.NumericMeasurement
|
|
170862
|
-
);
|
|
170863
|
-
var Pm25ConcentrationMeasurementServer2 = class extends Pm25ConcentrationMeasurementServerBase {
|
|
170864
|
-
async initialize() {
|
|
170865
|
-
if (this.state.measuredValue === void 0) {
|
|
170866
|
-
this.state.measuredValue = null;
|
|
170867
|
-
}
|
|
170868
|
-
if (this.state.minMeasuredValue === void 0) {
|
|
170869
|
-
this.state.minMeasuredValue = null;
|
|
170870
|
-
}
|
|
170871
|
-
if (this.state.maxMeasuredValue === void 0) {
|
|
170872
|
-
this.state.maxMeasuredValue = null;
|
|
170873
|
-
}
|
|
170874
|
-
if (this.state.uncertainty === void 0) {
|
|
170875
|
-
this.state.uncertainty = 0;
|
|
170876
|
-
}
|
|
170877
|
-
if (this.state.measurementUnit === void 0) {
|
|
170878
|
-
this.state.measurementUnit = ConcentrationMeasurement3.MeasurementUnit.Ugm3;
|
|
170879
|
-
}
|
|
170880
|
-
if (this.state.measurementMedium === void 0) {
|
|
170881
|
-
this.state.measurementMedium = ConcentrationMeasurement3.MeasurementMedium.Air;
|
|
170882
|
-
}
|
|
170883
|
-
logger233.debug(
|
|
170884
|
-
"Pm25ConcentrationMeasurementServer: before super.initialize()"
|
|
170885
|
-
);
|
|
170886
|
-
await super.initialize();
|
|
170887
|
-
logger233.debug(
|
|
170888
|
-
"Pm25ConcentrationMeasurementServer: after super.initialize()"
|
|
170889
|
-
);
|
|
170890
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170891
|
-
this.update(homeAssistant.entity);
|
|
170892
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170893
|
-
}
|
|
170894
|
-
update(entity) {
|
|
170895
|
-
if (!entity.state || !entity.state.attributes) {
|
|
170896
|
-
return;
|
|
170897
|
-
}
|
|
170898
|
-
const state = entity.state.state;
|
|
170899
|
-
let measuredValue = null;
|
|
170900
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170901
|
-
measuredValue = +state;
|
|
170902
|
-
}
|
|
170903
|
-
applyPatchState(this.state, {
|
|
170904
|
-
measuredValue
|
|
170905
|
-
});
|
|
170906
|
-
}
|
|
170907
|
-
};
|
|
170908
|
-
|
|
170909
|
-
// src/matter/endpoints/legacy/sensor/devices/pm25-sensor.ts
|
|
170910
|
-
var logger234 = Logger.get("Pm25AirQualityServer");
|
|
170911
|
-
var Pm25AirQualityServerBase = AirQualityServer.with(
|
|
170912
|
-
AirQuality3.Feature.Fair,
|
|
170913
|
-
AirQuality3.Feature.Moderate,
|
|
170914
|
-
AirQuality3.Feature.VeryPoor,
|
|
170915
|
-
AirQuality3.Feature.ExtremelyPoor
|
|
170916
|
-
);
|
|
170917
|
-
var Pm25AirQualityServer = class extends Pm25AirQualityServerBase {
|
|
170918
|
-
async initialize() {
|
|
170919
|
-
if (this.state.airQuality === void 0) {
|
|
170920
|
-
this.state.airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170921
|
-
}
|
|
170922
|
-
logger234.debug("Pm25AirQualityServer: before super.initialize()");
|
|
170923
|
-
await super.initialize();
|
|
170924
|
-
logger234.debug("Pm25AirQualityServer: after super.initialize()");
|
|
170925
|
-
const homeAssistant = await this.agent.load(HomeAssistantEntityBehavior);
|
|
170926
|
-
this.update(homeAssistant.entity);
|
|
170927
|
-
this.reactTo(homeAssistant.onChange, this.update, { offline: true });
|
|
170928
|
-
}
|
|
170929
|
-
update(entity) {
|
|
170930
|
-
const state = entity.state.state;
|
|
170931
|
-
let airQuality = AirQuality3.AirQualityEnum.Unknown;
|
|
170932
|
-
if (state != null && !Number.isNaN(+state)) {
|
|
170933
|
-
const value = +state;
|
|
170934
|
-
if (value <= 12) {
|
|
170935
|
-
airQuality = AirQuality3.AirQualityEnum.Good;
|
|
170936
|
-
} else if (value <= 35) {
|
|
170937
|
-
airQuality = AirQuality3.AirQualityEnum.Fair;
|
|
170938
|
-
} else if (value <= 55) {
|
|
170939
|
-
airQuality = AirQuality3.AirQualityEnum.Moderate;
|
|
170940
|
-
} else if (value <= 150) {
|
|
170941
|
-
airQuality = AirQuality3.AirQualityEnum.Poor;
|
|
170942
|
-
} else if (value <= 250) {
|
|
170943
|
-
airQuality = AirQuality3.AirQualityEnum.VeryPoor;
|
|
170944
|
-
} else {
|
|
170945
|
-
airQuality = AirQuality3.AirQualityEnum.ExtremelyPoor;
|
|
170946
|
-
}
|
|
170947
|
-
}
|
|
170948
|
-
applyPatchState(this.state, { airQuality });
|
|
170949
|
-
}
|
|
170950
|
-
};
|
|
170951
|
-
var Pm25SensorType = AirQualitySensorDevice.with(
|
|
170952
|
-
BasicInformationServer2,
|
|
170953
|
-
IdentifyServer2,
|
|
170954
|
-
HomeAssistantEntityBehavior,
|
|
170955
|
-
Pm25AirQualityServer,
|
|
170956
|
-
Pm25ConcentrationMeasurementServer2
|
|
170957
|
-
);
|
|
170958
|
-
|
|
170959
171005
|
// src/matter/endpoints/legacy/sensor/devices/temperature-humidity-sensor.ts
|
|
170960
171006
|
init_home_assistant_entity_behavior();
|
|
170961
171007
|
var temperatureConfig3 = {
|
|
@@ -174410,6 +174456,9 @@ var matterDeviceTypeFactories = {
|
|
|
174410
174456
|
formaldehyde_sensor: (ha) => FormaldehydeSensorType.set({ homeAssistantEntity: ha }),
|
|
174411
174457
|
radon_sensor: (ha) => RadonSensorType.set({ homeAssistantEntity: ha }),
|
|
174412
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 }),
|
|
174413
174462
|
electrical_meter: (ha) => ElectricalMeterType.set({ homeAssistantEntity: ha }),
|
|
174414
174463
|
// Legacy SolarPower alias, kept for existing mappings.
|
|
174415
174464
|
electrical_sensor: (ha) => ElectricalSensorType.set({ homeAssistantEntity: ha }),
|
|
@@ -179290,20 +179339,9 @@ var BridgeEndpointManager = class extends Service {
|
|
|
179290
179339
|
|
|
179291
179340
|
// src/services/bridges/server-mode-bridge.ts
|
|
179292
179341
|
init_dist();
|
|
179293
|
-
init_esm7();
|
|
179294
|
-
import * as os10 from "node:os";
|
|
179295
179342
|
init_protocol3();
|
|
179296
179343
|
init_diagnostic_event_bus();
|
|
179297
|
-
var imWrapMarker2 = /* @__PURE__ */ Symbol("hamh.wedgeImWrap");
|
|
179298
|
-
var commandMessageTypes2 = [
|
|
179299
|
-
MessageType.ReadRequest,
|
|
179300
|
-
MessageType.WriteRequest,
|
|
179301
|
-
MessageType.InvokeRequest,
|
|
179302
|
-
MessageType.TimedRequest
|
|
179303
|
-
];
|
|
179304
179344
|
var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
|
|
179305
|
-
var SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2 = 2500;
|
|
179306
|
-
var MDNS_ADDRESS_CHECK_INTERVAL_MS2 = 6e4;
|
|
179307
179345
|
function makeWarmStartState(state, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
179308
179346
|
return { ...state, last_updated: now };
|
|
179309
179347
|
}
|
|
@@ -179313,11 +179351,19 @@ var ServerModeBridge = class {
|
|
|
179313
179351
|
this.endpointManager = endpointManager;
|
|
179314
179352
|
this.server = server;
|
|
179315
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
|
+
);
|
|
179316
179361
|
}
|
|
179317
179362
|
dataProvider;
|
|
179318
179363
|
endpointManager;
|
|
179319
179364
|
server;
|
|
179320
179365
|
log;
|
|
179366
|
+
sessions;
|
|
179321
179367
|
status = {
|
|
179322
179368
|
code: BridgeStatus.Stopped,
|
|
179323
179369
|
reason: void 0
|
|
@@ -179326,46 +179372,9 @@ var ServerModeBridge = class {
|
|
|
179326
179372
|
// broadcast updates via WebSocket so the frontend sees every transition.
|
|
179327
179373
|
onStatusChange;
|
|
179328
179374
|
autoForceSyncTimer = null;
|
|
179329
|
-
deadSessionTimer = null;
|
|
179330
|
-
staleSessionTimers = /* @__PURE__ */ new Map();
|
|
179331
179375
|
warmStartTimer = null;
|
|
179332
|
-
// Tracks when each session opened, used for age-based rotation (#287).
|
|
179333
|
-
sessionStartedAt = /* @__PURE__ */ new Map();
|
|
179334
|
-
rotationTimer = null;
|
|
179335
|
-
maxSessionAgeMs = 0;
|
|
179336
|
-
// Wedge watchdog: last inbound Interaction Model request time per session and
|
|
179337
|
-
// last time the watchdog rotated it, both keyed by the long-lived session
|
|
179338
|
-
// object so they clear when the session goes away.
|
|
179339
|
-
lastImRequestAt = /* @__PURE__ */ new WeakMap();
|
|
179340
|
-
wedgeLastRotatedAt = /* @__PURE__ */ new WeakMap();
|
|
179341
|
-
wedgeWatchdogTimer = null;
|
|
179342
|
-
// V2 shadow signals (#365): last command-class request per session, plus
|
|
179343
|
-
// rings of subscribe and delivery give-up times (newest WEDGE_RING_SIZE).
|
|
179344
|
-
lastCommandImAt = /* @__PURE__ */ new WeakMap();
|
|
179345
|
-
subscribeTimesMs = /* @__PURE__ */ new WeakMap();
|
|
179346
|
-
giveUpTimesMs = /* @__PURE__ */ new WeakMap();
|
|
179347
|
-
// Sessions whose subscriptions.deleted we already hooked for give-ups.
|
|
179348
|
-
wedgeHookedSessions = /* @__PURE__ */ new WeakSet();
|
|
179349
|
-
// Watches the advertised interface addresses so a dynamic ISP IPv6 prefix
|
|
179350
|
-
// change forces a fresh operational announcement (#415).
|
|
179351
|
-
mdnsAddressTimer = null;
|
|
179352
|
-
advertisedAddressSnapshot = [];
|
|
179353
|
-
// Skip a tick while the previous one is still awaiting refresh, so a slow
|
|
179354
|
-
// refreshOperationalAdvertisement can't overlap and re-announce off a stale
|
|
179355
|
-
// snapshot.
|
|
179356
|
-
mdnsCheckInFlight = false;
|
|
179357
179376
|
// Tracks the last synced state JSON per entity to avoid pushing unchanged states.
|
|
179358
179377
|
lastSyncedStates = /* @__PURE__ */ new Map();
|
|
179359
|
-
// Session lifecycle diagnostic handlers (non-destructive, logging only).
|
|
179360
|
-
// biome-ignore lint/suspicious/noExplicitAny: matter.js internal types
|
|
179361
|
-
sessionDiagHandler;
|
|
179362
|
-
// biome-ignore lint/suspicious/noExplicitAny: matter.js internal types
|
|
179363
|
-
sessionAddedHandler;
|
|
179364
|
-
// biome-ignore lint/suspicious/noExplicitAny: matter.js internal types
|
|
179365
|
-
sessionDeletedHandler;
|
|
179366
|
-
// Warns when Alexa pairs on a non-5540 port, the fabric rolls back later (#401).
|
|
179367
|
-
// biome-ignore lint/suspicious/noExplicitAny: matter.js internal types
|
|
179368
|
-
fabricAddedHandler;
|
|
179369
179378
|
get id() {
|
|
179370
179379
|
return this.dataProvider.id;
|
|
179371
179380
|
}
|
|
@@ -179401,81 +179410,7 @@ var ServerModeBridge = class {
|
|
|
179401
179410
|
return out;
|
|
179402
179411
|
}
|
|
179403
179412
|
getSessionInfo() {
|
|
179404
|
-
|
|
179405
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
179406
|
-
const sessions = [...sessionManager.sessions];
|
|
179407
|
-
let totalSubscriptions = 0;
|
|
179408
|
-
const fabricMap = /* @__PURE__ */ new Map();
|
|
179409
|
-
const sessionList = sessions.map((s) => {
|
|
179410
|
-
const subCount = s.subscriptions.size;
|
|
179411
|
-
totalSubscriptions += subCount;
|
|
179412
|
-
const subscriptions = summarizeSubscriptions(s.subscriptions);
|
|
179413
|
-
const fi = typeof s.fabric?.fabricIndex === "number" ? s.fabric.fabricIndex : null;
|
|
179414
|
-
if (fi !== null) {
|
|
179415
|
-
const existing = fabricMap.get(fi) ?? {
|
|
179416
|
-
sessions: 0,
|
|
179417
|
-
subscriptions: 0
|
|
179418
|
-
};
|
|
179419
|
-
existing.sessions++;
|
|
179420
|
-
existing.subscriptions += subCount;
|
|
179421
|
-
fabricMap.set(fi, existing);
|
|
179422
|
-
}
|
|
179423
|
-
const nowMs = Date.now();
|
|
179424
|
-
const lastActiveMsAgo = typeof s.activeTimestamp === "number" && s.activeTimestamp > 0 ? nowMs - s.activeTimestamp : null;
|
|
179425
|
-
const lastAnyActivityMsAgo = typeof s.timestamp === "number" ? nowMs - s.timestamp : null;
|
|
179426
|
-
const lastImAt = this.lastImRequestAt.get(s);
|
|
179427
|
-
const lastImRequestMsAgo = lastImAt != null ? nowMs - lastImAt : null;
|
|
179428
|
-
const startedAt = this.sessionStartedAt.get(s.id);
|
|
179429
|
-
const lastCmdAt = this.lastCommandImAt.get(s);
|
|
179430
|
-
const lastCommandImRequestMsAgo = lastCmdAt != null ? nowMs - lastCmdAt : null;
|
|
179431
|
-
const subscribeTimes = this.subscribeTimesMs.get(s) ?? [];
|
|
179432
|
-
const giveUpTimes = this.giveUpTimesMs.get(s) ?? [];
|
|
179433
|
-
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
179434
|
-
const wedgeV2WouldRotate = decideWedgeRotationV2({
|
|
179435
|
-
subscriptionCount: subCount,
|
|
179436
|
-
sessionAgeMs: startedAt != null ? nowMs - startedAt : 0,
|
|
179437
|
-
commandSilenceMs: lastCommandImRequestMsAgo,
|
|
179438
|
-
subscribeTimesMs: subscribeTimes,
|
|
179439
|
-
giveUpTimesMs: giveUpTimes,
|
|
179440
|
-
nowMs,
|
|
179441
|
-
lastRotatedMsAgo: lastRotatedAt != null ? nowMs - lastRotatedAt : null
|
|
179442
|
-
});
|
|
179443
|
-
return {
|
|
179444
|
-
id: s.id,
|
|
179445
|
-
peerNodeId: String(s.peerNodeId),
|
|
179446
|
-
fabricIndex: fi,
|
|
179447
|
-
subscriptionCount: subCount,
|
|
179448
|
-
subscriptions,
|
|
179449
|
-
lastActiveMsAgo,
|
|
179450
|
-
lastAnyActivityMsAgo,
|
|
179451
|
-
lastImRequestMsAgo,
|
|
179452
|
-
lastCommandImRequestMsAgo,
|
|
179453
|
-
subscribesLast30Min: countRecent(subscribeTimes, nowMs),
|
|
179454
|
-
giveUpsLast30Min: countRecent(giveUpTimes, nowMs),
|
|
179455
|
-
wedgeV2WouldRotate,
|
|
179456
|
-
isPeerActive: Boolean(s.isPeerActive),
|
|
179457
|
-
ageMsFromOpen: startedAt != null ? nowMs - startedAt : null
|
|
179458
|
-
};
|
|
179459
|
-
});
|
|
179460
|
-
const fabrics = [...fabricMap.entries()].map(([fabricIndex, data]) => ({
|
|
179461
|
-
fabricIndex,
|
|
179462
|
-
sessions: data.sessions,
|
|
179463
|
-
subscriptions: data.subscriptions
|
|
179464
|
-
}));
|
|
179465
|
-
return {
|
|
179466
|
-
sessions: sessionList,
|
|
179467
|
-
totalSessions: sessions.length,
|
|
179468
|
-
totalSubscriptions,
|
|
179469
|
-
fabrics
|
|
179470
|
-
};
|
|
179471
|
-
} catch {
|
|
179472
|
-
return {
|
|
179473
|
-
sessions: [],
|
|
179474
|
-
totalSessions: 0,
|
|
179475
|
-
totalSubscriptions: 0,
|
|
179476
|
-
fabrics: []
|
|
179477
|
-
};
|
|
179478
|
-
}
|
|
179413
|
+
return this.sessions.getSessionInfo();
|
|
179479
179414
|
}
|
|
179480
179415
|
async initialize() {
|
|
179481
179416
|
await this.server.construction.ready.then();
|
|
@@ -179525,11 +179460,7 @@ var ServerModeBridge = class {
|
|
|
179525
179460
|
this.log.warn("Startup force sync failed:", e);
|
|
179526
179461
|
});
|
|
179527
179462
|
}
|
|
179528
|
-
this.
|
|
179529
|
-
this.wireFabricWarnings();
|
|
179530
|
-
this.startSessionRotation();
|
|
179531
|
-
this.startWedgeWatchdog();
|
|
179532
|
-
this.startMdnsAddressWatch();
|
|
179463
|
+
this.sessions.start();
|
|
179533
179464
|
this.scheduleWarmStart();
|
|
179534
179465
|
logMemoryUsage(this.log, "server mode bridge running");
|
|
179535
179466
|
this.log.info("Server mode bridge started");
|
|
@@ -179545,14 +179476,10 @@ ${e?.toString()}`);
|
|
|
179545
179476
|
}
|
|
179546
179477
|
}
|
|
179547
179478
|
async stop(code = BridgeStatus.Stopped, reason = "Manually stopped") {
|
|
179548
|
-
this.
|
|
179549
|
-
this.stopWedgeWatchdog();
|
|
179550
|
-
this.stopMdnsAddressWatch();
|
|
179551
|
-
this.unwireSessionDiagnostics();
|
|
179552
|
-
this.unwireFabricWarnings();
|
|
179479
|
+
this.sessions.stop();
|
|
179553
179480
|
this.cancelWarmStart();
|
|
179554
179481
|
this.stopAutoForceSync();
|
|
179555
|
-
await this.closeActiveSessions();
|
|
179482
|
+
await this.sessions.closeActiveSessions();
|
|
179556
179483
|
this.endpointManager.stopObserving();
|
|
179557
179484
|
try {
|
|
179558
179485
|
await this.server.cancel();
|
|
@@ -179578,8 +179505,7 @@ ${e?.toString()}`);
|
|
|
179578
179505
|
await this.refreshDevices();
|
|
179579
179506
|
if (this.status.code === BridgeStatus.Running) {
|
|
179580
179507
|
this.startAutoForceSyncIfEnabled();
|
|
179581
|
-
this.
|
|
179582
|
-
this.startWedgeWatchdog();
|
|
179508
|
+
this.sessions.reconfigure();
|
|
179583
179509
|
}
|
|
179584
179510
|
} catch (e) {
|
|
179585
179511
|
const reason = "Failed to update server mode bridge due to error:";
|
|
@@ -179622,677 +179548,6 @@ ${e?.toString()}`);
|
|
|
179622
179548
|
}, AUTO_FORCE_SYNC_INTERVAL_MS2);
|
|
179623
179549
|
this.log.info(`Force sync: every ${AUTO_FORCE_SYNC_INTERVAL_MS2 / 1e3}s`);
|
|
179624
179550
|
}
|
|
179625
|
-
wireSessionDiagnostics() {
|
|
179626
|
-
this.unwireSessionDiagnostics();
|
|
179627
|
-
try {
|
|
179628
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
179629
|
-
this.sessionDiagHandler = (session) => {
|
|
179630
|
-
const sessions = [...sessionManager.sessions];
|
|
179631
|
-
let totalSubs = 0;
|
|
179632
|
-
for (const s of sessions) {
|
|
179633
|
-
totalSubs += s.subscriptions.size;
|
|
179634
|
-
}
|
|
179635
|
-
this.log.debug(
|
|
179636
|
-
`Session ${session.id} (peer ${session.peerNodeId}): subscriptions=${session.subscriptions.size} | total: sessions=${sessions.length} subscriptions=${totalSubs}`
|
|
179637
|
-
);
|
|
179638
|
-
diagnosticEventBus.emit(
|
|
179639
|
-
"subscription_changed",
|
|
179640
|
-
`Session ${session.id}: ${session.subscriptions.size} subs (total ${totalSubs})`,
|
|
179641
|
-
{
|
|
179642
|
-
bridgeId: this.data.id,
|
|
179643
|
-
bridgeName: this.data.name,
|
|
179644
|
-
details: {
|
|
179645
|
-
sessionId: session.id,
|
|
179646
|
-
sessionSubs: session.subscriptions.size,
|
|
179647
|
-
totalSessions: sessions.length,
|
|
179648
|
-
totalSubs
|
|
179649
|
-
}
|
|
179650
|
-
}
|
|
179651
|
-
);
|
|
179652
|
-
if (totalSubs === 0 && sessions.length > 0) {
|
|
179653
|
-
this.log.warn(
|
|
179654
|
-
`All subscriptions lost, ${sessions.length} session(s) still active, waiting for controller to re-subscribe`
|
|
179655
|
-
);
|
|
179656
|
-
if (!this.deadSessionTimer) {
|
|
179657
|
-
const timeoutMs = deadSessionTimeoutMs(
|
|
179658
|
-
this.dataProvider.featureFlags
|
|
179659
|
-
);
|
|
179660
|
-
this.deadSessionTimer = setTimeout(() => {
|
|
179661
|
-
this.deadSessionTimer = null;
|
|
179662
|
-
this.closeDeadSessions();
|
|
179663
|
-
}, timeoutMs);
|
|
179664
|
-
this.log.info(
|
|
179665
|
-
`Scheduled dead session cleanup in ${timeoutMs / 1e3}s`
|
|
179666
|
-
);
|
|
179667
|
-
}
|
|
179668
|
-
} else if (totalSubs > 0 && this.deadSessionTimer) {
|
|
179669
|
-
clearTimeout(this.deadSessionTimer);
|
|
179670
|
-
this.deadSessionTimer = null;
|
|
179671
|
-
this.log.info(
|
|
179672
|
-
"Subscriptions recovered, canceled dead session cleanup"
|
|
179673
|
-
);
|
|
179674
|
-
}
|
|
179675
|
-
if (session.subscriptions.size === 0 && !this.staleSessionTimers.has(session.id)) {
|
|
179676
|
-
this.staleSessionTimers.set(
|
|
179677
|
-
session.id,
|
|
179678
|
-
setTimeout(() => {
|
|
179679
|
-
this.staleSessionTimers.delete(session.id);
|
|
179680
|
-
this.closeStaleSession(session.id);
|
|
179681
|
-
}, deadSessionTimeoutMs(this.dataProvider.featureFlags))
|
|
179682
|
-
);
|
|
179683
|
-
} else if (session.subscriptions.size > 0 && this.staleSessionTimers.has(session.id)) {
|
|
179684
|
-
clearTimeout(this.staleSessionTimers.get(session.id));
|
|
179685
|
-
this.staleSessionTimers.delete(session.id);
|
|
179686
|
-
}
|
|
179687
|
-
};
|
|
179688
|
-
sessionManager.subscriptionsChanged.on(this.sessionDiagHandler);
|
|
179689
|
-
this.sessionAddedHandler = (newSession) => {
|
|
179690
|
-
this.sessionStartedAt.set(newSession.id, Date.now());
|
|
179691
|
-
hookGiveUpsWithPeers(newSession);
|
|
179692
|
-
this.log.info(
|
|
179693
|
-
`Session opened: id=${newSession.id} peer=${newSession.peerNodeId}`
|
|
179694
|
-
);
|
|
179695
|
-
diagnosticEventBus.emit(
|
|
179696
|
-
"session_opened",
|
|
179697
|
-
`Session ${newSession.id} opened (peer ${newSession.peerNodeId})`,
|
|
179698
|
-
{
|
|
179699
|
-
bridgeId: this.data.id,
|
|
179700
|
-
bridgeName: this.data.name,
|
|
179701
|
-
details: { sessionId: newSession.id }
|
|
179702
|
-
}
|
|
179703
|
-
);
|
|
179704
|
-
for (const s of [...sessionManager.sessions]) {
|
|
179705
|
-
if (s !== newSession && !s.isClosing && s.peerNodeId === newSession.peerNodeId && s.fabric?.fabricIndex === newSession.fabric?.fabricIndex && s.subscriptions.size === 0) {
|
|
179706
|
-
this.log.info(
|
|
179707
|
-
`Session ${s.id} (peer ${s.peerNodeId}, 0 subs) replaced by session ${newSession.id}, closing it once it goes quiet`
|
|
179708
|
-
);
|
|
179709
|
-
const staleId = s.id;
|
|
179710
|
-
const armed = this.staleSessionTimers.get(staleId);
|
|
179711
|
-
if (armed) clearTimeout(armed);
|
|
179712
|
-
this.staleSessionTimers.set(
|
|
179713
|
-
staleId,
|
|
179714
|
-
setTimeout(() => {
|
|
179715
|
-
this.staleSessionTimers.delete(staleId);
|
|
179716
|
-
this.closeStaleSession(staleId, PRIMING_GRACE_MS);
|
|
179717
|
-
}, replacedSessionTimeoutMs(this.dataProvider.featureFlags))
|
|
179718
|
-
);
|
|
179719
|
-
}
|
|
179720
|
-
}
|
|
179721
|
-
const quietWindowMs = staleSessionQuietWindowMs(
|
|
179722
|
-
this.dataProvider.featureFlags
|
|
179723
|
-
);
|
|
179724
|
-
const now = Date.now();
|
|
179725
|
-
const supersededIds = new Set(
|
|
179726
|
-
selectSupersededSessions(
|
|
179727
|
-
[...sessionManager.sessions],
|
|
179728
|
-
newSession,
|
|
179729
|
-
quietWindowMs,
|
|
179730
|
-
now
|
|
179731
|
-
)
|
|
179732
|
-
);
|
|
179733
|
-
const closes = [];
|
|
179734
|
-
for (const s of [...sessionManager.sessions]) {
|
|
179735
|
-
if (!supersededIds.has(s.id) || s.subscriptions.size === 0) continue;
|
|
179736
|
-
const quietSec = Math.round((now - s.timestamp) / 1e3);
|
|
179737
|
-
this.log.info(
|
|
179738
|
-
`Closing superseded session ${s.id} (peer ${s.peerNodeId}, ${s.subscriptions.size} subs, quiet ${quietSec}s), replaced by session ${newSession.id}`
|
|
179739
|
-
);
|
|
179740
|
-
closes.push(
|
|
179741
|
-
s.initiateClose().catch(
|
|
179742
|
-
() => s.initiateForceClose({
|
|
179743
|
-
cause: new Error("superseded session, forcing")
|
|
179744
|
-
})
|
|
179745
|
-
)
|
|
179746
|
-
);
|
|
179747
|
-
}
|
|
179748
|
-
if (closes.length > 0) {
|
|
179749
|
-
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
179750
|
-
}
|
|
179751
|
-
};
|
|
179752
|
-
this.sessionDeletedHandler = (session) => {
|
|
179753
|
-
this.sessionStartedAt.delete(session.id);
|
|
179754
|
-
const armed = this.staleSessionTimers.get(session.id);
|
|
179755
|
-
if (armed) {
|
|
179756
|
-
clearTimeout(armed);
|
|
179757
|
-
this.staleSessionTimers.delete(session.id);
|
|
179758
|
-
}
|
|
179759
|
-
const sessions = [...sessionManager.sessions];
|
|
179760
|
-
this.log.warn(
|
|
179761
|
-
`Session closed: id=${session.id} peer=${session.peerNodeId} | remaining sessions=${sessions.length}`
|
|
179762
|
-
);
|
|
179763
|
-
diagnosticEventBus.emit(
|
|
179764
|
-
"session_closed",
|
|
179765
|
-
`Session ${session.id} closed (peer ${session.peerNodeId})`,
|
|
179766
|
-
{
|
|
179767
|
-
bridgeId: this.data.id,
|
|
179768
|
-
bridgeName: this.data.name,
|
|
179769
|
-
details: {
|
|
179770
|
-
sessionId: session.id,
|
|
179771
|
-
remainingSessions: sessions.length
|
|
179772
|
-
}
|
|
179773
|
-
}
|
|
179774
|
-
);
|
|
179775
|
-
};
|
|
179776
|
-
sessionManager.sessions.added.on(this.sessionAddedHandler);
|
|
179777
|
-
sessionManager.sessions.deleted.on(this.sessionDeletedHandler);
|
|
179778
|
-
seedExistingSessionStarts(this.sessionStartedAt, sessionManager.sessions);
|
|
179779
|
-
const hookGiveUpsWithPeers = (sess) => this.hookSubscriptionGiveUps(
|
|
179780
|
-
sess,
|
|
179781
|
-
() => [...sessionManager.sessions].filter(
|
|
179782
|
-
(s) => s.peerNodeId === sess.peerNodeId && s.fabric?.fabricIndex === sess.fabric?.fabricIndex
|
|
179783
|
-
)
|
|
179784
|
-
);
|
|
179785
|
-
for (const sess of sessionManager.sessions) {
|
|
179786
|
-
hookGiveUpsWithPeers(sess);
|
|
179787
|
-
}
|
|
179788
|
-
this.wireImRequestTracking();
|
|
179789
|
-
} catch {
|
|
179790
|
-
}
|
|
179791
|
-
}
|
|
179792
|
-
// Stamp the time of every inbound Interaction Model request per session by
|
|
179793
|
-
// wrapping InteractionServer.onNewExchange. The wedge watchdog reads these to
|
|
179794
|
-
// tell a live-but-consuming controller from one that only keeps acking.
|
|
179795
|
-
wireImRequestTracking() {
|
|
179796
|
-
try {
|
|
179797
|
-
const is = this.server.env.get(InteractionServer);
|
|
179798
|
-
const marked = is;
|
|
179799
|
-
if (marked[imWrapMarker2]) {
|
|
179800
|
-
return;
|
|
179801
|
-
}
|
|
179802
|
-
const original = is.onNewExchange.bind(is);
|
|
179803
|
-
is.onNewExchange = (exchange, message) => {
|
|
179804
|
-
const session = exchange.session;
|
|
179805
|
-
if (session) {
|
|
179806
|
-
const now = Date.now();
|
|
179807
|
-
this.lastImRequestAt.set(session, now);
|
|
179808
|
-
const type = message?.payloadHeader?.messageType;
|
|
179809
|
-
if (type === MessageType.SubscribeRequest) {
|
|
179810
|
-
this.pushWedgeRing(this.subscribeTimesMs, session, now);
|
|
179811
|
-
} else if (type != null && commandMessageTypes2.includes(type)) {
|
|
179812
|
-
this.lastCommandImAt.set(session, now);
|
|
179813
|
-
}
|
|
179814
|
-
}
|
|
179815
|
-
return original(exchange, message);
|
|
179816
|
-
};
|
|
179817
|
-
marked[imWrapMarker2] = true;
|
|
179818
|
-
} catch {
|
|
179819
|
-
}
|
|
179820
|
-
}
|
|
179821
|
-
// Keep only the newest WEDGE_RING_SIZE timestamps per session.
|
|
179822
|
-
pushWedgeRing(ring, session, now) {
|
|
179823
|
-
const times = ring.get(session) ?? [];
|
|
179824
|
-
times.push(now);
|
|
179825
|
-
if (times.length > WEDGE_RING_SIZE) {
|
|
179826
|
-
times.splice(0, times.length - WEDGE_RING_SIZE);
|
|
179827
|
-
}
|
|
179828
|
-
ring.set(session, times);
|
|
179829
|
-
}
|
|
179830
|
-
// Watch this session's subscriptions for server-side delivery give-ups
|
|
179831
|
-
// (#365 v2 shadow). isTerminated true fires both on a peer cancel and on
|
|
179832
|
-
// the 3-strikes delivery give-up; only the give-up arrives without a
|
|
179833
|
-
// coincident inbound IM request, so that is the discriminator.
|
|
179834
|
-
hookSubscriptionGiveUps(session, peerSessions) {
|
|
179835
|
-
const key = session;
|
|
179836
|
-
if (this.wedgeHookedSessions.has(key)) {
|
|
179837
|
-
return;
|
|
179838
|
-
}
|
|
179839
|
-
const deleted = session.subscriptions?.deleted;
|
|
179840
|
-
if (typeof deleted?.on !== "function") {
|
|
179841
|
-
return;
|
|
179842
|
-
}
|
|
179843
|
-
this.wedgeHookedSessions.add(key);
|
|
179844
|
-
deleted.on((sub) => {
|
|
179845
|
-
if (sub?.isTerminated !== true) {
|
|
179846
|
-
return;
|
|
179847
|
-
}
|
|
179848
|
-
const now = Date.now();
|
|
179849
|
-
const fresh = (s) => {
|
|
179850
|
-
const at = this.lastImRequestAt.get(s);
|
|
179851
|
-
return at != null && now - at <= WEDGE_GIVE_UP_QUIET_MS;
|
|
179852
|
-
};
|
|
179853
|
-
if (fresh(key)) {
|
|
179854
|
-
return;
|
|
179855
|
-
}
|
|
179856
|
-
for (const s of peerSessions?.() ?? []) {
|
|
179857
|
-
if (s !== key && fresh(s)) {
|
|
179858
|
-
return;
|
|
179859
|
-
}
|
|
179860
|
-
}
|
|
179861
|
-
this.pushWedgeRing(this.giveUpTimesMs, key, now);
|
|
179862
|
-
this.log.debug(
|
|
179863
|
-
`wedge v2 give-up recorded sub=${sub?.subscriptionId}`
|
|
179864
|
-
);
|
|
179865
|
-
});
|
|
179866
|
-
}
|
|
179867
|
-
closeStaleSession(sessionId, minQuietMs = 0) {
|
|
179868
|
-
try {
|
|
179869
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
179870
|
-
for (const s of [...sessionManager.sessions]) {
|
|
179871
|
-
if (s.id !== sessionId || s.isClosing || s.subscriptions.size > 0) {
|
|
179872
|
-
continue;
|
|
179873
|
-
}
|
|
179874
|
-
const idleSec = Math.round((Date.now() - s.timestamp) / 1e3);
|
|
179875
|
-
if (!staleSessionShouldClose(
|
|
179876
|
-
s,
|
|
179877
|
-
Math.max(
|
|
179878
|
-
staleSessionQuietWindowMs(this.dataProvider.featureFlags),
|
|
179879
|
-
minQuietMs
|
|
179880
|
-
)
|
|
179881
|
-
)) {
|
|
179882
|
-
this.log.info(
|
|
179883
|
-
`Keeping session ${s.id} (peer ${s.peerNodeId}, 0 subs, last traffic ${idleSec}s ago)`
|
|
179884
|
-
);
|
|
179885
|
-
this.staleSessionTimers.set(
|
|
179886
|
-
sessionId,
|
|
179887
|
-
setTimeout(() => {
|
|
179888
|
-
this.staleSessionTimers.delete(sessionId);
|
|
179889
|
-
this.closeStaleSession(sessionId, minQuietMs);
|
|
179890
|
-
}, deadSessionTimeoutMs(this.dataProvider.featureFlags))
|
|
179891
|
-
);
|
|
179892
|
-
break;
|
|
179893
|
-
}
|
|
179894
|
-
this.log.warn(
|
|
179895
|
-
`Closing stale session ${s.id} (peer ${s.peerNodeId}, no subscriptions for ${deadSessionTimeoutMs(this.dataProvider.featureFlags) / 1e3}s, no traffic for ${idleSec}s)`
|
|
179896
|
-
);
|
|
179897
|
-
s.initiateClose().catch(() => {
|
|
179898
|
-
return s.initiateForceClose({
|
|
179899
|
-
cause: new Error("graceful close failed, forcing")
|
|
179900
|
-
});
|
|
179901
|
-
}).catch(() => {
|
|
179902
|
-
}).finally(() => this.triggerMdnsReAnnounce());
|
|
179903
|
-
break;
|
|
179904
|
-
}
|
|
179905
|
-
} catch {
|
|
179906
|
-
}
|
|
179907
|
-
}
|
|
179908
|
-
closeDeadSessions() {
|
|
179909
|
-
try {
|
|
179910
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
179911
|
-
const sessions = [...sessionManager.sessions];
|
|
179912
|
-
const closes = [];
|
|
179913
|
-
let kept = 0;
|
|
179914
|
-
for (const s of sessions) {
|
|
179915
|
-
if (s.isClosing || s.subscriptions.size > 0) {
|
|
179916
|
-
continue;
|
|
179917
|
-
}
|
|
179918
|
-
if (this.staleSessionTimers.has(s.id)) {
|
|
179919
|
-
continue;
|
|
179920
|
-
}
|
|
179921
|
-
const idleSec = Math.round((Date.now() - s.timestamp) / 1e3);
|
|
179922
|
-
if (!staleSessionShouldClose(
|
|
179923
|
-
s,
|
|
179924
|
-
staleSessionQuietWindowMs(this.dataProvider.featureFlags)
|
|
179925
|
-
)) {
|
|
179926
|
-
this.log.info(
|
|
179927
|
-
`Keeping session ${s.id} (peer ${s.peerNodeId}, 0 subs, last traffic ${idleSec}s ago)`
|
|
179928
|
-
);
|
|
179929
|
-
kept++;
|
|
179930
|
-
continue;
|
|
179931
|
-
}
|
|
179932
|
-
this.log.warn(
|
|
179933
|
-
`Closing dead session ${s.id} (peer ${s.peerNodeId}, no subscriptions for ${deadSessionTimeoutMs(this.dataProvider.featureFlags) / 1e3}s, no traffic for ${idleSec}s)`
|
|
179934
|
-
);
|
|
179935
|
-
closes.push(
|
|
179936
|
-
s.initiateClose().catch(() => {
|
|
179937
|
-
return s.initiateForceClose({
|
|
179938
|
-
cause: new Error("graceful close failed, forcing")
|
|
179939
|
-
});
|
|
179940
|
-
})
|
|
179941
|
-
);
|
|
179942
|
-
}
|
|
179943
|
-
if (kept > 0 && !this.deadSessionTimer) {
|
|
179944
|
-
this.deadSessionTimer = setTimeout(() => {
|
|
179945
|
-
this.deadSessionTimer = null;
|
|
179946
|
-
this.closeDeadSessions();
|
|
179947
|
-
}, deadSessionTimeoutMs(this.dataProvider.featureFlags));
|
|
179948
|
-
}
|
|
179949
|
-
if (closes.length > 0) {
|
|
179950
|
-
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
179951
|
-
}
|
|
179952
|
-
} catch {
|
|
179953
|
-
}
|
|
179954
|
-
}
|
|
179955
|
-
// Close every active session on shutdown so each controller is told to drop
|
|
179956
|
-
// its CASE session instead of being left with a stale one. Mirrors the
|
|
179957
|
-
// dead-session close, but covers all sessions and waits (capped) so the
|
|
179958
|
-
// close actually reaches the peer before the server is canceled.
|
|
179959
|
-
async closeActiveSessions() {
|
|
179960
|
-
try {
|
|
179961
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
179962
|
-
const closes = [];
|
|
179963
|
-
for (const s of [...sessionManager.sessions]) {
|
|
179964
|
-
if (s.isClosing) {
|
|
179965
|
-
continue;
|
|
179966
|
-
}
|
|
179967
|
-
closes.push(
|
|
179968
|
-
s.initiateClose().catch(() => {
|
|
179969
|
-
return s.initiateForceClose({
|
|
179970
|
-
cause: new Error("graceful close failed, forcing")
|
|
179971
|
-
});
|
|
179972
|
-
})
|
|
179973
|
-
);
|
|
179974
|
-
}
|
|
179975
|
-
if (closes.length === 0) {
|
|
179976
|
-
return;
|
|
179977
|
-
}
|
|
179978
|
-
this.log.info(`Closing ${closes.length} active session(s) on shutdown`);
|
|
179979
|
-
let timer;
|
|
179980
|
-
const timeout = new Promise((resolve11) => {
|
|
179981
|
-
timer = setTimeout(resolve11, SHUTDOWN_SESSION_CLOSE_TIMEOUT_MS2);
|
|
179982
|
-
});
|
|
179983
|
-
try {
|
|
179984
|
-
await Promise.race([Promise.allSettled(closes), timeout]);
|
|
179985
|
-
} finally {
|
|
179986
|
-
clearTimeout(timer);
|
|
179987
|
-
}
|
|
179988
|
-
} catch {
|
|
179989
|
-
}
|
|
179990
|
-
}
|
|
179991
|
-
/**
|
|
179992
|
-
* Force a fresh mDNS operational advertisement after session cleanup.
|
|
179993
|
-
* matter.js DeviceAdvertiser only re-announces when a subscription is
|
|
179994
|
-
* canceled BY THE PEER. When the server cancels after 3 delivery
|
|
179995
|
-
* timeouts, no re-announcement happens and the controller may not
|
|
179996
|
-
* realize it should reconnect (#266).
|
|
179997
|
-
*/
|
|
179998
|
-
triggerMdnsReAnnounce() {
|
|
179999
|
-
try {
|
|
180000
|
-
const advertiser = this.server.env.get(DeviceAdvertiser);
|
|
180001
|
-
advertiser.restartAdvertisement();
|
|
180002
|
-
this.log.info("Triggered mDNS re-announcement after session cleanup");
|
|
180003
|
-
} catch {
|
|
180004
|
-
}
|
|
180005
|
-
}
|
|
180006
|
-
unwireSessionDiagnostics() {
|
|
180007
|
-
try {
|
|
180008
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
180009
|
-
if (this.sessionDiagHandler) {
|
|
180010
|
-
sessionManager.subscriptionsChanged.off(this.sessionDiagHandler);
|
|
180011
|
-
}
|
|
180012
|
-
if (this.sessionAddedHandler) {
|
|
180013
|
-
sessionManager.sessions.added.off(this.sessionAddedHandler);
|
|
180014
|
-
}
|
|
180015
|
-
if (this.sessionDeletedHandler) {
|
|
180016
|
-
sessionManager.sessions.deleted.off(this.sessionDeletedHandler);
|
|
180017
|
-
}
|
|
180018
|
-
} catch {
|
|
180019
|
-
}
|
|
180020
|
-
this.sessionDiagHandler = void 0;
|
|
180021
|
-
this.sessionAddedHandler = void 0;
|
|
180022
|
-
this.sessionDeletedHandler = void 0;
|
|
180023
|
-
if (this.deadSessionTimer) {
|
|
180024
|
-
clearTimeout(this.deadSessionTimer);
|
|
180025
|
-
this.deadSessionTimer = null;
|
|
180026
|
-
}
|
|
180027
|
-
for (const timer of this.staleSessionTimers.values()) {
|
|
180028
|
-
clearTimeout(timer);
|
|
180029
|
-
}
|
|
180030
|
-
this.staleSessionTimers.clear();
|
|
180031
|
-
this.sessionStartedAt.clear();
|
|
180032
|
-
}
|
|
180033
|
-
// Warn at the fabric-added event when Alexa pairs on a non-5540 port. Alexa
|
|
180034
|
-
// completes AddNOC but never CASEs, so the fabric rolls back on failsafe
|
|
180035
|
-
// expiry ~20s later and the committed-fabric warning path never sees it (#401).
|
|
180036
|
-
wireFabricWarnings() {
|
|
180037
|
-
this.unwireFabricWarnings();
|
|
180038
|
-
try {
|
|
180039
|
-
const fabrics = this.server.env.get(FabricManager);
|
|
180040
|
-
this.fabricAddedHandler = (fabric) => {
|
|
180041
|
-
const port = this.dataProvider.port;
|
|
180042
|
-
if (alexaPairingPortProblem(fabric.rootVendorId, port)) {
|
|
180043
|
-
this.log.warn(
|
|
180044
|
-
`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)`
|
|
180045
|
-
);
|
|
180046
|
-
}
|
|
180047
|
-
};
|
|
180048
|
-
fabrics.events.added.on(this.fabricAddedHandler);
|
|
180049
|
-
} catch {
|
|
180050
|
-
}
|
|
180051
|
-
}
|
|
180052
|
-
unwireFabricWarnings() {
|
|
180053
|
-
try {
|
|
180054
|
-
const fabrics = this.server.env.get(FabricManager);
|
|
180055
|
-
if (this.fabricAddedHandler) {
|
|
180056
|
-
fabrics.events.added.off(this.fabricAddedHandler);
|
|
180057
|
-
}
|
|
180058
|
-
} catch {
|
|
180059
|
-
}
|
|
180060
|
-
this.fabricAddedHandler = void 0;
|
|
180061
|
-
}
|
|
180062
|
-
// Start the periodic age-based session rotation (#287).
|
|
180063
|
-
startSessionRotation() {
|
|
180064
|
-
this.stopSessionRotation();
|
|
180065
|
-
const hours = this.readSessionMaxAgeHours();
|
|
180066
|
-
if (hours === 0) {
|
|
180067
|
-
this.log.info(
|
|
180068
|
-
"Session rotation disabled (HAMH_MATTER_SESSION_MAX_AGE_HOURS=0)"
|
|
180069
|
-
);
|
|
180070
|
-
return;
|
|
180071
|
-
}
|
|
180072
|
-
this.maxSessionAgeMs = hours * 60 * 60 * 1e3;
|
|
180073
|
-
this.rotationTimer = setInterval(
|
|
180074
|
-
() => this.rotateAgedSessions(),
|
|
180075
|
-
ROTATION_CHECK_INTERVAL_MS
|
|
180076
|
-
);
|
|
180077
|
-
this.log.info(
|
|
180078
|
-
`Session rotation: max age ${hours}h, check every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
|
|
180079
|
-
);
|
|
180080
|
-
}
|
|
180081
|
-
stopSessionRotation() {
|
|
180082
|
-
if (this.rotationTimer) {
|
|
180083
|
-
clearInterval(this.rotationTimer);
|
|
180084
|
-
this.rotationTimer = null;
|
|
180085
|
-
}
|
|
180086
|
-
}
|
|
180087
|
-
// Opt-in wedge watchdog: reuse the rotation check cadence (5min) to look for
|
|
180088
|
-
// the one session wedged on "Updating" and rotate just that one.
|
|
180089
|
-
startWedgeWatchdog() {
|
|
180090
|
-
this.stopWedgeWatchdog();
|
|
180091
|
-
if (!this.dataProvider.featureFlags?.wedgeWatchdog) {
|
|
180092
|
-
return;
|
|
180093
|
-
}
|
|
180094
|
-
this.wedgeWatchdogTimer = setInterval(
|
|
180095
|
-
() => this.runWedgeWatchdogCheck(),
|
|
180096
|
-
ROTATION_CHECK_INTERVAL_MS
|
|
180097
|
-
);
|
|
180098
|
-
this.log.info(
|
|
180099
|
-
`Wedge watchdog: checking every ${ROTATION_CHECK_INTERVAL_MS / 6e4}min`
|
|
180100
|
-
);
|
|
180101
|
-
}
|
|
180102
|
-
stopWedgeWatchdog() {
|
|
180103
|
-
if (this.wedgeWatchdogTimer) {
|
|
180104
|
-
clearInterval(this.wedgeWatchdogTimer);
|
|
180105
|
-
this.wedgeWatchdogTimer = null;
|
|
180106
|
-
}
|
|
180107
|
-
}
|
|
180108
|
-
// Rotate exactly the sessions the pure rule flags as wedged. Closing is the
|
|
180109
|
-
// same graceful-then-force path age rotation uses, so a false positive just
|
|
180110
|
-
// re-CASEs the controller.
|
|
180111
|
-
runWedgeWatchdogCheck() {
|
|
180112
|
-
try {
|
|
180113
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
180114
|
-
const now = Date.now();
|
|
180115
|
-
const closes = [];
|
|
180116
|
-
for (const s of [...sessionManager.sessions]) {
|
|
180117
|
-
if (s.isClosing) continue;
|
|
180118
|
-
const startedAt = this.sessionStartedAt.get(s.id);
|
|
180119
|
-
const sessionAgeMs = startedAt != null ? now - startedAt : 0;
|
|
180120
|
-
const lastImAt = this.lastImRequestAt.get(s);
|
|
180121
|
-
const lastImRequestMsAgo = lastImAt != null ? now - lastImAt : null;
|
|
180122
|
-
const lastRotatedAt = this.wedgeLastRotatedAt.get(s);
|
|
180123
|
-
const lastRotatedMsAgo = lastRotatedAt != null ? now - lastRotatedAt : null;
|
|
180124
|
-
const v1 = decideWedgeRotation({
|
|
180125
|
-
subscriptionCount: s.subscriptions.size,
|
|
180126
|
-
sessionAgeMs,
|
|
180127
|
-
lastImRequestMsAgo,
|
|
180128
|
-
lastRotatedMsAgo
|
|
180129
|
-
});
|
|
180130
|
-
const lastCmdAt = this.lastCommandImAt.get(s);
|
|
180131
|
-
const commandSilenceMs = lastCmdAt != null ? now - lastCmdAt : null;
|
|
180132
|
-
const subscribeTimes = this.subscribeTimesMs.get(s) ?? [];
|
|
180133
|
-
const giveUpTimes = this.giveUpTimesMs.get(s) ?? [];
|
|
180134
|
-
const v2 = decideWedgeRotationV2({
|
|
180135
|
-
subscriptionCount: s.subscriptions.size,
|
|
180136
|
-
sessionAgeMs,
|
|
180137
|
-
commandSilenceMs,
|
|
180138
|
-
subscribeTimesMs: subscribeTimes,
|
|
180139
|
-
giveUpTimesMs: giveUpTimes,
|
|
180140
|
-
nowMs: now,
|
|
180141
|
-
lastRotatedMsAgo
|
|
180142
|
-
});
|
|
180143
|
-
const cmdSilenceMin = Math.round(
|
|
180144
|
-
(commandSilenceMs ?? sessionAgeMs) / 6e4
|
|
180145
|
-
);
|
|
180146
|
-
const v2Stats = `cmdSilenceMin=${cmdSilenceMin} subscribes30m=${countRecent(subscribeTimes, now)} giveUps30m=${countRecent(giveUpTimes, now)}`;
|
|
180147
|
-
if (v2 && !v1) {
|
|
180148
|
-
this.log.info(`wedge v2 would rotate session ${s.id}: ${v2Stats}`);
|
|
180149
|
-
}
|
|
180150
|
-
if (!v1) {
|
|
180151
|
-
continue;
|
|
180152
|
-
}
|
|
180153
|
-
this.log.info(
|
|
180154
|
-
`wedge v2 session ${s.id}: v1 rotated, v2 agree=${v2} ${v2Stats}`
|
|
180155
|
-
);
|
|
180156
|
-
const silenceMin = Math.round(
|
|
180157
|
-
(lastImRequestMsAgo ?? sessionAgeMs) / 6e4
|
|
180158
|
-
);
|
|
180159
|
-
this.log.info(
|
|
180160
|
-
`Wedge watchdog: rotating session ${s.id}, no inbound interaction for ${silenceMin}min`
|
|
180161
|
-
);
|
|
180162
|
-
this.wedgeLastRotatedAt.set(s, now);
|
|
180163
|
-
closes.push(
|
|
180164
|
-
s.initiateClose().catch(() => {
|
|
180165
|
-
return s.initiateForceClose({
|
|
180166
|
-
cause: new Error("wedge watchdog, forcing")
|
|
180167
|
-
});
|
|
180168
|
-
})
|
|
180169
|
-
);
|
|
180170
|
-
}
|
|
180171
|
-
if (closes.length > 0) {
|
|
180172
|
-
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
180173
|
-
}
|
|
180174
|
-
} catch {
|
|
180175
|
-
}
|
|
180176
|
-
}
|
|
180177
|
-
// Poll the interface addresses mDNS advertises and re-announce when they
|
|
180178
|
-
// change. matter.js caches the operational records at first announcement, so
|
|
180179
|
-
// a dynamic ISP IPv6 prefix change keeps advertising the dead global address
|
|
180180
|
-
// until a restart (#415). refreshOperationalAdvertisement re-runs the lookup.
|
|
180181
|
-
startMdnsAddressWatch() {
|
|
180182
|
-
this.stopMdnsAddressWatch();
|
|
180183
|
-
const { netInterface, stripGlobalIpv6, ipv4Enabled } = this.readMdnsWatchSettings();
|
|
180184
|
-
this.advertisedAddressSnapshot = collectAdvertisedAddresses(
|
|
180185
|
-
os10.networkInterfaces(),
|
|
180186
|
-
netInterface,
|
|
180187
|
-
stripGlobalIpv6,
|
|
180188
|
-
ipv4Enabled
|
|
180189
|
-
);
|
|
180190
|
-
this.mdnsAddressTimer = setInterval(() => {
|
|
180191
|
-
if (this.mdnsCheckInFlight) return;
|
|
180192
|
-
this.mdnsCheckInFlight = true;
|
|
180193
|
-
this.checkAdvertisedAddresses().catch((e) => {
|
|
180194
|
-
this.log.warn("mDNS address check failed:", e);
|
|
180195
|
-
}).finally(() => {
|
|
180196
|
-
this.mdnsCheckInFlight = false;
|
|
180197
|
-
});
|
|
180198
|
-
}, MDNS_ADDRESS_CHECK_INTERVAL_MS2);
|
|
180199
|
-
}
|
|
180200
|
-
stopMdnsAddressWatch() {
|
|
180201
|
-
if (this.mdnsAddressTimer) {
|
|
180202
|
-
clearInterval(this.mdnsAddressTimer);
|
|
180203
|
-
this.mdnsAddressTimer = null;
|
|
180204
|
-
}
|
|
180205
|
-
}
|
|
180206
|
-
// Read the mDNS settings the shared setup applied. MdnsService lives on the
|
|
180207
|
-
// root env and stripGlobalIpv6 swaps in a FilteredNetwork, so both are
|
|
180208
|
-
// visible from the bridge env.
|
|
180209
|
-
readMdnsWatchSettings() {
|
|
180210
|
-
try {
|
|
180211
|
-
const mdns2 = this.server.env.get(MdnsService);
|
|
180212
|
-
const stripGlobalIpv6 = this.server.env.get(Network) instanceof FilteredNetwork;
|
|
180213
|
-
return {
|
|
180214
|
-
netInterface: mdns2.limitedToNetInterface,
|
|
180215
|
-
stripGlobalIpv6,
|
|
180216
|
-
ipv4Enabled: mdns2.enableIpv4
|
|
180217
|
-
};
|
|
180218
|
-
} catch {
|
|
180219
|
-
return { stripGlobalIpv6: false, ipv4Enabled: true };
|
|
180220
|
-
}
|
|
180221
|
-
}
|
|
180222
|
-
async checkAdvertisedAddresses() {
|
|
180223
|
-
const { netInterface, stripGlobalIpv6, ipv4Enabled } = this.readMdnsWatchSettings();
|
|
180224
|
-
const advertiser = this.server.env.get(DeviceAdvertiser);
|
|
180225
|
-
const fabrics = this.server.env.get(FabricManager);
|
|
180226
|
-
this.advertisedAddressSnapshot = await runMdnsAddressWatchTick({
|
|
180227
|
-
readInterfaces: () => os10.networkInterfaces(),
|
|
180228
|
-
netInterface,
|
|
180229
|
-
stripGlobalIpv6,
|
|
180230
|
-
ipv4Enabled,
|
|
180231
|
-
currentSnapshot: this.advertisedAddressSnapshot,
|
|
180232
|
-
fabrics: () => fabrics.fabrics,
|
|
180233
|
-
refresh: (fabric) => advertiser.refreshOperationalAdvertisement(fabric),
|
|
180234
|
-
onChange: (prev, curr) => {
|
|
180235
|
-
this.log.warn(
|
|
180236
|
-
`Advertised address set changed (${prev.length} -> ${curr.length} addresses), re-announcing operational mDNS (#415)`
|
|
180237
|
-
);
|
|
180238
|
-
}
|
|
180239
|
-
});
|
|
180240
|
-
}
|
|
180241
|
-
// Resolve session rotation max age. Bridge config wins, then env var,
|
|
180242
|
-
// then built-in default. 0 disables, otherwise clamped to range.
|
|
180243
|
-
readSessionMaxAgeHours() {
|
|
180244
|
-
const { min, max } = SESSION_MAX_AGE_HOURS_RANGE;
|
|
180245
|
-
const fromConfig = this.dataProvider.sessionMaxAgeHours;
|
|
180246
|
-
if (fromConfig != null && Number.isFinite(fromConfig) && fromConfig >= 0) {
|
|
180247
|
-
if (fromConfig === 0) return 0;
|
|
180248
|
-
if (fromConfig < min) return min;
|
|
180249
|
-
if (fromConfig > max) return max;
|
|
180250
|
-
return fromConfig;
|
|
180251
|
-
}
|
|
180252
|
-
const raw = process.env.HAMH_MATTER_SESSION_MAX_AGE_HOURS;
|
|
180253
|
-
const parsed = parseSessionMaxAgeHours(raw);
|
|
180254
|
-
if (parsed == null) {
|
|
180255
|
-
this.log.warn(
|
|
180256
|
-
`Invalid HAMH_MATTER_SESSION_MAX_AGE_HOURS=${raw}, falling back to ${DEFAULT_SESSION_MAX_AGE_HOURS}h`
|
|
180257
|
-
);
|
|
180258
|
-
return DEFAULT_SESSION_MAX_AGE_HOURS;
|
|
180259
|
-
}
|
|
180260
|
-
return parsed;
|
|
180261
|
-
}
|
|
180262
|
-
// Gracefully close sessions older than maxSessionAgeMs so controllers
|
|
180263
|
-
// re-establish CASE and re-subscribe. Stale (0-sub) sessions are handled
|
|
180264
|
-
// by the existing dead-session path, so only rotate ones with subscriptions.
|
|
180265
|
-
rotateAgedSessions() {
|
|
180266
|
-
if (this.maxSessionAgeMs === 0) return;
|
|
180267
|
-
try {
|
|
180268
|
-
const sessionManager = this.server.env.get(SessionManager);
|
|
180269
|
-
const now = Date.now();
|
|
180270
|
-
const closes = [];
|
|
180271
|
-
for (const s of [...sessionManager.sessions]) {
|
|
180272
|
-
const startedAt = this.sessionStartedAt.get(s.id);
|
|
180273
|
-
if (startedAt == null) continue;
|
|
180274
|
-
const ageMs = now - startedAt;
|
|
180275
|
-
if (ageMs < this.maxSessionAgeMs || s.isClosing || s.subscriptions.size === 0) {
|
|
180276
|
-
continue;
|
|
180277
|
-
}
|
|
180278
|
-
const ageMin = Math.round(ageMs / 6e4);
|
|
180279
|
-
this.log.info(
|
|
180280
|
-
`Rotating session ${s.id} (peer ${s.peerNodeId}, age ${ageMin}min, subs ${s.subscriptions.size})`
|
|
180281
|
-
);
|
|
180282
|
-
closes.push(
|
|
180283
|
-
s.initiateClose().catch(() => {
|
|
180284
|
-
return s.initiateForceClose({
|
|
180285
|
-
cause: new Error("session rotation, forcing")
|
|
180286
|
-
});
|
|
180287
|
-
})
|
|
180288
|
-
);
|
|
180289
|
-
}
|
|
180290
|
-
if (closes.length > 0) {
|
|
180291
|
-
Promise.allSettled(closes).then(() => this.triggerMdnsReAnnounce());
|
|
180292
|
-
}
|
|
180293
|
-
} catch {
|
|
180294
|
-
}
|
|
180295
|
-
}
|
|
180296
179551
|
stopAutoForceSync() {
|
|
180297
179552
|
if (this.autoForceSyncTimer) {
|
|
180298
179553
|
clearInterval(this.autoForceSyncTimer);
|