@riddix/hamh 2.1.0-alpha.740 → 2.1.0-alpha.742

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.
@@ -124034,6 +124034,154 @@ var init_clusters = __esm({
124034
124034
  }
124035
124035
  });
124036
124036
 
124037
+ // ../common/dist/controller-compat.js
124038
+ function classifyController(vendorId3) {
124039
+ return controllerByVendorId[vendorId3];
124040
+ }
124041
+ function computeControllerWarnings(controllers, exposed) {
124042
+ const seen = /* @__PURE__ */ new Set();
124043
+ const warnings = [];
124044
+ for (const { entityId, deviceTypeId } of exposed) {
124045
+ const support = deviceTypeIdSupport[deviceTypeId];
124046
+ if (!support)
124047
+ continue;
124048
+ for (const controller of controllers) {
124049
+ if (support[controller] !== "no")
124050
+ continue;
124051
+ const key = `${entityId}:${deviceTypeId}:${controller}`;
124052
+ if (seen.has(key))
124053
+ continue;
124054
+ seen.add(key);
124055
+ warnings.push({
124056
+ entityId,
124057
+ deviceTypeId,
124058
+ controller,
124059
+ controllerLabel: controllerLabels[controller],
124060
+ note: support.note
124061
+ });
124062
+ }
124063
+ }
124064
+ return warnings;
124065
+ }
124066
+ var controllerByVendorId, deviceTypeIdSupport, controllerLabels;
124067
+ var init_controller_compat = __esm({
124068
+ "../common/dist/controller-compat.js"() {
124069
+ "use strict";
124070
+ controllerByVendorId = {
124071
+ 4937: "apple",
124072
+ // 0x1349 Apple Home
124073
+ 4996: "apple",
124074
+ // 0x1384 Apple (iCloud Keychain)
124075
+ 24582: "google",
124076
+ // 0x6006 Google Home
124077
+ 4631: "alexa",
124078
+ // 0x1217 Amazon Alexa
124079
+ 4448: "alexa"
124080
+ // 0x1160 Amazon (some Alexa ecosystems)
124081
+ };
124082
+ deviceTypeIdSupport = {
124083
+ 43: {
124084
+ apple: "no",
124085
+ google: "yes",
124086
+ alexa: "yes",
124087
+ note: "Apple Home has no standalone fan."
124088
+ },
124089
+ 45: {
124090
+ apple: "no",
124091
+ google: "yes",
124092
+ alexa: "yes",
124093
+ note: "Apple Home does not list air purifiers."
124094
+ },
124095
+ 34: {
124096
+ apple: "no",
124097
+ google: "yes",
124098
+ alexa: "no",
124099
+ note: "Only Google Home shows Matter speakers."
124100
+ },
124101
+ 40: {
124102
+ apple: "no",
124103
+ google: "no",
124104
+ alexa: "no",
124105
+ note: "TV/media types are not shown by these controllers."
124106
+ },
124107
+ 773: {
124108
+ apple: "no",
124109
+ google: "yes",
124110
+ alexa: "no",
124111
+ note: "Only Google Home shows pressure sensors."
124112
+ },
124113
+ 774: {
124114
+ apple: "no",
124115
+ google: "yes",
124116
+ alexa: "no",
124117
+ note: "Only Google Home shows flow sensors."
124118
+ },
124119
+ 44: {
124120
+ apple: "no",
124121
+ google: "yes",
124122
+ alexa: "yes",
124123
+ note: "Apple Home does not show air quality."
124124
+ },
124125
+ 23: {
124126
+ apple: "no",
124127
+ google: "no",
124128
+ alexa: "unknown",
124129
+ note: "Power/energy is rarely shown unless it is on a smart plug."
124130
+ },
124131
+ 24: {
124132
+ apple: "no",
124133
+ google: "no",
124134
+ alexa: "no",
124135
+ note: "Battery is usually shown inside a device, not on its own."
124136
+ },
124137
+ 39: {
124138
+ apple: "no",
124139
+ google: "no",
124140
+ alexa: "no",
124141
+ note: "Mode Select is not supported here (Google #356)."
124142
+ },
124143
+ 66: { apple: "no", google: "no", alexa: "no" },
124144
+ 771: {
124145
+ apple: "no",
124146
+ google: "yes",
124147
+ alexa: "no",
124148
+ note: "Only Google Home shows pumps."
124149
+ },
124150
+ 68: {
124151
+ apple: "no",
124152
+ google: "no",
124153
+ alexa: "no",
124154
+ note: "Newer Matter detector, thin support; Alexa may reject it (#365)."
124155
+ },
124156
+ 65: {
124157
+ apple: "no",
124158
+ google: "no",
124159
+ alexa: "no",
124160
+ note: "Newer Matter detector, thin support; Alexa may reject it (#365)."
124161
+ },
124162
+ 67: {
124163
+ apple: "yes",
124164
+ google: "no",
124165
+ alexa: "yes",
124166
+ note: "Newer Matter detector, can be risky on Alexa bridges (#365)."
124167
+ },
124168
+ 118: { apple: "yes", google: "no", alexa: "yes" },
124169
+ 117: {
124170
+ apple: "no",
124171
+ google: "no",
124172
+ alexa: "unknown",
124173
+ note: "Appliance types have little controller support."
124174
+ },
124175
+ 15: { apple: "partial", google: "no", alexa: "yes" }
124176
+ };
124177
+ controllerLabels = {
124178
+ apple: "Apple Home",
124179
+ google: "Google Home",
124180
+ alexa: "Alexa"
124181
+ };
124182
+ }
124183
+ });
124184
+
124037
124185
  // ../common/dist/controller-profiles.js
124038
124186
  var init_controller_profiles = __esm({
124039
124187
  "../common/dist/controller-profiles.js"() {
@@ -125070,6 +125218,7 @@ var init_dist = __esm({
125070
125218
  init_bridge_export();
125071
125219
  init_bridge_templates();
125072
125220
  init_clusters();
125221
+ init_controller_compat();
125073
125222
  init_controller_profiles();
125074
125223
  init_diagnostic_event();
125075
125224
  init_domains();
@@ -126858,6 +127007,7 @@ function entityMappingApi(mappingStorage) {
126858
127007
  }
126859
127008
 
126860
127009
  // src/api/health-api.ts
127010
+ init_dist();
126861
127011
  import express8 from "express";
126862
127012
  function healthApi(bridgeService, haClient, version2, startTime) {
126863
127013
  const router = express8.Router();
@@ -126903,6 +127053,12 @@ function healthApi(bridgeService, haClient, version2, startTime) {
126903
127053
  const data = b.data;
126904
127054
  const fabrics = data.commissioning?.fabrics ?? [];
126905
127055
  const sessionInfo = b.getSessionInfo();
127056
+ const controllers = [
127057
+ ...new Set(
127058
+ fabrics.map((f) => classifyController(f.rootVendorId)).filter((c) => c !== void 0)
127059
+ )
127060
+ ];
127061
+ const controllerWarnings = controllers.length > 0 ? computeControllerWarnings(controllers, b.getExposedDeviceTypes()) : [];
126906
127062
  return {
126907
127063
  id: data.id,
126908
127064
  name: data.name,
@@ -126921,6 +127077,7 @@ function healthApi(bridgeService, haClient, version2, startTime) {
126921
127077
  rootVendorId: f.rootVendorId
126922
127078
  })),
126923
127079
  failedEntityCount: data.failedEntities?.length ?? 0,
127080
+ controllerWarnings,
126924
127081
  connectivity: {
126925
127082
  totalSessions: sessionInfo.totalSessions,
126926
127083
  totalSubscriptions: sessionInfo.totalSubscriptions,
@@ -148310,6 +148467,28 @@ var Bridge = class {
148310
148467
  get aggregator() {
148311
148468
  return this.endpointManager.root;
148312
148469
  }
148470
+ /**
148471
+ * The entity id and numeric Matter device type of every exposed endpoint,
148472
+ * walking composed sub-endpoints. Used to warn when a device type is not
148473
+ * supported by a commissioned controller (#365 class).
148474
+ */
148475
+ getExposedDeviceTypes() {
148476
+ const out = [];
148477
+ const collect = (ep, inheritedEntityId) => {
148478
+ const entityId = ep.entityId ?? inheritedEntityId;
148479
+ const deviceTypeId = ep.type?.deviceType;
148480
+ if (typeof deviceTypeId === "number" && entityId) {
148481
+ out.push({ entityId, deviceTypeId });
148482
+ }
148483
+ for (const child of ep.parts) {
148484
+ collect(child, entityId);
148485
+ }
148486
+ };
148487
+ for (const ep of this.aggregator.parts) {
148488
+ collect(ep);
148489
+ }
148490
+ return out;
148491
+ }
148313
148492
  get pluginInfo() {
148314
148493
  return this.endpointManager.getPluginInfo();
148315
148494
  }
@@ -151212,10 +151391,16 @@ var ThermostatServerBase = class extends FullFeaturedBase {
151212
151391
  if (this.features.heating) {
151213
151392
  minHeatLimit = minSetpointLimit ?? WIDE_MIN;
151214
151393
  maxHeatLimit = maxSetpointLimit ?? WIDE_MAX;
151394
+ if (minHeatLimit > maxHeatLimit) {
151395
+ [minHeatLimit, maxHeatLimit] = [maxHeatLimit, minHeatLimit];
151396
+ }
151215
151397
  }
151216
151398
  if (this.features.cooling) {
151217
151399
  minCoolLimit = minSetpointLimit ?? WIDE_MIN;
151218
151400
  maxCoolLimit = maxSetpointLimit ?? WIDE_MAX;
151401
+ if (minCoolLimit > maxCoolLimit) {
151402
+ [minCoolLimit, maxCoolLimit] = [maxCoolLimit, minCoolLimit];
151403
+ }
151219
151404
  }
151220
151405
  const clampedHeatingSetpoint = this.clampSetpoint(
151221
151406
  targetHeatingTemperature,
@@ -152028,6 +152213,10 @@ function ClimateDevice(homeAssistantEntity, includeBasicInformation = true) {
152028
152213
  rockLeftRight: swingModesRockSupport.rockLeftRight || supportsHorizontalSwing || void 0,
152029
152214
  rockUpDown: swingModesRockSupport.rockUpDown || supportsVerticalSwing || void 0
152030
152215
  } : void 0;
152216
+ const rawMinLimit = toMatterTemp(attributes8.min_temp) ?? 0;
152217
+ const rawMaxLimit = toMatterTemp(attributes8.max_temp) ?? 5e3;
152218
+ const minLimit = Math.min(rawMinLimit, rawMaxLimit);
152219
+ const maxLimit = Math.max(rawMinLimit, rawMaxLimit);
152031
152220
  const initialState = {
152032
152221
  // Pass actual current_temperature for initial state.
152033
152222
  // If unavailable (null/undefined), update() will fall back to the
@@ -152035,11 +152224,12 @@ function ClimateDevice(homeAssistantEntity, includeBasicInformation = true) {
152035
152224
  localTemperature: toMatterTemp(attributes8.current_temperature),
152036
152225
  occupiedHeatingSetpoint: toMatterTemp(attributes8.target_temp_low) ?? toMatterTemp(attributes8.temperature) ?? 2e3,
152037
152226
  occupiedCoolingSetpoint: toMatterTemp(attributes8.target_temp_high) ?? toMatterTemp(attributes8.temperature) ?? 2400,
152038
- // Use HA's actual min/max limits, fall back to wide range (0-50°C) if not provided
152039
- minHeatSetpointLimit: toMatterTemp(attributes8.min_temp) ?? 0,
152040
- maxHeatSetpointLimit: toMatterTemp(attributes8.max_temp) ?? 5e3,
152041
- minCoolSetpointLimit: toMatterTemp(attributes8.min_temp) ?? 0,
152042
- maxCoolSetpointLimit: toMatterTemp(attributes8.max_temp) ?? 5e3
152227
+ // Use HA's actual min/max limits, fall back to wide range (0-50°C) if not
152228
+ // provided. Ordered above so min <= max always holds.
152229
+ minHeatSetpointLimit: minLimit,
152230
+ maxHeatSetpointLimit: maxLimit,
152231
+ minCoolSetpointLimit: minLimit,
152232
+ maxCoolSetpointLimit: maxLimit
152043
152233
  };
152044
152234
  const autoMode = supportsHeating && supportsCooling && attributes8.hvac_modes.includes(ClimateHvacMode.heat_cool) && (attributes8.hvac_modes.includes(ClimateHvacMode.heat) || attributes8.hvac_modes.includes(ClimateHvacMode.cool));
152045
152235
  return ClimateDeviceType(
@@ -163435,6 +163625,28 @@ var ServerModeBridge = class {
163435
163625
  this.endpointManager.failedEntities
163436
163626
  );
163437
163627
  }
163628
+ /**
163629
+ * The entity id and numeric Matter device type of every exposed device, so a
163630
+ * controller-support warning can be raised per bridge (#365 class). Server
163631
+ * mode is flat, but walk parts anyway to stay correct.
163632
+ */
163633
+ getExposedDeviceTypes() {
163634
+ const out = [];
163635
+ const collect = (ep, inheritedEntityId) => {
163636
+ const entityId = ep.entityId ?? inheritedEntityId;
163637
+ const deviceTypeId = ep.type?.deviceType;
163638
+ if (typeof deviceTypeId === "number" && entityId) {
163639
+ out.push({ entityId, deviceTypeId });
163640
+ }
163641
+ for (const child of ep.parts) {
163642
+ collect(child, entityId);
163643
+ }
163644
+ };
163645
+ for (const device of this.endpointManager.devices) {
163646
+ collect(device);
163647
+ }
163648
+ return out;
163649
+ }
163438
163650
  getSessionInfo() {
163439
163651
  try {
163440
163652
  const sessionManager = this.server.env.get(SessionManager);