@riddix/hamh 2.1.0-alpha.814 → 2.1.0-alpha.816

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.
@@ -132609,6 +132609,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
132609
132609
  batteryEntity: config11.batteryEntity,
132610
132610
  roomEntities: config11.roomEntities,
132611
132611
  disableLockPin: config11.disableLockPin,
132612
+ lockUsercodeService: config11.lockUsercodeService,
132613
+ lockUsercodeSlot: config11.lockUsercodeSlot,
132612
132614
  powerEntity: config11.powerEntity,
132613
132615
  energyEntity: config11.energyEntity,
132614
132616
  suctionLevelEntity: config11.suctionLevelEntity,
@@ -132760,6 +132762,8 @@ WARNING: ${includeIdentity ? "This backup contains sensitive Matter identity dat
132760
132762
  batteryEntity: config11.batteryEntity,
132761
132763
  roomEntities: config11.roomEntities,
132762
132764
  disableLockPin: config11.disableLockPin,
132765
+ lockUsercodeService: config11.lockUsercodeService,
132766
+ lockUsercodeSlot: config11.lockUsercodeSlot,
132763
132767
  powerEntity: config11.powerEntity,
132764
132768
  energyEntity: config11.energyEntity,
132765
132769
  suctionLevelEntity: config11.suctionLevelEntity,
@@ -133602,6 +133606,8 @@ function entityMappingApi(mappingStorage) {
133602
133606
  chargingStateEntity: body.chargingStateEntity,
133603
133607
  roomEntities: body.roomEntities,
133604
133608
  disableLockPin: body.disableLockPin,
133609
+ lockUsercodeService: body.lockUsercodeService,
133610
+ lockUsercodeSlot: body.lockUsercodeSlot,
133605
133611
  powerEntity: body.powerEntity,
133606
133612
  energyEntity: body.energyEntity,
133607
133613
  suctionLevelEntity: body.suctionLevelEntity,
@@ -134092,6 +134098,8 @@ function configToProfileEntry(config11) {
134092
134098
  batteryEntity: config11.batteryEntity,
134093
134099
  roomEntities: config11.roomEntities,
134094
134100
  disableLockPin: config11.disableLockPin,
134101
+ lockUsercodeService: config11.lockUsercodeService,
134102
+ lockUsercodeSlot: config11.lockUsercodeSlot,
134095
134103
  powerEntity: config11.powerEntity,
134096
134104
  energyEntity: config11.energyEntity,
134097
134105
  suctionLevelEntity: config11.suctionLevelEntity,
@@ -134221,6 +134229,8 @@ function mappingProfileApi(mappingStorage) {
134221
134229
  batteryEntity: entry.batteryEntity,
134222
134230
  roomEntities: entry.roomEntities,
134223
134231
  disableLockPin: entry.disableLockPin,
134232
+ lockUsercodeService: entry.lockUsercodeService,
134233
+ lockUsercodeSlot: entry.lockUsercodeSlot,
134224
134234
  powerEntity: entry.powerEntity,
134225
134235
  energyEntity: entry.energyEntity,
134226
134236
  suctionLevelEntity: entry.suctionLevelEntity,
@@ -138410,6 +138420,8 @@ var EntityMappingStorage = class extends Service {
138410
138420
  chargingStateEntity: request.chargingStateEntity?.trim() || void 0,
138411
138421
  roomEntities: roomEntities.length > 0 ? roomEntities : void 0,
138412
138422
  disableLockPin: request.disableLockPin || void 0,
138423
+ lockUsercodeService: request.lockUsercodeService?.trim() || void 0,
138424
+ lockUsercodeSlot: sanitizeUsercodeSlot(request.lockUsercodeSlot),
138413
138425
  powerEntity: request.powerEntity?.trim() || void 0,
138414
138426
  energyEntity: request.energyEntity?.trim() || void 0,
138415
138427
  pressureEntity: request.pressureEntity?.trim() || void 0,
@@ -138439,7 +138451,7 @@ var EntityMappingStorage = class extends Service {
138439
138451
  climateAutoMode: request.climateAutoMode || void 0,
138440
138452
  composedEntities: request.composedEntities?.filter((e) => e.entityId?.trim()) ?? void 0
138441
138453
  };
138442
- if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.powerEntity && !config11.energyEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && (!config11.fanWindPresets || (config11.fanWindPresets.natural?.length ?? 0) === 0 && (config11.fanWindPresets.sleep?.length ?? 0) === 0) && !config11.fanRestoreSpeedOnPowerOn && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.selectExposeAsSwitch && !config11.selectSwitchOnOption && !config11.selectSwitchOffOption && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0)) {
138454
+ if (!config11.matterDeviceType && !config11.customName && !config11.customProductName && !config11.customVendorName && !config11.customSerialNumber && config11.customVendorId === void 0 && config11.disabled !== true && !config11.filterLifeEntity && !config11.cleaningModeEntity && !config11.temperatureEntity && !config11.humidityEntity && !config11.batteryEntity && !config11.chargingStateEntity && !config11.roomEntities && !config11.disableLockPin && !config11.lockUsercodeService && config11.lockUsercodeSlot === void 0 && !config11.powerEntity && !config11.energyEntity && !config11.pressureEntity && !config11.suctionLevelEntity && !config11.mopIntensityEntity && (!config11.customServiceAreas || config11.customServiceAreas.length === 0) && (!config11.customFanSpeedTags || Object.keys(config11.customFanSpeedTags).length === 0) && (!config11.fanWindPresets || (config11.fanWindPresets.natural?.length ?? 0) === 0 && (config11.fanWindPresets.sleep?.length ?? 0) === 0) && !config11.fanRestoreSpeedOnPowerOn && !config11.currentRoomEntity && !config11.cleanedAreaEntity && !config11.disableCustomAreaRoomModes && !config11.valetudoIdentifier && !config11.coverSwapOpenClose && !config11.coverExposeAsDimmableLight && !config11.selectExposeAsSwitch && !config11.selectSwitchOnOption && !config11.selectSwitchOffOption && !config11.coverSliderDebounceMs && !config11.updateThrottleMs && !config11.disableClimateOnOff && !config11.disableClimateFanControl && !config11.climateKeepModeOnIdle && !config11.climateExposeFan && !config11.climateAutoMode && (!config11.composedEntities || config11.composedEntities.length === 0)) {
138443
138455
  bridgeMap.delete(request.entityId);
138444
138456
  } else {
138445
138457
  bridgeMap.set(request.entityId, config11);
@@ -138469,6 +138481,16 @@ function sanitizeVendorId(value) {
138469
138481
  }
138470
138482
  return n;
138471
138483
  }
138484
+ function sanitizeUsercodeSlot(value) {
138485
+ if (value === void 0 || value === null || value === "") {
138486
+ return void 0;
138487
+ }
138488
+ const n = typeof value === "string" ? Number(value) : value;
138489
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
138490
+ return void 0;
138491
+ }
138492
+ return n;
138493
+ }
138472
138494
  function sanitizeDebounceMs(value) {
138473
138495
  if (value === void 0 || value === null || value === "") {
138474
138496
  return void 0;
@@ -156747,6 +156769,7 @@ function getMappedEntityIds(mapping) {
156747
156769
  if (!mapping) return [];
156748
156770
  const ids = [];
156749
156771
  if (mapping.batteryEntity) ids.push(mapping.batteryEntity);
156772
+ if (mapping.faultEntity) ids.push(mapping.faultEntity);
156750
156773
  if (mapping.chargingStateEntity) ids.push(mapping.chargingStateEntity);
156751
156774
  if (mapping.temperatureEntity) ids.push(mapping.temperatureEntity);
156752
156775
  if (mapping.humidityEntity) ids.push(mapping.humidityEntity);
@@ -161283,6 +161306,47 @@ init_nodejs();
161283
161306
 
161284
161307
  // src/matter/endpoints/legacy/binary-sensor/smoke-co-alarm.ts
161285
161308
  init_home_assistant_entity_behavior();
161309
+ function getBatteryPercent(entity, agent) {
161310
+ const homeAssistant = agent.get(HomeAssistantEntityBehavior);
161311
+ const batteryEntity = homeAssistant.state.mapping?.batteryEntity;
161312
+ if (batteryEntity) {
161313
+ const stateProvider = agent.env.get(EntityStateProvider);
161314
+ const battery = stateProvider.getBatteryPercent(batteryEntity);
161315
+ if (battery != null) {
161316
+ return Math.max(0, Math.min(100, battery));
161317
+ }
161318
+ }
161319
+ const attrs = entity.attributes;
161320
+ const level = attrs.battery_level ?? attrs.battery;
161321
+ if (level == null || Number.isNaN(Number(level))) {
161322
+ return null;
161323
+ }
161324
+ return Number(level);
161325
+ }
161326
+ function getBatteryAlert(entity, agent) {
161327
+ const batteryEntity = agent.get(HomeAssistantEntityBehavior).state.mapping?.batteryEntity;
161328
+ if (batteryEntity?.startsWith("binary_sensor.")) {
161329
+ const state = agent.env.get(EntityStateProvider).getState(batteryEntity);
161330
+ return state?.state === "on" ? SmokeCoAlarm3.AlarmState.Warning : SmokeCoAlarm3.AlarmState.Normal;
161331
+ }
161332
+ const percent3 = getBatteryPercent(entity, agent);
161333
+ if (percent3 == null) return SmokeCoAlarm3.AlarmState.Normal;
161334
+ if (percent3 <= 10) return SmokeCoAlarm3.AlarmState.Critical;
161335
+ if (percent3 <= 20) return SmokeCoAlarm3.AlarmState.Warning;
161336
+ return SmokeCoAlarm3.AlarmState.Normal;
161337
+ }
161338
+ function getHardwareFault(agent) {
161339
+ const faultEntity = agent.get(HomeAssistantEntityBehavior).state.mapping?.faultEntity;
161340
+ if (!faultEntity) return false;
161341
+ return agent.env.get(EntityStateProvider).getState(faultEntity)?.state === "on";
161342
+ }
161343
+ function getExpressedState(primary, active, battery, fault) {
161344
+ if (primary !== SmokeCoAlarm3.AlarmState.Normal) return active;
161345
+ if (battery !== SmokeCoAlarm3.AlarmState.Normal)
161346
+ return SmokeCoAlarm3.ExpressedState.BatteryAlert;
161347
+ if (fault) return SmokeCoAlarm3.ExpressedState.HardwareFault;
161348
+ return SmokeCoAlarm3.ExpressedState.Normal;
161349
+ }
161286
161350
  var SmokeAlarmServerWithFeature = SmokeCoAlarmServer.with(SmokeCoAlarm3.Feature.SmokeAlarm);
161287
161351
  var CoAlarmServerWithFeature = SmokeCoAlarmServer.with(SmokeCoAlarm3.Feature.CoAlarm);
161288
161352
  var SmokeAlarmServerImpl = class extends SmokeAlarmServerWithFeature {
@@ -161294,8 +161358,19 @@ var SmokeAlarmServerImpl = class extends SmokeAlarmServerWithFeature {
161294
161358
  }
161295
161359
  update(entity) {
161296
161360
  const isOn = this.agent.get(HomeAssistantEntityBehavior).isAvailable && entity.state.state === "on";
161361
+ const smokeState = isOn ? SmokeCoAlarm3.AlarmState.Warning : SmokeCoAlarm3.AlarmState.Normal;
161362
+ const batteryAlert = getBatteryAlert(entity.state, this.agent);
161363
+ const hardwareFaultAlert = getHardwareFault(this.agent);
161297
161364
  applyPatchState(this.state, {
161298
- smokeState: isOn ? SmokeCoAlarm3.AlarmState.Warning : SmokeCoAlarm3.AlarmState.Normal
161365
+ smokeState,
161366
+ batteryAlert,
161367
+ hardwareFaultAlert,
161368
+ expressedState: getExpressedState(
161369
+ smokeState,
161370
+ SmokeCoAlarm3.ExpressedState.SmokeAlarm,
161371
+ batteryAlert,
161372
+ hardwareFaultAlert
161373
+ )
161299
161374
  });
161300
161375
  }
161301
161376
  };
@@ -161308,30 +161383,23 @@ var CoAlarmServerImpl = class extends CoAlarmServerWithFeature {
161308
161383
  }
161309
161384
  update(entity) {
161310
161385
  const isOn = this.agent.get(HomeAssistantEntityBehavior).isAvailable && entity.state.state === "on";
161386
+ const coState = isOn ? SmokeCoAlarm3.AlarmState.Warning : SmokeCoAlarm3.AlarmState.Normal;
161387
+ const batteryAlert = getBatteryAlert(entity.state, this.agent);
161388
+ const hardwareFaultAlert = getHardwareFault(this.agent);
161311
161389
  applyPatchState(this.state, {
161312
- coState: isOn ? SmokeCoAlarm3.AlarmState.Warning : SmokeCoAlarm3.AlarmState.Normal
161390
+ coState,
161391
+ batteryAlert,
161392
+ hardwareFaultAlert,
161393
+ expressedState: getExpressedState(
161394
+ coState,
161395
+ SmokeCoAlarm3.ExpressedState.CoAlarm,
161396
+ batteryAlert,
161397
+ hardwareFaultAlert
161398
+ )
161313
161399
  });
161314
161400
  }
161315
161401
  };
161316
- var AlarmPowerSourceServer = PowerSourceServer2({
161317
- getBatteryPercent: (entity, agent) => {
161318
- const homeAssistant = agent.get(HomeAssistantEntityBehavior);
161319
- const batteryEntity = homeAssistant.state.mapping?.batteryEntity;
161320
- if (batteryEntity) {
161321
- const stateProvider = agent.env.get(EntityStateProvider);
161322
- const battery = stateProvider.getBatteryPercent(batteryEntity);
161323
- if (battery != null) {
161324
- return Math.max(0, Math.min(100, battery));
161325
- }
161326
- }
161327
- const attrs = entity.attributes;
161328
- const level = attrs.battery_level ?? attrs.battery;
161329
- if (level == null || Number.isNaN(Number(level))) {
161330
- return null;
161331
- }
161332
- return Number(level);
161333
- }
161334
- });
161402
+ var AlarmPowerSourceServer = PowerSourceServer2({ getBatteryPercent });
161335
161403
  var SmokeAlarmType = SmokeCoAlarmDevice.with(
161336
161404
  BasicInformationServer2,
161337
161405
  IdentifyServer2,
@@ -164130,6 +164198,27 @@ function normalizeSupportedIndex(index) {
164130
164198
  }
164131
164199
  return null;
164132
164200
  }
164201
+ function buildUsercodeSetAction(service, slot, pin) {
164202
+ const pinKey = service.split(".")[0] === "zha" ? "user_code" : "usercode";
164203
+ return { action: service, data: { code_slot: slot, [pinKey]: pin } };
164204
+ }
164205
+ function buildUsercodeClearAction(service, slot) {
164206
+ return {
164207
+ action: service.replace(".set_", ".clear_"),
164208
+ data: { code_slot: slot }
164209
+ };
164210
+ }
164211
+ function usercodePassthroughFrom(homeAssistant) {
164212
+ const service = homeAssistant.state.mapping?.lockUsercodeService?.trim();
164213
+ if (!service) {
164214
+ return void 0;
164215
+ }
164216
+ return {
164217
+ service,
164218
+ slot: homeAssistant.state.mapping?.lockUsercodeSlot ?? 1,
164219
+ callAction: (action) => homeAssistant.callAction(action)
164220
+ };
164221
+ }
164133
164222
  function asFabricIndex(value) {
164134
164223
  return value === void 0 || value === 0 ? null : FabricIndex3(value);
164135
164224
  }
@@ -164220,7 +164309,7 @@ function buildGetCredentialStatusResponse(env, entityId, request) {
164220
164309
  nextCredentialIndex: null
164221
164310
  };
164222
164311
  }
164223
- async function applySetCredential(env, entityId, request, fabricIndex) {
164312
+ async function applySetCredential(env, entityId, request, fabricIndex, passthrough) {
164224
164313
  if (request.credential.credentialType !== DoorLock3.CredentialType.Pin || normalizeSupportedIndex(request.credential.credentialIndex) === null) {
164225
164314
  return {
164226
164315
  status: Status2.Failure,
@@ -164246,6 +164335,9 @@ async function applySetCredential(env, entityId, request, fabricIndex) {
164246
164335
  lastModifiedFabricIndex: fabricIndex,
164247
164336
  creatorFabricIndex: fabricIndex
164248
164337
  });
164338
+ passthrough?.callAction(
164339
+ buildUsercodeSetAction(passthrough.service, passthrough.slot, pinCode)
164340
+ );
164249
164341
  }
164250
164342
  return {
164251
164343
  status: Status2.Success,
@@ -164265,15 +164357,35 @@ async function applySetUser(env, entityId, request, fabricIndex) {
164265
164357
  fabricIndex
164266
164358
  });
164267
164359
  }
164268
- async function applyClearCredential(env, entityId, request) {
164360
+ async function applyClearCredential(env, entityId, request, passthrough) {
164269
164361
  if (request.credential === null) {
164270
164362
  const storage2 = env.get(LockCredentialStorage);
164271
164363
  await storage2.deleteCredential(entityId);
164364
+ passthrough?.callAction(
164365
+ buildUsercodeClearAction(passthrough.service, passthrough.slot)
164366
+ );
164272
164367
  return;
164273
164368
  }
164274
164369
  if (request.credential.credentialType === DoorLock3.CredentialType.Pin && normalizeSupportedIndex(request.credential.credentialIndex) !== null) {
164275
164370
  const storage2 = env.get(LockCredentialStorage);
164276
164371
  await storage2.deleteCredential(entityId);
164372
+ passthrough?.callAction(
164373
+ buildUsercodeClearAction(passthrough.service, passthrough.slot)
164374
+ );
164375
+ }
164376
+ }
164377
+ async function applyClearUser(env, entityId, request, passthrough) {
164378
+ const slot = normalizeSupportedIndex(request.userIndex);
164379
+ if (slot === null && request.userIndex !== 65534) {
164380
+ return;
164381
+ }
164382
+ const storage2 = env.get(LockCredentialStorage);
164383
+ const hadCredential = storage2.hasCredential(entityId);
164384
+ await storage2.deleteCredential(entityId);
164385
+ if (hadCredential) {
164386
+ passthrough?.callAction(
164387
+ buildUsercodeClearAction(passthrough.service, passthrough.slot)
164388
+ );
164277
164389
  }
164278
164390
  }
164279
164391
  var LockServerBase = class extends DoorLockServer {
@@ -164449,12 +164561,13 @@ var LockServerWithPinBase = class extends PinCredentialBase {
164449
164561
  );
164450
164562
  }
164451
164563
  async clearUser(request) {
164452
- const slot = normalizeSupportedIndex(request.userIndex);
164453
- if (slot !== null || request.userIndex === 65534) {
164454
- const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164455
- const storage2 = this.env.get(LockCredentialStorage);
164456
- await storage2.deleteCredential(homeAssistant.entityId);
164457
- }
164564
+ const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164565
+ await applyClearUser(
164566
+ this.env,
164567
+ homeAssistant.entityId,
164568
+ request,
164569
+ usercodePassthroughFrom(homeAssistant)
164570
+ );
164458
164571
  }
164459
164572
  async setCredential(request) {
164460
164573
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
@@ -164462,7 +164575,8 @@ var LockServerWithPinBase = class extends PinCredentialBase {
164462
164575
  this.env,
164463
164576
  homeAssistant.entityId,
164464
164577
  request,
164465
- this.context.fabric
164578
+ this.context.fabric,
164579
+ usercodePassthroughFrom(homeAssistant)
164466
164580
  );
164467
164581
  }
164468
164582
  getCredentialStatus(request) {
@@ -164475,7 +164589,12 @@ var LockServerWithPinBase = class extends PinCredentialBase {
164475
164589
  }
164476
164590
  async clearCredential(request) {
164477
164591
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164478
- await applyClearCredential(this.env, homeAssistant.entityId, request);
164592
+ await applyClearCredential(
164593
+ this.env,
164594
+ homeAssistant.entityId,
164595
+ request,
164596
+ usercodePassthroughFrom(homeAssistant)
164597
+ );
164479
164598
  }
164480
164599
  };
164481
164600
  ((LockServerWithPinBase2) => {
@@ -164645,12 +164764,13 @@ var LockServerWithPinAndUnboltBase = class extends PinCredentialUnboltBase {
164645
164764
  );
164646
164765
  }
164647
164766
  async clearUser(request) {
164648
- const slot = normalizeSupportedIndex(request.userIndex);
164649
- if (slot !== null || request.userIndex === 65534) {
164650
- const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164651
- const storage2 = this.env.get(LockCredentialStorage);
164652
- await storage2.deleteCredential(homeAssistant.entityId);
164653
- }
164767
+ const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164768
+ await applyClearUser(
164769
+ this.env,
164770
+ homeAssistant.entityId,
164771
+ request,
164772
+ usercodePassthroughFrom(homeAssistant)
164773
+ );
164654
164774
  }
164655
164775
  async setCredential(request) {
164656
164776
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
@@ -164658,7 +164778,8 @@ var LockServerWithPinAndUnboltBase = class extends PinCredentialUnboltBase {
164658
164778
  this.env,
164659
164779
  homeAssistant.entityId,
164660
164780
  request,
164661
- this.context.fabric
164781
+ this.context.fabric,
164782
+ usercodePassthroughFrom(homeAssistant)
164662
164783
  );
164663
164784
  }
164664
164785
  getCredentialStatus(request) {
@@ -164671,7 +164792,12 @@ var LockServerWithPinAndUnboltBase = class extends PinCredentialUnboltBase {
164671
164792
  }
164672
164793
  async clearCredential(request) {
164673
164794
  const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
164674
- await applyClearCredential(this.env, homeAssistant.entityId, request);
164795
+ await applyClearCredential(
164796
+ this.env,
164797
+ homeAssistant.entityId,
164798
+ request,
164799
+ usercodePassthroughFrom(homeAssistant)
164800
+ );
164675
164801
  }
164676
164802
  };
164677
164803
  ((LockServerWithPinAndUnboltBase2) => {
@@ -170038,6 +170164,21 @@ var LegacyEndpoint = class _LegacyEndpoint extends EntityEndpoint {
170038
170164
  );
170039
170165
  }
170040
170166
  }
170167
+ const alarmDeviceClass = state.attributes.device_class;
170168
+ const isSmokeCoAlarm = mapping?.matterDeviceType === "smoke_co_alarm" || entityId.startsWith("binary_sensor.") && (alarmDeviceClass === "smoke" || alarmDeviceClass === "carbon_monoxide" || alarmDeviceClass === "gas");
170169
+ if (registry2.isAutoBatteryMappingEnabled() && !mapping?.faultEntity && isSmokeCoAlarm) {
170170
+ const faultEntityId = registry2.findProblemEntityForDevice(
170171
+ entity.device_id
170172
+ );
170173
+ if (faultEntityId && faultEntityId !== entityId) {
170174
+ effectiveMapping = {
170175
+ ...effectiveMapping,
170176
+ entityId: effectiveMapping?.entityId ?? entityId,
170177
+ faultEntity: faultEntityId
170178
+ };
170179
+ logger240.debug(`Auto-assigned fault ${faultEntityId} to ${entityId}`);
170180
+ }
170181
+ }
170041
170182
  if (!mapping?.powerEntity) {
170042
170183
  const domain = entityId.split(".")[0];
170043
170184
  if (domain === "switch") {
@@ -170379,7 +170520,11 @@ init_esm();
170379
170520
  init_esm3();
170380
170521
 
170381
170522
  // src/plugins/builtin/camera/av-stream-server.ts
170382
- var Base4 = CameraAvStreamManagementServer.with("Video", "Audio", "Snapshot");
170523
+ var Base4 = CameraAvStreamManagementServer.with(
170524
+ "Video",
170525
+ "Snapshot",
170526
+ "ImageControl"
170527
+ );
170383
170528
  var CameraAvStreamServer = class extends Base4 {
170384
170529
  setStreamPriorities(request) {
170385
170530
  this.state.streamUsagePriorities = request.streamPriorities;
@@ -170411,28 +170556,6 @@ var CameraAvStreamServer = class extends Base4 {
170411
170556
  (s) => s.videoStreamId !== request.videoStreamId
170412
170557
  );
170413
170558
  }
170414
- audioStreamAllocate(request) {
170415
- const audioStreamId = this.state.nextAudioStreamId++;
170416
- this.state.allocatedAudioStreams = [
170417
- ...this.state.allocatedAudioStreams,
170418
- {
170419
- audioStreamId,
170420
- streamUsage: request.streamUsage,
170421
- audioCodec: request.audioCodec,
170422
- channelCount: request.channelCount,
170423
- sampleRate: request.sampleRate,
170424
- bitRate: request.bitRate,
170425
- bitDepth: request.bitDepth,
170426
- referenceCount: 1
170427
- }
170428
- ];
170429
- return { audioStreamId };
170430
- }
170431
- audioStreamDeallocate(request) {
170432
- this.state.allocatedAudioStreams = this.state.allocatedAudioStreams.filter(
170433
- (s) => s.audioStreamId !== request.audioStreamId
170434
- );
170435
- }
170436
170559
  snapshotStreamAllocate(request) {
170437
170560
  const snapshotStreamId = this.state.nextSnapshotStreamId++;
170438
170561
  this.state.allocatedSnapshotStreams = [
@@ -170470,7 +170593,6 @@ var CameraAvStreamServer = class extends Base4 {
170470
170593
  bridge;
170471
170594
  entityId;
170472
170595
  nextVideoStreamId = 1;
170473
- nextAudioStreamId = 1;
170474
170596
  nextSnapshotStreamId = 1;
170475
170597
  }
170476
170598
  CameraAvStreamServer2.State = State;
@@ -170550,7 +170672,12 @@ var CameraWebRtcProviderServer = class extends WebRtcTransportProviderServer {
170550
170672
  })(CameraWebRtcProviderServer || (CameraWebRtcProviderServer = {}));
170551
170673
 
170552
170674
  // src/plugins/builtin/camera/camera-endpoint.ts
170553
- function createCameraEndpointType(bridge, entityId) {
170675
+ var defaultSensorParams = {
170676
+ sensorWidth: 1920,
170677
+ sensorHeight: 1080,
170678
+ maxFps: 30
170679
+ };
170680
+ function createCameraEndpointType(bridge, entityId, sensor = defaultSensorParams) {
170554
170681
  return CameraDevice.with(
170555
170682
  CameraAvStreamServer,
170556
170683
  CameraWebRtcProviderServer
@@ -170567,14 +170694,54 @@ function createCameraEndpointType(bridge, entityId) {
170567
170694
  supportedStreamUsages: [StreamUsage.LiveView],
170568
170695
  streamUsagePriorities: [StreamUsage.LiveView],
170569
170696
  allocatedVideoStreams: [],
170570
- allocatedAudioStreams: [],
170571
170697
  allocatedSnapshotStreams: [],
170572
170698
  maxConcurrentEncoders: 1,
170573
170699
  maxEncodedPixelRate: 3840 * 2160 * 30,
170574
- maxNetworkBandwidth: 2e4,
170700
+ maxNetworkBandwidth: 2e7,
170701
+ // 20 Mbps, attribute is bits per second
170575
170702
  currentFrameRate: 0,
170703
+ videoSensorParams: {
170704
+ sensorWidth: sensor.sensorWidth,
170705
+ sensorHeight: sensor.sensorHeight,
170706
+ maxFps: sensor.maxFps
170707
+ },
170708
+ viewport: {
170709
+ x1: 0,
170710
+ y1: 0,
170711
+ x2: sensor.sensorWidth,
170712
+ y2: sensor.sensorHeight
170713
+ },
170714
+ minViewportResolution: { width: 64, height: 64 },
170715
+ rateDistortionTradeOffPoints: [
170716
+ {
170717
+ codec: CameraAvStreamManagement3.VideoCodec.H264,
170718
+ resolution: {
170719
+ width: sensor.sensorWidth,
170720
+ height: sensor.sensorHeight
170721
+ },
170722
+ minBitRate: 1e4
170723
+ }
170724
+ ],
170725
+ maxContentBufferSize: 1048576,
170726
+ // one snapshot jpeg in flight
170727
+ // ImageControl attributes: matter.js 0.17.5 conformance forces the
170728
+ // feature. Controllers can write these but the proxied HA media path
170729
+ // never applies a transform.
170730
+ imageRotation: 0,
170731
+ imageFlipHorizontal: false,
170732
+ imageFlipVertical: false,
170733
+ snapshotCapabilities: [
170734
+ {
170735
+ resolution: {
170736
+ width: sensor.sensorWidth,
170737
+ height: sensor.sensorHeight
170738
+ },
170739
+ maxFrameRate: 1,
170740
+ imageCodec: CameraAvStreamManagement3.ImageCodec.Jpeg,
170741
+ requiresEncodedPixels: false
170742
+ }
170743
+ ],
170576
170744
  nextVideoStreamId: 1,
170577
- nextAudioStreamId: 1,
170578
170745
  nextSnapshotStreamId: 1
170579
170746
  }
170580
170747
  });
@@ -170787,6 +170954,27 @@ var CameraPlugin = class {
170787
170954
  title: "Camera entity ids (comma-separated)",
170788
170955
  description: "e.g. camera.front,camera.garage",
170789
170956
  required: true
170957
+ },
170958
+ sensorWidth: {
170959
+ type: "number",
170960
+ title: "Sensor width (px)",
170961
+ description: "Video sensor width reported to controllers.",
170962
+ default: defaultSensorParams.sensorWidth,
170963
+ required: false
170964
+ },
170965
+ sensorHeight: {
170966
+ type: "number",
170967
+ title: "Sensor height (px)",
170968
+ description: "Video sensor height reported to controllers.",
170969
+ default: defaultSensorParams.sensorHeight,
170970
+ required: false
170971
+ },
170972
+ maxFps: {
170973
+ type: "number",
170974
+ title: "Max frame rate (fps)",
170975
+ description: "Maximum frame rate the sensor supports.",
170976
+ default: defaultSensorParams.maxFps,
170977
+ required: false
170790
170978
  }
170791
170979
  }
170792
170980
  };
@@ -170808,19 +170996,37 @@ var CameraPlugin = class {
170808
170996
  this.log.info("no camera entity ids configured");
170809
170997
  return;
170810
170998
  }
170999
+ const sensor = this.sensorParams();
170811
171000
  this.bridge = new WebRtcBridge({ haUrl, haToken });
170812
171001
  for (const entityId of entityIds) {
170813
171002
  const id = entityId.replace(/\./g, "_");
170814
171003
  await context.registerDevice({
170815
171004
  id,
170816
171005
  name: entityId,
170817
- endpointType: createCameraEndpointType(this.bridge, entityId),
171006
+ endpointType: createCameraEndpointType(this.bridge, entityId, sensor),
170818
171007
  clusters: []
170819
171008
  });
170820
171009
  this.deviceIds.push(id);
170821
171010
  }
170822
171011
  this.log.info(`Exposed ${this.deviceIds.length} camera(s)`);
170823
171012
  }
171013
+ // Clamp the configured sensor to values the cluster accepts; a bad or missing
171014
+ // entry falls back to the default for that field.
171015
+ sensorParams() {
171016
+ const dim = (n, fallback) => typeof n === "number" && Number.isFinite(n) ? Math.min(65535, Math.max(64, Math.floor(n))) : fallback;
171017
+ const fps = (n, fallback) => typeof n === "number" && Number.isFinite(n) ? Math.min(65535, Math.max(1, Math.floor(n))) : fallback;
171018
+ return {
171019
+ sensorWidth: dim(
171020
+ this.config.sensorWidth,
171021
+ defaultSensorParams.sensorWidth
171022
+ ),
171023
+ sensorHeight: dim(
171024
+ this.config.sensorHeight,
171025
+ defaultSensorParams.sensorHeight
171026
+ ),
171027
+ maxFps: fps(this.config.maxFps, defaultSensorParams.maxFps)
171028
+ };
171029
+ }
170824
171030
  async teardown() {
170825
171031
  for (const id of this.deviceIds) {
170826
171032
  await this.context?.unregisterDevice(id).catch(() => {
@@ -171752,6 +171958,8 @@ var BridgeRegistry = class _BridgeRegistry {
171752
171958
  _usedBatteryEntities = /* @__PURE__ */ new Set();
171753
171959
  // Cache for battery entity lookups (deviceId -> entityId or null)
171754
171960
  _batteryEntityCache = /* @__PURE__ */ new Map();
171961
+ // Cache for problem entity lookups (deviceId -> entityId or null) (#408)
171962
+ _problemEntityCache = /* @__PURE__ */ new Map();
171755
171963
  // Track humidity entities that have been auto-assigned to temperature sensors
171756
171964
  _usedHumidityEntities = /* @__PURE__ */ new Set();
171757
171965
  // Track pressure entities that have been auto-assigned to temperature sensors
@@ -171845,6 +172053,35 @@ var BridgeRegistry = class _BridgeRegistry {
171845
172053
  isBatteryEntityUsed(entityId) {
171846
172054
  return this._usedBatteryEntities.has(entityId);
171847
172055
  }
172056
+ /**
172057
+ * Find a problem/safety binary sensor on the same HA device, so a smoke/CO
172058
+ * alarm can drive hardwareFaultAlert from it. Prefers device_class=problem
172059
+ * over safety. Returns the entity_id, or undefined if none found (#408).
172060
+ */
172061
+ findProblemEntityForDevice(deviceId) {
172062
+ if (this._problemEntityCache.has(deviceId)) {
172063
+ const cached = this._problemEntityCache.get(deviceId);
172064
+ return cached === null ? void 0 : cached;
172065
+ }
172066
+ const entities = values3(this.registry.entities);
172067
+ const sameDevice = entities.filter((e) => e.device_id === deviceId);
172068
+ let safety;
172069
+ for (const entity of sameDevice) {
172070
+ if (!entity.entity_id.startsWith("binary_sensor.")) continue;
172071
+ const state = this.registry.states[entity.entity_id];
172072
+ if (!state) continue;
172073
+ const attrs = state.attributes;
172074
+ if (attrs.device_class === "problem") {
172075
+ this._problemEntityCache.set(deviceId, entity.entity_id);
172076
+ return entity.entity_id;
172077
+ }
172078
+ if (attrs.device_class === "safety" && !safety) {
172079
+ safety = entity.entity_id;
172080
+ }
172081
+ }
172082
+ this._problemEntityCache.set(deviceId, safety ?? null);
172083
+ return safety;
172084
+ }
171848
172085
  /**
171849
172086
  * Check if auto battery mapping is enabled for this bridge.
171850
172087
  */
@@ -172303,6 +172540,7 @@ var BridgeRegistry = class _BridgeRegistry {
172303
172540
  this._usedEnergyEntities.clear();
172304
172541
  this._usedComposedSubEntities.clear();
172305
172542
  this._batteryEntityCache.clear();
172543
+ this._problemEntityCache.clear();
172306
172544
  this._entities = pickBy(this.registry.entities, (entity) => {
172307
172545
  const device = this.registry.devices[entity.device_id];
172308
172546
  const filter = this.dataProvider.filter;