@riddix/hamh 2.1.0-alpha.569 → 2.1.0-alpha.571

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.
@@ -145651,6 +145651,53 @@ var init_service = __esm({
145651
145651
  }
145652
145652
  });
145653
145653
 
145654
+ // src/services/diagnostics/diagnostic-event-bus.ts
145655
+ var MAX_EVENTS, eventCounter, DiagnosticEventBusImpl, diagnosticEventBus;
145656
+ var init_diagnostic_event_bus = __esm({
145657
+ "src/services/diagnostics/diagnostic-event-bus.ts"() {
145658
+ "use strict";
145659
+ MAX_EVENTS = 500;
145660
+ eventCounter = 0;
145661
+ DiagnosticEventBusImpl = class {
145662
+ events = [];
145663
+ listeners = /* @__PURE__ */ new Set();
145664
+ emit(type, message, options) {
145665
+ const event3 = {
145666
+ id: `diag_${++eventCounter}`,
145667
+ timestamp: Date.now(),
145668
+ type,
145669
+ message,
145670
+ bridgeId: options?.bridgeId,
145671
+ bridgeName: options?.bridgeName,
145672
+ entityId: options?.entityId,
145673
+ details: options?.details
145674
+ };
145675
+ this.events.push(event3);
145676
+ if (this.events.length > MAX_EVENTS) {
145677
+ this.events = this.events.slice(-MAX_EVENTS);
145678
+ }
145679
+ for (const listener of this.listeners) {
145680
+ try {
145681
+ listener(event3);
145682
+ } catch {
145683
+ }
145684
+ }
145685
+ }
145686
+ subscribe(listener) {
145687
+ this.listeners.add(listener);
145688
+ return () => this.listeners.delete(listener);
145689
+ }
145690
+ getRecentEvents(limit = 100) {
145691
+ return this.events.slice(-limit);
145692
+ }
145693
+ get totalEventCount() {
145694
+ return eventCounter;
145695
+ }
145696
+ };
145697
+ diagnosticEventBus = new DiagnosticEventBusImpl();
145698
+ }
145699
+ });
145700
+
145654
145701
  // ../common/dist/bridge-data.js
145655
145702
  var BridgeStatus;
145656
145703
  var init_bridge_data = __esm({
@@ -146135,6 +146182,7 @@ var init_binary_sensor = __esm({
146135
146182
  BinarySensorDeviceClass2["Power"] = "power";
146136
146183
  BinarySensorDeviceClass2["Presence"] = "presence";
146137
146184
  BinarySensorDeviceClass2["Problem"] = "problem";
146185
+ BinarySensorDeviceClass2["Rain"] = "rain";
146138
146186
  BinarySensorDeviceClass2["Running"] = "running";
146139
146187
  BinarySensorDeviceClass2["Safety"] = "safety";
146140
146188
  BinarySensorDeviceClass2["Smoke"] = "smoke";
@@ -146359,6 +146407,7 @@ var init_sensor = __esm({
146359
146407
  SensorDeviceClass2["precipitation"] = "precipitation";
146360
146408
  SensorDeviceClass2["precipitation_intensity"] = "precipitation_intensity";
146361
146409
  SensorDeviceClass2["pressure"] = "pressure";
146410
+ SensorDeviceClass2["radon"] = "radon";
146362
146411
  SensorDeviceClass2["reactive_power"] = "reactive_power";
146363
146412
  SensorDeviceClass2["signal_strength"] = "signal_strength";
146364
146413
  SensorDeviceClass2["sound_pressure"] = "sound_pressure";
@@ -147214,6 +147263,7 @@ var init_home_assistant_actions = __esm({
147214
147263
  init_service();
147215
147264
  init_debounce_context();
147216
147265
  init_retry();
147266
+ init_diagnostic_event_bus();
147217
147267
  defaultConfig = {
147218
147268
  retryAttempts: 3,
147219
147269
  retryBaseDelayMs: 100,
@@ -147253,6 +147303,14 @@ var init_home_assistant_actions = __esm({
147253
147303
  `Failed to call action '${action}' for entity '${entity_id ?? "(no target)"}': ${errorMsg}`
147254
147304
  );
147255
147305
  });
147306
+ diagnosticEventBus.emit(
147307
+ "command_received",
147308
+ `Action ${action} for ${entity_id ?? "(no target)"}`,
147309
+ {
147310
+ entityId: entity_id ?? calls[0].entityId,
147311
+ details: { action, data }
147312
+ }
147313
+ );
147256
147314
  this.fireEvent("hamh_action", {
147257
147315
  entity_id: entity_id ?? calls[0].entityId,
147258
147316
  action,
@@ -147460,49 +147518,7 @@ import nocache from "nocache";
147460
147518
 
147461
147519
  // src/services/diagnostics/diagnostic-service.ts
147462
147520
  init_esm();
147463
-
147464
- // src/services/diagnostics/diagnostic-event-bus.ts
147465
- var MAX_EVENTS = 500;
147466
- var eventCounter = 0;
147467
- var DiagnosticEventBusImpl = class {
147468
- events = [];
147469
- listeners = /* @__PURE__ */ new Set();
147470
- emit(type, message, options) {
147471
- const event3 = {
147472
- id: `diag_${++eventCounter}`,
147473
- timestamp: Date.now(),
147474
- type,
147475
- message,
147476
- bridgeId: options?.bridgeId,
147477
- bridgeName: options?.bridgeName,
147478
- entityId: options?.entityId,
147479
- details: options?.details
147480
- };
147481
- this.events.push(event3);
147482
- if (this.events.length > MAX_EVENTS) {
147483
- this.events = this.events.slice(-MAX_EVENTS);
147484
- }
147485
- for (const listener of this.listeners) {
147486
- try {
147487
- listener(event3);
147488
- } catch {
147489
- }
147490
- }
147491
- }
147492
- subscribe(listener) {
147493
- this.listeners.add(listener);
147494
- return () => this.listeners.delete(listener);
147495
- }
147496
- getRecentEvents(limit = 100) {
147497
- return this.events.slice(-limit);
147498
- }
147499
- get totalEventCount() {
147500
- return eventCounter;
147501
- }
147502
- };
147503
- var diagnosticEventBus = new DiagnosticEventBusImpl();
147504
-
147505
- // src/services/diagnostics/diagnostic-service.ts
147521
+ init_diagnostic_event_bus();
147506
147522
  var logger141 = Logger.get("DiagnosticService");
147507
147523
  var ignoredStateKeys = /* @__PURE__ */ new Set([
147508
147524
  "homeAssistantEntity",
@@ -148456,13 +148472,20 @@ var totalMemMB = Math.round(os.totalmem() / 1024 / 1024);
148456
148472
  var defaultMaxSize = totalMemMB < 2048 ? 200 : 1e3;
148457
148473
  var logBuffer = {
148458
148474
  entries: [],
148459
- maxSize: defaultMaxSize
148475
+ maxSize: defaultMaxSize,
148476
+ listeners: /* @__PURE__ */ new Set()
148460
148477
  };
148461
148478
  function addLogEntry(entry) {
148462
148479
  logBuffer.entries.push(entry);
148463
148480
  if (logBuffer.entries.length > logBuffer.maxSize) {
148464
148481
  logBuffer.entries.shift();
148465
148482
  }
148483
+ for (const listener of logBuffer.listeners) {
148484
+ try {
148485
+ listener(entry);
148486
+ } catch {
148487
+ }
148488
+ }
148466
148489
  }
148467
148490
  function logsApi(_logger) {
148468
148491
  const router = express5.Router();
@@ -148519,10 +148542,12 @@ function logsApi(_logger) {
148519
148542
  for (const log of recentLogs) {
148520
148543
  sendLog(log);
148521
148544
  }
148545
+ logBuffer.listeners.add(sendLog);
148522
148546
  const intervalId = setInterval(() => {
148523
148547
  res.write(": keepalive\n\n");
148524
148548
  }, 3e4);
148525
148549
  req.on("close", () => {
148550
+ logBuffer.listeners.delete(sendLog);
148526
148551
  clearInterval(intervalId);
148527
148552
  });
148528
148553
  });
@@ -151154,6 +151179,7 @@ function replaceBase(dist) {
151154
151179
  }
151155
151180
 
151156
151181
  // src/api/websocket-api.ts
151182
+ init_diagnostic_event_bus();
151157
151183
  import { WebSocketServer as WebSocketServer2 } from "ws";
151158
151184
  var WebSocketApi = class {
151159
151185
  constructor(log, bridgeService) {
@@ -166496,9 +166522,11 @@ function validatePluginDevice(device) {
166496
166522
  var PluginManager = class {
166497
166523
  instances = /* @__PURE__ */ new Map();
166498
166524
  domainMappings = /* @__PURE__ */ new Map();
166525
+ domainMappingOwners = /* @__PURE__ */ new Map();
166499
166526
  storageDir;
166500
166527
  bridgeId;
166501
166528
  runner = new SafePluginRunner();
166529
+ registry;
166502
166530
  /** Callback invoked when a plugin registers a new device */
166503
166531
  onDeviceRegistered;
166504
166532
  /** Callback invoked when a plugin removes a device */
@@ -166509,6 +166537,9 @@ var PluginManager = class {
166509
166537
  this.bridgeId = bridgeId;
166510
166538
  this.storageDir = storageDir;
166511
166539
  }
166540
+ setRegistry(registry2) {
166541
+ this.registry = registry2;
166542
+ }
166512
166543
  /**
166513
166544
  * Load and register a built-in plugin instance.
166514
166545
  */
@@ -166639,6 +166670,7 @@ var PluginManager = class {
166639
166670
  );
166640
166671
  }
166641
166672
  this.domainMappings.set(mapping.domain, mapping);
166673
+ this.domainMappingOwners.set(mapping.domain, plugin.name);
166642
166674
  pluginLogger.info(
166643
166675
  `Registered domain mapping: ${mapping.domain} \u2192 ${mapping.matterDeviceType}`
166644
166676
  );
@@ -166754,6 +166786,12 @@ var PluginManager = class {
166754
166786
  if (instance) {
166755
166787
  instance.metadata.enabled = false;
166756
166788
  }
166789
+ for (const [domain, owner] of this.domainMappingOwners) {
166790
+ if (owner === pluginName) {
166791
+ this.domainMappings.delete(domain);
166792
+ this.domainMappingOwners.delete(domain);
166793
+ }
166794
+ }
166757
166795
  }
166758
166796
  enablePlugin(pluginName) {
166759
166797
  this.runner.resetCircuitBreaker(pluginName);
@@ -166774,6 +166812,7 @@ var PluginManager = class {
166774
166812
  const instance = this.instances.get(pluginName);
166775
166813
  if (!instance) return false;
166776
166814
  instance.metadata.config = config10;
166815
+ this.registry?.updateConfig(pluginName, config10);
166777
166816
  if (instance.plugin.onConfigChanged) {
166778
166817
  await this.runner.run(
166779
166818
  pluginName,
@@ -167114,6 +167153,7 @@ function ensureCommissioningConfig(server) {
167114
167153
  }
167115
167154
 
167116
167155
  // src/services/bridges/bridge.ts
167156
+ init_diagnostic_event_bus();
167117
167157
  var AUTO_FORCE_SYNC_INTERVAL_MS = 9e4;
167118
167158
  var DEAD_SESSION_TIMEOUT_MS = 6e4;
167119
167159
  var Bridge = class {
@@ -167336,6 +167376,20 @@ ${e?.toString()}`);
167336
167376
  this.log.info(
167337
167377
  `Session ${session.id} (peer ${session.peerNodeId}): subscriptions=${session.subscriptions.size} | total: sessions=${sessions.length} subscriptions=${totalSubs}`
167338
167378
  );
167379
+ diagnosticEventBus.emit(
167380
+ "subscription_changed",
167381
+ `Session ${session.id}: ${session.subscriptions.size} subs (total ${totalSubs})`,
167382
+ {
167383
+ bridgeId: this.data.id,
167384
+ bridgeName: this.data.name,
167385
+ details: {
167386
+ sessionId: session.id,
167387
+ sessionSubs: session.subscriptions.size,
167388
+ totalSessions: sessions.length,
167389
+ totalSubs
167390
+ }
167391
+ }
167392
+ );
167339
167393
  if (totalSubs === 0 && sessions.length > 0) {
167340
167394
  this.log.warn(
167341
167395
  `All subscriptions lost \u2014 ${sessions.length} session(s) still active, waiting for controller to re-subscribe`
@@ -167374,6 +167428,15 @@ ${e?.toString()}`);
167374
167428
  this.log.info(
167375
167429
  `Session opened: id=${newSession.id} peer=${newSession.peerNodeId}`
167376
167430
  );
167431
+ diagnosticEventBus.emit(
167432
+ "session_opened",
167433
+ `Session ${newSession.id} opened (peer ${newSession.peerNodeId})`,
167434
+ {
167435
+ bridgeId: this.data.id,
167436
+ bridgeName: this.data.name,
167437
+ details: { sessionId: newSession.id }
167438
+ }
167439
+ );
167377
167440
  for (const s of [...sessionManager.sessions]) {
167378
167441
  if (s !== newSession && !s.isClosing && s.peerNodeId === newSession.peerNodeId && s.fabric?.fabricIndex === newSession.fabric?.fabricIndex && s.subscriptions.size === 0) {
167379
167442
  this.log.info(
@@ -167389,6 +167452,18 @@ ${e?.toString()}`);
167389
167452
  this.log.warn(
167390
167453
  `Session closed: id=${session.id} peer=${session.peerNodeId} | remaining sessions=${sessions.length}`
167391
167454
  );
167455
+ diagnosticEventBus.emit(
167456
+ "session_closed",
167457
+ `Session ${session.id} closed (peer ${session.peerNodeId})`,
167458
+ {
167459
+ bridgeId: this.data.id,
167460
+ bridgeName: this.data.name,
167461
+ details: {
167462
+ sessionId: session.id,
167463
+ remainingSessions: sessions.length
167464
+ }
167465
+ }
167466
+ );
167392
167467
  };
167393
167468
  sessionManager.sessions.added.on(this.sessionAddedHandler);
167394
167469
  sessionManager.sessions.deleted.on(this.sessionDeletedHandler);
@@ -170178,6 +170253,15 @@ var OnOffSensorWithBatteryType = OnOffSensorDevice.with(
170178
170253
  })
170179
170254
  );
170180
170255
 
170256
+ // src/matter/endpoints/legacy/binary-sensor/rain-sensor.ts
170257
+ init_home_assistant_entity_behavior();
170258
+ var RainSensorType = RainSensorDevice.with(
170259
+ BasicInformationServer2,
170260
+ IdentifyServer2,
170261
+ HomeAssistantEntityBehavior,
170262
+ BooleanStateServer2()
170263
+ );
170264
+
170181
170265
  // ../../node_modules/.pnpm/@matter+main@0.16.10/node_modules/@matter/main/dist/esm/forwards/behaviors/smoke-co-alarm.js
170182
170266
  init_nodejs();
170183
170267
 
@@ -170307,7 +170391,8 @@ var deviceClasses = {
170307
170391
  [BinarySensorDeviceClass.Occupancy]: OccupancySensorType,
170308
170392
  [BinarySensorDeviceClass.Presence]: OccupancySensorType,
170309
170393
  [BinarySensorDeviceClass.Smoke]: SmokeAlarmType,
170310
- [BinarySensorDeviceClass.Moisture]: WaterLeakDetectorType
170394
+ [BinarySensorDeviceClass.Moisture]: WaterLeakDetectorType,
170395
+ [BinarySensorDeviceClass.Rain]: RainSensorType
170311
170396
  };
170312
170397
  var batteryTypes = /* @__PURE__ */ new Map([
170313
170398
  [ContactSensorType, ContactSensorWithBatteryType],
@@ -170340,15 +170425,6 @@ function BinarySensorDevice(homeAssistantEntity) {
170340
170425
  return type.set({ homeAssistantEntity });
170341
170426
  }
170342
170427
 
170343
- // src/matter/endpoints/legacy/binary-sensor/rain-sensor.ts
170344
- init_home_assistant_entity_behavior();
170345
- var RainSensorType = RainSensorDevice.with(
170346
- BasicInformationServer2,
170347
- IdentifyServer2,
170348
- HomeAssistantEntityBehavior,
170349
- BooleanStateServer2()
170350
- );
170351
-
170352
170428
  // src/matter/endpoints/legacy/button/index.ts
170353
170429
  init_home_assistant_entity_behavior();
170354
170430
  var ButtonOnOffServerBase = class extends OnOffServer.with("Lighting") {
@@ -175185,6 +175261,7 @@ var TvocSensorType = AirQualitySensorDevice.with(
175185
175261
 
175186
175262
  // src/matter/endpoints/legacy/sensor/index.ts
175187
175263
  init_dist();
175264
+ init_diagnostic_event_bus();
175188
175265
 
175189
175266
  // src/matter/behaviors/carbon-dioxide-concentration-measurement-server.ts
175190
175267
  init_home_assistant_entity_behavior();
@@ -175734,6 +175811,9 @@ function SensorDevice(homeAssistantEntity) {
175734
175811
  if (deviceClass === SensorDeviceClass.pm1) {
175735
175812
  return Pm1SensorType.set({ homeAssistantEntity });
175736
175813
  }
175814
+ if (deviceClass === SensorDeviceClass.radon) {
175815
+ return RadonSensorType.set({ homeAssistantEntity });
175816
+ }
175737
175817
  if (deviceClass === SensorDeviceClass.power || deviceClass === SensorDeviceClass.energy || deviceClass === SensorDeviceClass.voltage || deviceClass === SensorDeviceClass.current) {
175738
175818
  return ElectricalSensorType.set({ homeAssistantEntity });
175739
175819
  }
@@ -179082,6 +179162,9 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
179082
179162
  }
179083
179163
  };
179084
179164
 
179165
+ // src/services/bridges/bridge-endpoint-manager.ts
179166
+ init_diagnostic_event_bus();
179167
+
179085
179168
  // src/services/home-assistant/api/subscribe-entities.ts
179086
179169
  init_esm();
179087
179170
  import crypto7 from "node:crypto";
@@ -179185,6 +179268,7 @@ var subscribeEntities = (conn, onChange, entityIds) => entitiesColl(conn, entity
179185
179268
 
179186
179269
  // src/services/bridges/entity-isolation-service.ts
179187
179270
  init_esm();
179271
+ init_diagnostic_event_bus();
179188
179272
  var logger202 = Logger.get("EntityIsolation");
179189
179273
  var EntityIsolationServiceImpl = class {
179190
179274
  isolatedEntities = /* @__PURE__ */ new Map();
@@ -179270,6 +179354,11 @@ var EntityIsolationServiceImpl = class {
179270
179354
  logger202.warn(
179271
179355
  `Isolating entity "${entityName}" from bridge ${bridgeId} due to: ${reason}`
179272
179356
  );
179357
+ diagnosticEventBus.emit("entity_error", `Entity isolated: ${entityName}`, {
179358
+ bridgeId,
179359
+ entityId: entityName,
179360
+ details: { reason: classification }
179361
+ });
179273
179362
  try {
179274
179363
  await callback(entityName);
179275
179364
  return true;
@@ -179735,10 +179824,19 @@ var BridgeEndpointManager = class extends Service {
179735
179824
  }
179736
179825
  }
179737
179826
  const latencyMs = Math.round((performance.now() - startMs) * 100) / 100;
179738
- if (latencyMs > 200) {
179739
- this.log.warn(
179740
- `Slow state update: ${endpoints.length} endpoints in ${latencyMs}ms` + (failedCount > 0 ? ` (${failedCount} failed)` : "")
179741
- );
179827
+ if (latencyMs > 200 || failedCount > 0) {
179828
+ const msg = `State update: ${endpoints.length} endpoints in ${latencyMs}ms` + (failedCount > 0 ? ` (${failedCount} failed)` : "");
179829
+ if (latencyMs > 200) {
179830
+ this.log.warn(`Slow ${msg}`);
179831
+ }
179832
+ diagnosticEventBus.emit("state_update", msg, {
179833
+ bridgeId: this.bridgeId,
179834
+ details: {
179835
+ endpointCount: endpoints.length,
179836
+ failedCount,
179837
+ latencyMs
179838
+ }
179839
+ });
179742
179840
  }
179743
179841
  }
179744
179842
  /**
@@ -180468,6 +180566,7 @@ function hashAreaId(areaId) {
180468
180566
 
180469
180567
  // src/services/bridges/server-mode-bridge.ts
180470
180568
  init_dist();
180569
+ init_diagnostic_event_bus();
180471
180570
  var AUTO_FORCE_SYNC_INTERVAL_MS2 = 9e4;
180472
180571
  var DEAD_SESSION_TIMEOUT_MS2 = 6e4;
180473
180572
  var ServerModeBridge = class {
@@ -181214,9 +181313,12 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
181214
181313
  }
181215
181314
  /**
181216
181315
  * Write directly to the RvcOperationalState cluster in a fresh
181217
- * transaction. Each call produces a unique errorStateLabel value
181316
+ * transaction. Each call produces a unique errorStateDetails value
181218
181317
  * so the struct is never deep-equal to its predecessor,
181219
181318
  * guaranteeing matter.js emits attrsChanged → subscription report.
181319
+ *
181320
+ * errorStateDetails (id 2) has conformance "O" (always optional)
181321
+ * unlike errorStateLabel (id 1) which requires errorStateId 128-191.
181220
181322
  */
181221
181323
  async pushKeepalive() {
181222
181324
  try {
@@ -181228,7 +181330,7 @@ var ServerModeVacuumEndpoint = class _ServerModeVacuumEndpoint extends EntityEnd
181228
181330
  const errorStateId = opState.state.operationalError.errorStateId;
181229
181331
  opState.state.operationalError = {
181230
181332
  errorStateId,
181231
- errorStateLabel: `k${counter}`
181333
+ errorStateDetails: `k${counter}`
181232
181334
  };
181233
181335
  });
181234
181336
  logger203.info(`Keepalive #${counter} committed for ${this.entityId}`);
@@ -181558,6 +181660,7 @@ var BridgeEnvironment = class _BridgeEnvironment extends EnvironmentBase {
181558
181660
  if (this.storageLocation) {
181559
181661
  pluginManager = new PluginManager(bridgeId, this.storageLocation);
181560
181662
  pluginRegistry = new PluginRegistry(this.storageLocation);
181663
+ pluginManager.setRegistry(pluginRegistry);
181561
181664
  pluginInstaller = new PluginInstaller(this.storageLocation);
181562
181665
  }
181563
181666
  this.set(