@camstack/addon-provider-homeassistant 1.2.625 → 1.2.627

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.
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  enumerable: true
25
25
  }) : target, mod));
26
26
  //#endregion
27
- const require_dist = require("../dist-oNUlRdur.js");
27
+ const require_dist = require("../dist-CRwUHjY7.js");
28
28
  let node_crypto = require("node:crypto");
29
29
  require("node:zlib");
30
30
  let node_fs = require("node:fs");
@@ -731,12 +731,6 @@ var STATUS_UNKNOWN = "unknown";
731
731
  function consumableEntity(key, facet) {
732
732
  return `${CONSUMABLE_PREFIX}_${toSlug(key)}_${facet}`;
733
733
  }
734
- /** Home Assistant's own duration units; a cycle count is a plain count. */
735
- var REMAINING_UNIT = {
736
- days: "d",
737
- hours: "h",
738
- cycles: "cycles"
739
- };
740
734
  /**
741
735
  * The item's form (the capability's own derivation, `consumableFormOf`): the one
742
736
  * the provider declared when it is a form the capability knows, else the one its
@@ -829,7 +823,7 @@ function consumableEntities(item) {
829
823
  entityCategory: "diagnostic"
830
824
  });
831
825
  if (item.remaining !== void 0) {
832
- const isDuration = item.remaining.unit !== "cycles";
826
+ const isDuration = item.remaining.kind === "duration";
833
827
  out.push({
834
828
  ...base,
835
829
  facet: "remaining",
@@ -837,7 +831,7 @@ function consumableEntities(item) {
837
831
  uniqueSuffix: unique("remaining"),
838
832
  platform: "sensor",
839
833
  label: `${item.label} remaining`,
840
- unit: REMAINING_UNIT[item.remaining.unit],
834
+ ...isDuration ? { unit: "s" } : {},
841
835
  stateClass: "measurement",
842
836
  ...isDuration ? { deviceClass: "duration" } : {},
843
837
  icon: isDuration ? "mdi:timer-sand" : "mdi:counter",
@@ -3184,7 +3178,7 @@ mireds: {
3184
3178
  platform: "sensor",
3185
3179
  field: "desiccantLeftDays",
3186
3180
  label: "Desiccant remaining",
3187
- unit: "d",
3181
+ unit: "s",
3188
3182
  entityCategory: "diagnostic",
3189
3183
  enabledByDefault: false
3190
3184
  },
@@ -3198,11 +3192,8 @@ mireds: {
3198
3192
  }
3199
3193
  },
3200
3194
  /**
3201
- * The cap carries its OWN unit next to each measurement
3202
- * (`temperatureUnit`, `pressureUnit`, `windSpeedUnit`), so no static
3203
- * `unit_of_measurement` can be honest for a provider reporting °F.
3204
- * Declaring one anyway is how a dashboard comes to say 71 °C. Humidity
3205
- * is the exception: a percentage is a percentage.
3195
+ * Weather numbers are stored in the quantity base (°C, hPa, m/s). The
3196
+ * export advertises those symbols. A Fahrenheit dashboard converts at read.
3206
3197
  */
3207
3198
  weather: {
3208
3199
  platform: "sensor",
@@ -3213,6 +3204,8 @@ mireds: {
3213
3204
  platform: "sensor",
3214
3205
  field: "temperature",
3215
3206
  label: "Temperature",
3207
+ unit: "°C",
3208
+ deviceClass: "temperature",
3216
3209
  icon: "mdi:thermometer"
3217
3210
  },
3218
3211
  humidity: {
@@ -3226,6 +3219,8 @@ mireds: {
3226
3219
  platform: "sensor",
3227
3220
  field: "pressure",
3228
3221
  label: "Pressure",
3222
+ unit: "hPa",
3223
+ deviceClass: "pressure",
3229
3224
  icon: "mdi:gauge",
3230
3225
  enabledByDefault: false
3231
3226
  },
@@ -3233,6 +3228,8 @@ mireds: {
3233
3228
  platform: "sensor",
3234
3229
  field: "windSpeed",
3235
3230
  label: "Wind speed",
3231
+ unit: "m/s",
3232
+ deviceClass: "wind_speed",
3236
3233
  icon: "mdi:weather-windy",
3237
3234
  enabledByDefault: false
3238
3235
  },
@@ -3443,7 +3440,7 @@ function readFiniteNumber(slice, field) {
3443
3440
  * builds with nothing to choose from — so an empty list falls back to a
3444
3441
  * read-only sensor rather than a dead control.
3445
3442
  */
3446
- function controlEntitySpecs(slice) {
3443
+ function controlEntitySpecs(slice, quantity) {
3447
3444
  const kind = typeof slice?.["kind"] === "string" ? slice["kind"] : null;
3448
3445
  const primary = derivedEntityId("control");
3449
3446
  const kindExtra = {
@@ -3468,7 +3465,7 @@ function controlEntitySpecs(slice) {
3468
3465
  const min = readFiniteNumber(slice, "min") ?? PERCENT_RANGE.min;
3469
3466
  const max = readFiniteNumber(slice, "max") ?? PERCENT_RANGE.max;
3470
3467
  const step = readFiniteNumber(slice, "step") ?? PERCENT_RANGE.step;
3471
- const unit = typeof slice?.["unit"] === "string" ? slice["unit"] : void 0;
3468
+ const unit = quantity === void 0 ? void 0 : require_dist.baseUnitOf(quantity);
3472
3469
  if (min < max && step > 0) return [{
3473
3470
  entity: primary,
3474
3471
  platform: "number",
@@ -3491,6 +3488,10 @@ function controlEntitySpecs(slice) {
3491
3488
  }, kindExtra];
3492
3489
  }
3493
3490
  var CAPS_NOT_EXPORTED = [
3491
+ {
3492
+ cap: "group-status",
3493
+ reason: "Bookkeeping for a helper group (D944): how many members were included, excluded, or named. The value an operator automates on is the output capability (the temperature, the motion, the watts). These counts are session-only, have no history, and would publish a second entity that never means the reading."
3494
+ },
3494
3495
  {
3495
3496
  cap: "recording-onboard",
3496
3497
  reason: "An EDITOR of the CAMERA’s own recorder: a weekly schedule of windows per trigger, plus the facts about the card it writes to. The schedule is a document an operator authors, not a reading — an entity would publish 168 slots as a state and nothing in Home Assistant could act on it. The storage side is a reading, but it belongs to the camera’s own health rather than to this cap’s editor surface, and publishing capacity here would put the same number in two places the day a health cap carries it. ONE thing here is arguably a real binary sensor: `recordingToNowhere` — scheduled to record with no usable card. It is deliberately NOT exported yet, because exporting half a cap needs the split decided first, and the two cameras it fires on today are already a danger banner and a WARN."
@@ -3738,7 +3739,15 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
3738
3739
  * row stays the projector + writable-route authority.
3739
3740
  */
3740
3741
  if (cap === "control") {
3741
- specs.push(...controlEntitySpecs(device.capSlices?.["control"]).map((spec) => degradeUnsupported(spec, options)));
3742
+ specs.push(...controlEntitySpecs(device.capSlices?.["control"], device.readingQuantity).map((spec) => degradeUnsupported(spec, options)));
3743
+ continue;
3744
+ }
3745
+ if (cap === "numeric-sensor") {
3746
+ const unit = device.readingQuantity === void 0 ? void 0 : require_dist.baseUnitOf(device.readingQuantity);
3747
+ specs.push(...capEntitySpecs(cap).map((spec) => degradeUnsupported(unit === void 0 ? spec : {
3748
+ ...spec,
3749
+ unit
3750
+ }, options)));
3742
3751
  continue;
3743
3752
  }
3744
3753
  const mapping = CAP_ENTITY_MAP[cap];
@@ -7963,6 +7972,7 @@ var HaExportAddon = class extends require_dist.BaseAddon {
7963
7972
  if (items.held) heldBack = true;
7964
7973
  const manufacturer = readString(device.metadata ?? {}, "manufacturer");
7965
7974
  const model = readString(device.metadata ?? {}, "model");
7975
+ const readingQuantity = device.sourceInfo?.quantity;
7966
7976
  return {
7967
7977
  device: {
7968
7978
  id: device.id,
@@ -7984,7 +7994,8 @@ var HaExportAddon = class extends require_dist.BaseAddon {
7984
7994
  * `CatalogDevice.capSlices`.
7985
7995
  */
7986
7996
  capSlices: snapshot,
7987
- itemEntities: items.entities
7997
+ itemEntities: items.entities,
7998
+ ...require_dist.isReadingQuantity(readingQuantity) ? { readingQuantity } : {}
7988
7999
  },
7989
8000
  heldBack
7990
8001
  };
@@ -1,4 +1,4 @@
1
- import { At as BaseAddon, D as buildAddonRouteProvider, F as consumableFormOf, Gt as string, H as deviceExportCapability, I as consumableKindOf, It as nodePin, Rt as array, Ut as object, Yt as EventCategory, _ as addonRoutesCapability, a as COCO_TO_MACRO, at as oauthIntegrationCapability, c as ConsumableItemSchema, d as FanDirectionSchema, f as HvacModeSchema, i as CAMERA_SWITCH_ORDER, l as ConsumableStatusSchema, o as CameraSwitchIdSchema, p as MediaPlayerRepeatSchema, qt as unknown, s as ConsumableFormSchema, t as AlarmArmModeSchema } from "../dist-DvZEnx9C.mjs";
1
+ import { $t as unknown, D as buildAddonRouteProvider, F as consumableFormOf, H as deviceExportCapability, Ht as nodePin, I as consumableKindOf, It as baseUnitOf, Pt as BaseAddon, Vt as isReadingQuantity, Wt as array, Yt as object, Zt as string, _ as addonRoutesCapability, a as COCO_TO_MACRO, c as ConsumableItemSchema, d as FanDirectionSchema, f as HvacModeSchema, i as CAMERA_SWITCH_ORDER, l as ConsumableStatusSchema, o as CameraSwitchIdSchema, ot as oauthIntegrationCapability, p as MediaPlayerRepeatSchema, s as ConsumableFormSchema, t as AlarmArmModeSchema, tn as EventCategory } from "../dist-BFnk4bwH.mjs";
2
2
  import { createHmac, timingSafeEqual } from "node:crypto";
3
3
  import "node:zlib";
4
4
  import "node:fs";
@@ -703,12 +703,6 @@ var STATUS_UNKNOWN = "unknown";
703
703
  function consumableEntity(key, facet) {
704
704
  return `${CONSUMABLE_PREFIX}_${toSlug(key)}_${facet}`;
705
705
  }
706
- /** Home Assistant's own duration units; a cycle count is a plain count. */
707
- var REMAINING_UNIT = {
708
- days: "d",
709
- hours: "h",
710
- cycles: "cycles"
711
- };
712
706
  /**
713
707
  * The item's form (the capability's own derivation, `consumableFormOf`): the one
714
708
  * the provider declared when it is a form the capability knows, else the one its
@@ -794,7 +788,7 @@ function consumableEntities(item) {
794
788
  entityCategory: "diagnostic"
795
789
  });
796
790
  if (item.remaining !== void 0) {
797
- const isDuration = item.remaining.unit !== "cycles";
791
+ const isDuration = item.remaining.kind === "duration";
798
792
  out.push({
799
793
  ...base,
800
794
  facet: "remaining",
@@ -802,7 +796,7 @@ function consumableEntities(item) {
802
796
  uniqueSuffix: unique("remaining"),
803
797
  platform: "sensor",
804
798
  label: `${item.label} remaining`,
805
- unit: REMAINING_UNIT[item.remaining.unit],
799
+ ...isDuration ? { unit: "s" } : {},
806
800
  stateClass: "measurement",
807
801
  ...isDuration ? { deviceClass: "duration" } : {},
808
802
  icon: isDuration ? "mdi:timer-sand" : "mdi:counter",
@@ -3149,7 +3143,7 @@ mireds: {
3149
3143
  platform: "sensor",
3150
3144
  field: "desiccantLeftDays",
3151
3145
  label: "Desiccant remaining",
3152
- unit: "d",
3146
+ unit: "s",
3153
3147
  entityCategory: "diagnostic",
3154
3148
  enabledByDefault: false
3155
3149
  },
@@ -3163,11 +3157,8 @@ mireds: {
3163
3157
  }
3164
3158
  },
3165
3159
  /**
3166
- * The cap carries its OWN unit next to each measurement
3167
- * (`temperatureUnit`, `pressureUnit`, `windSpeedUnit`), so no static
3168
- * `unit_of_measurement` can be honest for a provider reporting °F.
3169
- * Declaring one anyway is how a dashboard comes to say 71 °C. Humidity
3170
- * is the exception: a percentage is a percentage.
3160
+ * Weather numbers are stored in the quantity base (°C, hPa, m/s). The
3161
+ * export advertises those symbols. A Fahrenheit dashboard converts at read.
3171
3162
  */
3172
3163
  weather: {
3173
3164
  platform: "sensor",
@@ -3178,6 +3169,8 @@ mireds: {
3178
3169
  platform: "sensor",
3179
3170
  field: "temperature",
3180
3171
  label: "Temperature",
3172
+ unit: "°C",
3173
+ deviceClass: "temperature",
3181
3174
  icon: "mdi:thermometer"
3182
3175
  },
3183
3176
  humidity: {
@@ -3191,6 +3184,8 @@ mireds: {
3191
3184
  platform: "sensor",
3192
3185
  field: "pressure",
3193
3186
  label: "Pressure",
3187
+ unit: "hPa",
3188
+ deviceClass: "pressure",
3194
3189
  icon: "mdi:gauge",
3195
3190
  enabledByDefault: false
3196
3191
  },
@@ -3198,6 +3193,8 @@ mireds: {
3198
3193
  platform: "sensor",
3199
3194
  field: "windSpeed",
3200
3195
  label: "Wind speed",
3196
+ unit: "m/s",
3197
+ deviceClass: "wind_speed",
3201
3198
  icon: "mdi:weather-windy",
3202
3199
  enabledByDefault: false
3203
3200
  },
@@ -3408,7 +3405,7 @@ function readFiniteNumber(slice, field) {
3408
3405
  * builds with nothing to choose from — so an empty list falls back to a
3409
3406
  * read-only sensor rather than a dead control.
3410
3407
  */
3411
- function controlEntitySpecs(slice) {
3408
+ function controlEntitySpecs(slice, quantity) {
3412
3409
  const kind = typeof slice?.["kind"] === "string" ? slice["kind"] : null;
3413
3410
  const primary = derivedEntityId("control");
3414
3411
  const kindExtra = {
@@ -3433,7 +3430,7 @@ function controlEntitySpecs(slice) {
3433
3430
  const min = readFiniteNumber(slice, "min") ?? PERCENT_RANGE.min;
3434
3431
  const max = readFiniteNumber(slice, "max") ?? PERCENT_RANGE.max;
3435
3432
  const step = readFiniteNumber(slice, "step") ?? PERCENT_RANGE.step;
3436
- const unit = typeof slice?.["unit"] === "string" ? slice["unit"] : void 0;
3433
+ const unit = quantity === void 0 ? void 0 : baseUnitOf(quantity);
3437
3434
  if (min < max && step > 0) return [{
3438
3435
  entity: primary,
3439
3436
  platform: "number",
@@ -3456,6 +3453,10 @@ function controlEntitySpecs(slice) {
3456
3453
  }, kindExtra];
3457
3454
  }
3458
3455
  var CAPS_NOT_EXPORTED = [
3456
+ {
3457
+ cap: "group-status",
3458
+ reason: "Bookkeeping for a helper group (D944): how many members were included, excluded, or named. The value an operator automates on is the output capability (the temperature, the motion, the watts). These counts are session-only, have no history, and would publish a second entity that never means the reading."
3459
+ },
3459
3460
  {
3460
3461
  cap: "recording-onboard",
3461
3462
  reason: "An EDITOR of the CAMERA’s own recorder: a weekly schedule of windows per trigger, plus the facts about the card it writes to. The schedule is a document an operator authors, not a reading — an entity would publish 168 slots as a state and nothing in Home Assistant could act on it. The storage side is a reading, but it belongs to the camera’s own health rather than to this cap’s editor surface, and publishing capacity here would put the same number in two places the day a health cap carries it. ONE thing here is arguably a real binary sensor: `recordingToNowhere` — scheduled to record with no usable card. It is deliberately NOT exported yet, because exporting half a cap needs the split decided first, and the two cameras it fires on today are already a danger banner and a WARN."
@@ -3703,7 +3704,15 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
3703
3704
  * row stays the projector + writable-route authority.
3704
3705
  */
3705
3706
  if (cap === "control") {
3706
- specs.push(...controlEntitySpecs(device.capSlices?.["control"]).map((spec) => degradeUnsupported(spec, options)));
3707
+ specs.push(...controlEntitySpecs(device.capSlices?.["control"], device.readingQuantity).map((spec) => degradeUnsupported(spec, options)));
3708
+ continue;
3709
+ }
3710
+ if (cap === "numeric-sensor") {
3711
+ const unit = device.readingQuantity === void 0 ? void 0 : baseUnitOf(device.readingQuantity);
3712
+ specs.push(...capEntitySpecs(cap).map((spec) => degradeUnsupported(unit === void 0 ? spec : {
3713
+ ...spec,
3714
+ unit
3715
+ }, options)));
3707
3716
  continue;
3708
3717
  }
3709
3718
  const mapping = CAP_ENTITY_MAP[cap];
@@ -7928,6 +7937,7 @@ var HaExportAddon = class extends BaseAddon {
7928
7937
  if (items.held) heldBack = true;
7929
7938
  const manufacturer = readString(device.metadata ?? {}, "manufacturer");
7930
7939
  const model = readString(device.metadata ?? {}, "model");
7940
+ const readingQuantity = device.sourceInfo?.quantity;
7931
7941
  return {
7932
7942
  device: {
7933
7943
  id: device.id,
@@ -7949,7 +7959,8 @@ var HaExportAddon = class extends BaseAddon {
7949
7959
  * `CatalogDevice.capSlices`.
7950
7960
  */
7951
7961
  capSlices: snapshot,
7952
- itemEntities: items.entities
7962
+ itemEntities: items.entities,
7963
+ ...isReadingQuantity(readingQuantity) ? { readingQuantity } : {}
7953
7964
  },
7954
7965
  heldBack
7955
7966
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.2.625",
3
+ "version": "1.2.627",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",