@camstack/types 1.2.415 → 1.2.417

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.
Files changed (56) hide show
  1. package/dist/addon.js +1 -1
  2. package/dist/addon.mjs +1 -1
  3. package/dist/capabilities/air-quality-sensor.cap.d.ts +2 -8
  4. package/dist/capabilities/ambient-light-sensor.cap.d.ts +3 -14
  5. package/dist/capabilities/battery.cap.d.ts +9 -2
  6. package/dist/capabilities/binary.cap.d.ts +0 -5
  7. package/dist/capabilities/capability-definition.d.ts +6 -6
  8. package/dist/capabilities/consumables.cap.d.ts +30 -41
  9. package/dist/capabilities/control.cap.d.ts +3 -25
  10. package/dist/capabilities/core-blocks.cap.d.ts +1 -0
  11. package/dist/capabilities/customize-catalog.d.ts +26 -8
  12. package/dist/capabilities/customize-spec.d.ts +8 -4
  13. package/dist/capabilities/device-manager.cap.d.ts +79 -0
  14. package/dist/capabilities/device-provider.cap.d.ts +36 -0
  15. package/dist/capabilities/doorbell.cap.d.ts +1 -0
  16. package/dist/capabilities/enum-sensor.cap.d.ts +5 -17
  17. package/dist/capabilities/humidity-sensor.cap.d.ts +3 -14
  18. package/dist/capabilities/index.d.ts +1 -1
  19. package/dist/capabilities/motion.cap.d.ts +1 -1
  20. package/dist/capabilities/numeric-sensor.cap.d.ts +8 -16
  21. package/dist/capabilities/pet-feeder.cap.d.ts +1 -1
  22. package/dist/capabilities/power-meter.cap.d.ts +0 -11
  23. package/dist/capabilities/pressure-sensor.cap.d.ts +3 -9
  24. package/dist/capabilities/running-state.cap.d.ts +2 -2
  25. package/dist/capabilities/runtime-field-unit.d.ts +1 -0
  26. package/dist/capabilities/runtime-field-units.d.ts +8 -0
  27. package/dist/capabilities/switch.cap.d.ts +0 -5
  28. package/dist/capabilities/temperature-sensor.cap.d.ts +5 -20
  29. package/dist/capabilities/weather.cap.d.ts +4 -13
  30. package/dist/client.d.ts +2 -1
  31. package/dist/client.js +54 -5
  32. package/dist/client.mjs +52 -6
  33. package/dist/composition/composition-report.d.ts +8 -3
  34. package/dist/composition/customize/override-draft.d.ts +10 -0
  35. package/dist/composition/state-fields.d.ts +1 -1
  36. package/dist/composition/validate-composition.d.ts +3 -2
  37. package/dist/device/cap-vocabulary/types.d.ts +2 -2
  38. package/dist/device/schema-fields.d.ts +3 -0
  39. package/dist/device/source-info.d.ts +12 -6
  40. package/dist/generated/addon-cap-facts.d.ts +1 -1
  41. package/dist/generated/customize-catalog.d.ts +59 -87
  42. package/dist/generated/history-catalog.d.ts +1 -1
  43. package/dist/index.d.ts +4 -2
  44. package/dist/index.js +842 -507
  45. package/dist/index.mjs +825 -507
  46. package/dist/{sleep-BErAxhul.mjs → sleep-CARd6wZF.mjs} +161 -128
  47. package/dist/{sleep-BPQ2ZyaI.js → sleep-gtskVoMw.js} +214 -127
  48. package/dist/units/convert.d.ts +5 -0
  49. package/dist/units/format-reading.d.ts +15 -0
  50. package/dist/units/index.d.ts +8 -1
  51. package/dist/units/ingest-reading.d.ts +21 -0
  52. package/dist/units/quantity.d.ts +47 -0
  53. package/dist/units/threshold.d.ts +6 -0
  54. package/dist/units/unit-table.d.ts +2 -228
  55. package/package.json +7 -2
  56. package/dist/capabilities/advanced-parameters.d.ts +0 -17
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-BPQ2ZyaI.js");
2
+ const require_sleep = require("./sleep-gtskVoMw.js");
3
3
  const require_event_category = require("./event-category-DXZ3f9Q5.js");
4
4
  const require_composition = require("./composition-DsmkwP9-.js");
5
5
  const require_node_color = require("./node-color.js");
@@ -12398,6 +12398,17 @@ function unwrapSchema(schema) {
12398
12398
  while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
12399
12399
  return s;
12400
12400
  }
12401
+ function isMetaBag(value) {
12402
+ return typeof value === "object" && value !== null;
12403
+ }
12404
+ function metaQuantity(schema) {
12405
+ const bag = zod.z.globalRegistry.get(schema);
12406
+ if (isMetaBag(bag) && require_sleep.isReadingQuantity(bag.quantity)) return bag.quantity;
12407
+ }
12408
+ /** Quantity declared with `readingMeta` on the field or on the unwrapped leaf. */
12409
+ function quantityOfField(schema) {
12410
+ return metaQuantity(schema) ?? metaQuantity(unwrapSchema(schema));
12411
+ }
12401
12412
  function leafKind(schema) {
12402
12413
  const s = unwrapSchema(schema);
12403
12414
  if (s instanceof zod.z.ZodEnum) return {
@@ -12426,11 +12437,15 @@ function enumerateSchemaFields(schema, prefix = "") {
12426
12437
  continue;
12427
12438
  }
12428
12439
  const leaf = leafKind(field);
12429
- if (leaf) out.push({
12430
- path,
12431
- kind: leaf.kind,
12432
- ...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
12433
- });
12440
+ if (leaf) {
12441
+ const quantity = quantityOfField(field);
12442
+ out.push({
12443
+ path,
12444
+ kind: leaf.kind,
12445
+ ...leaf.enumValues ? { enumValues: leaf.enumValues } : {},
12446
+ ...quantity !== void 0 ? { quantity } : {}
12447
+ });
12448
+ }
12434
12449
  }
12435
12450
  return out;
12436
12451
  }
@@ -12491,7 +12506,7 @@ function requiredTopLevelKeys(schema) {
12491
12506
  /**
12492
12507
  * The proper prefixes of `path` whose schema accepts `undefined`: a required
12493
12508
  * leaf under one of them is required only once something writes that parent
12494
- * (`remaining.unit` is required only when `remaining` is present).
12509
+ * (`remaining.kind` is required only when `remaining` is present).
12495
12510
  */
12496
12511
  function optionalAncestors(schema, path) {
12497
12512
  const segments = path.split(".");
@@ -12608,7 +12623,8 @@ var CompositionProblemCodeSchema = zod.z.enum([
12608
12623
  "item-field-reserved",
12609
12624
  "target-missing",
12610
12625
  "target-unreadable",
12611
- "not-replaceable"
12626
+ "not-replaceable",
12627
+ "native-capability-replace"
12612
12628
  ]);
12613
12629
  /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
12614
12630
  var CompositionProblemSchema = zod.z.object({
@@ -12674,9 +12690,11 @@ function compositionFieldKey(plan) {
12674
12690
  return `${plan.cap}.${path}`;
12675
12691
  }
12676
12692
  /**
12677
- * What a feature does to its target (D663): `new` builds a capability on a new
12678
- * device; on an existing device it `add`s a capability the device lacks, or
12679
- * `replace`s named fields of one it has natively.
12693
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
12694
+ * new device; on an existing device it `add`s a capability the device lacks.
12695
+ * Replacing a capability the device already serves is refused
12696
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
12697
+ * wire for a claim the hub already holds.
12680
12698
  */
12681
12699
  var CompositionFeatureModeSchema = zod.z.enum([
12682
12700
  "new",
@@ -13510,6 +13528,9 @@ function isNativeCap(target, cap) {
13510
13528
  const owner = target.nativeCaps.get(cap);
13511
13529
  return owner !== void 0 && owner !== "composer";
13512
13530
  }
13531
+ function isPlanProblem(value) {
13532
+ return "code" in value;
13533
+ }
13513
13534
  /** The target read, or the refusal that replaces the whole plan when it could not be (D391). */
13514
13535
  function planTargetOf(composition, target) {
13515
13536
  if (composition.target.kind === "new") return {
@@ -13525,26 +13546,59 @@ function planTargetOf(composition, target) {
13525
13546
  found: target
13526
13547
  };
13527
13548
  }
13528
- function featureTargetOf(target, cap) {
13549
+ var NATIVE_REPLACE_KEPT = new Set([
13550
+ "not-composable",
13551
+ "unknown-capability",
13552
+ "reserved-variant"
13553
+ ]);
13554
+ /** A native override is refused, unless the cap cannot be composed at all — that reason stays. */
13555
+ function nativeReplaceOrBlock(refusal, probed) {
13556
+ const kept = probed.problems.filter((item) => NATIVE_REPLACE_KEPT.has(item.code));
13557
+ if (kept.length > 0) return {
13558
+ problems: kept,
13559
+ fields: [],
13560
+ features: [],
13561
+ events: []
13562
+ };
13563
+ return refused(refusal);
13564
+ }
13565
+ function featureTargetOf(target, cap, at) {
13529
13566
  if (target.kind === "new") return {
13530
13567
  mode: "new",
13531
13568
  deviceType: target.type
13532
13569
  };
13570
+ if (isNativeCap(target.found, cap)) return problem("native-capability-replace", at, `native-capability-replace: stored override replaces native capability \`${cap}\` on device ${target.found.deviceId} — refused (D944)`);
13533
13571
  return {
13534
- mode: isNativeCap(target.found, cap) ? "replace" : "add",
13572
+ mode: "add",
13535
13573
  deviceType: target.found.type
13536
13574
  };
13537
13575
  }
13538
13576
  /**
13539
13577
  * The static plan. `target` describes an EXISTING target device (one read);
13540
- * it is ignored for a new one. On an existing target each feature REPLACES a
13541
- * cap the device has natively, else ADDS it (D663).
13578
+ * it is ignored for a new one. On an existing target each feature ADDS a cap
13579
+ * the device lacks. A feature that would replace a native capability is
13580
+ * refused by name (D944). A new device is unchanged.
13542
13581
  */
13543
13582
  function planComposition(composition, lookupCap, target) {
13544
13583
  const planTarget = planTargetOf(composition, target);
13545
13584
  if ("code" in planTarget) return refused(planTarget);
13546
13585
  const results = composition.features.map((feature, index) => {
13547
- const result = feature.kind === "passthrough" ? refused(problem("reserved-variant", `features[${index}]`, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`)) : planFeature(feature, index, featureTargetOf(planTarget, feature.cap), lookupCap);
13586
+ const at = `features[${index}]`;
13587
+ if (feature.kind === "passthrough") return refused(problem("reserved-variant", at, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`));
13588
+ const featureTarget = featureTargetOf(planTarget, feature.cap, at);
13589
+ if (isPlanProblem(featureTarget)) {
13590
+ if (!require_composition.compositionFeatureEnabled(feature) || planTarget.kind !== "existing") return {
13591
+ problems: [],
13592
+ fields: [],
13593
+ features: [],
13594
+ events: []
13595
+ };
13596
+ return nativeReplaceOrBlock(featureTarget, planFeature(feature, index, {
13597
+ mode: "add",
13598
+ deviceType: planTarget.found.type
13599
+ }, lookupCap));
13600
+ }
13601
+ const result = planFeature(feature, index, featureTarget, lookupCap);
13548
13602
  return require_composition.compositionFeatureEnabled(feature) ? result : {
13549
13603
  problems: result.problems,
13550
13604
  fields: [],
@@ -13685,7 +13739,11 @@ function cycleProblems(composition, ctx, fields, self) {
13685
13739
  function validateComposition(composition, ctx) {
13686
13740
  const plan = planComposition(composition, ctx.lookupCap, ctx.target ?? void 0);
13687
13741
  const self = candidateSelf(composition, ctx, plan);
13688
- const cycles = cycleProblems(composition, ctx, plan.fields, self);
13742
+ const refusedFeatures = plan.problems.filter((item) => item.code === "native-capability-replace").map((item) => item.path);
13743
+ const cycles = cycleProblems(composition, ctx, plan.fields, self).filter((item) => {
13744
+ if (refusedFeatures.some((at) => item.path === at || item.path.startsWith(`${at}.`))) return false;
13745
+ return refusedFeatures.length === 0 || item.path !== "features";
13746
+ });
13689
13747
  const problems = [
13690
13748
  ...plan.problems,
13691
13749
  ...sourceProblems(plan.fields, ctx, self),
@@ -16432,12 +16490,12 @@ function parseAdminBase(baseUrl) {
16432
16490
  * providers detect renames by matching `uniqueId` and patch `id`
16433
16491
  * in-place so dispatch keeps working.
16434
16492
  *
16435
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
16436
- * `precision`) live in the cap STATUS SLICE so they flow live with every
16437
- * state push and are the single source of truth. The `device_class` tag is
16438
- * an HA-specific input that is consumed at the discovery boundary
16439
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
16440
- * stored after that point.
16493
+ * **Display unit and precision are NOT stored here.** They are a read-time
16494
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
16495
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
16496
+ * neither is repeated on each sample. The `device_class` tag is an
16497
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
16498
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
16441
16499
  *
16442
16500
  * The `raw` escape hatch carries provider-specific attributes that don't
16443
16501
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -16451,6 +16509,14 @@ var SourceInfoSchema = zod.z.object({
16451
16509
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
16452
16510
  * Used to detect rename when `id` changes. */
16453
16511
  uniqueId: zod.z.string().optional(),
16512
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
16513
+ quantity: require_sleep.ReadingQuantitySchema.optional(),
16514
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
16515
+ dateTimeShape: zod.z.enum([
16516
+ "date",
16517
+ "time",
16518
+ "datetime"
16519
+ ]).optional(),
16454
16520
  /** Provider-specific extras that don't fit the structured slots.
16455
16521
  * Whitelist what goes here per provider — do NOT dump entire HA
16456
16522
  * attribute blobs (would explode the meta JSON column). */
@@ -16535,9 +16601,38 @@ function normalizeUnit(raw) {
16535
16601
  if (lower === "mph") return "mph";
16536
16602
  if (lower === "m/s") return "m/s";
16537
16603
  if (lower === "m") return "m";
16604
+ if (lower === "m²" || lower === "m2") return "m²";
16605
+ if (lower === "mm") return "mm";
16606
+ if (lower === "cm") return "cm";
16538
16607
  if (lower === "km") return "km";
16608
+ if (lower === "in" || lower === "inch" || lower === "inches") return "in";
16609
+ if (lower === "ft" || lower === "foot" || lower === "feet") return "ft";
16539
16610
  if (lower === "kg") return "kg";
16540
16611
  if (lower === "g") return "g";
16612
+ if (lower === "l" || lower === "liter" || lower === "litre" || lower === "liters" || lower === "litres") return "L";
16613
+ if (lower === "ml") return "mL";
16614
+ if (lower === "gal" || lower === "gallon" || lower === "gallons") return "gal";
16615
+ if (lower === "s" || lower === "sec" || lower === "second" || lower === "seconds") return "s";
16616
+ if (lower === "ms") return "ms";
16617
+ if (lower === "min" || lower === "minute" || lower === "minutes") return "min";
16618
+ if (lower === "h" || lower === "hr" || lower === "hour" || lower === "hours") return "h";
16619
+ if (lower === "d" || lower === "day" || lower === "days") return "d";
16620
+ if (lower === "b" || lower === "byte" || lower === "bytes") return "B";
16621
+ if (lower === "kib") return "KiB";
16622
+ if (lower === "mib") return "MiB";
16623
+ if (lower === "gib") return "GiB";
16624
+ if (lower === "tib") return "TiB";
16625
+ if (lower === "kb") return "kB";
16626
+ if (lower === "mb") return "MB";
16627
+ if (lower === "gb") return "GB";
16628
+ if (lower === "dbm") return "dBm";
16629
+ if (lower === "dbfs") return "dBFS";
16630
+ if (lower === "inhg") return "inHg";
16631
+ if (lower === "mm/h") return "mm/h";
16632
+ if (lower === "w/m²" || lower === "w/m2") return "W/m²";
16633
+ if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
16634
+ if (lower === "kbps") return "kbps";
16635
+ if (lower === "fps") return "fps";
16541
16636
  return trimmed;
16542
16637
  }
16543
16638
  /**
@@ -37969,7 +38064,8 @@ var deviceManagerCapability = {
37969
38064
  "enum"
37970
38065
  ]),
37971
38066
  enumValues: zod.z.array(zod.z.string()).optional(),
37972
- item: zod.z.boolean().optional()
38067
+ item: zod.z.boolean().optional(),
38068
+ quantity: require_sleep.ReadingQuantitySchema.optional()
37973
38069
  })).readonly(),
37974
38070
  itemArray: zod.z.object({
37975
38071
  path: zod.z.string(),
@@ -44293,8 +44389,8 @@ var MaskPolygonShapeSchema = zod.z.object({
44293
44389
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44294
44390
  var MaskGridShapeSchema = zod.z.object({
44295
44391
  kind: zod.z.literal("grid"),
44296
- gridWidth: zod.z.number(),
44297
- gridHeight: zod.z.number(),
44392
+ gridWidth: zod.z.number().meta(require_sleep.readingMeta("none")),
44393
+ gridHeight: zod.z.number().meta(require_sleep.readingMeta("none")),
44298
44394
  cells: zod.z.array(zod.z.boolean())
44299
44395
  });
44300
44396
  /** Directed line / tripwire — an ordered list of normalized points (≥2). */
@@ -60558,27 +60654,17 @@ var addonsCapability = {
60558
60654
  */
60559
60655
  var AirQualitySensorStatusSchema = zod.z.object({
60560
60656
  /** Carbon dioxide concentration in ppm. */
60561
- co2Ppm: zod.z.number().min(0).optional(),
60657
+ co2Ppm: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-volume")),
60562
60658
  /** Total volatile organic compounds in ppb. */
60563
- vocPpb: zod.z.number().min(0).optional(),
60659
+ vocPpb: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-ppb")),
60564
60660
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
60565
- pm25: zod.z.number().min(0).optional(),
60661
+ pm25: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-mass")),
60566
60662
  /** Particulate matter ≤ 10 μm in µg/m³. */
60567
- pm10: zod.z.number().min(0).optional(),
60663
+ pm10: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-mass")),
60568
60664
  /** Composite AQI value (typically 0..500). */
60569
- aqi: zod.z.number().optional(),
60665
+ aqi: zod.z.number().optional().meta(require_sleep.readingMeta("index")),
60570
60666
  /** Ms epoch when the slice was last updated. */
60571
- lastFetchedAt: zod.z.number(),
60572
- /** Live display unit of the single metric this slice carries (e.g. HA
60573
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
60574
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
60575
- * per slice is unambiguous. */
60576
- unit: zod.z.string().optional(),
60577
- /** Suggested decimal places for numeric display.
60578
- * Populated live from the upstream source when provided (e.g. HA
60579
- * `attributes.suggested_display_precision`). Falls back to
60580
- * auto-formatting when absent. */
60581
- precision: zod.z.number().int().min(0).max(10).optional()
60667
+ lastFetchedAt: zod.z.number()
60582
60668
  });
60583
60669
  var airQualitySensorCapability = {
60584
60670
  name: "air-quality-sensor",
@@ -60615,12 +60701,12 @@ var airQualitySensorCapability = {
60615
60701
  },
60616
60702
  pm25: {
60617
60703
  kind: "gauge",
60618
- unit: "ug/m3",
60704
+ unit: "µg/m³",
60619
60705
  deadband: 1
60620
60706
  },
60621
60707
  pm10: {
60622
60708
  kind: "gauge",
60623
- unit: "ug/m3",
60709
+ unit: "µg/m³",
60624
60710
  deadband: 1
60625
60711
  },
60626
60712
  aqi: {
@@ -60640,22 +60726,13 @@ var airQualitySensorCapability = {
60640
60726
  * entries with `device_class: illuminance`.
60641
60727
  */
60642
60728
  var AmbientLightSensorStatusSchema = zod.z.object({
60643
- /** Current illuminance in lux (lx). */
60644
- lux: zod.z.number().min(0).meta({
60645
- unit: "lx",
60729
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
60730
+ lux: zod.z.number().min(0).nullable().meta(require_sleep.readingMeta("illuminance", {
60731
+ precision: 0,
60646
60732
  label: "Illuminance"
60647
- }),
60733
+ })),
60648
60734
  /** Ms epoch when the slice was last updated. */
60649
- lastFetchedAt: zod.z.number(),
60650
- /** Live display unit from the upstream source (e.g. HA
60651
- * `attributes.unit_of_measurement`). The UI prefers this over the
60652
- * role's canonical unit. Absent → fall back to the canonical unit. */
60653
- unit: zod.z.string().optional(),
60654
- /** Suggested decimal places for numeric display.
60655
- * Populated live from the upstream source when provided (e.g. HA
60656
- * `attributes.suggested_display_precision`). Falls back to
60657
- * auto-formatting when absent. */
60658
- precision: zod.z.number().int().min(0).max(10).optional()
60735
+ lastFetchedAt: zod.z.number()
60659
60736
  });
60660
60737
  var ambientLightSensorCapability = {
60661
60738
  name: "ambient-light-sensor",
@@ -60687,12 +60764,7 @@ var ambientLightSensorCapability = {
60687
60764
  } } },
60688
60765
  /** Clock fields: written, but excluded from the compare that decides
60689
60766
  * whether persisting is worth a SQLite commit. */
60690
- volatileStateFields: ["lastFetchedAt"],
60691
- customize: {
60692
- version: 1,
60693
- advanced: "derive",
60694
- targets: []
60695
- }
60767
+ volatileStateFields: ["lastFetchedAt"]
60696
60768
  };
60697
60769
  //#endregion
60698
60770
  //#region src/capabilities/audio-metrics.cap.ts
@@ -60934,10 +61006,7 @@ var BatteryStatusSchema = zod.z.object({
60934
61006
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
60935
61007
  * declines to band a non-finite reading, which is the correct shape.
60936
61008
  */
60937
- percentage: zod.z.number().min(0).max(100).nullable().meta({
60938
- unit: "%",
60939
- label: "Charge"
60940
- }),
61009
+ percentage: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage", { label: "Charge" })),
60941
61010
  /**
60942
61011
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
60943
61012
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -60964,7 +61033,14 @@ var BatteryStatusSchema = zod.z.object({
60964
61033
  */
60965
61034
  sleeping: zod.z.boolean().meta({ label: "Sleeping" }),
60966
61035
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60967
- lastUpdated: zod.z.number(),
61036
+ lastUpdated: zod.z.number().meta(require_sleep.readingMeta("none")),
61037
+ /**
61038
+ * Battery voltage in volts, for a cell the firmware reports as voltage
61039
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
61040
+ * that only report charge. A second reading on this cap, not a second
61041
+ * `numeric-sensor` on the same device.
61042
+ */
61043
+ volts: zod.z.number().nullable().optional().meta(require_sleep.readingMeta("voltage", { label: "Voltage" })),
60968
61044
  /**
60969
61045
  * Ms epoch of the last time the device PROVED it was reachable — a
60970
61046
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -61016,7 +61092,6 @@ var BATTERY_DEVICE_TYPES = [
61016
61092
  /** Generic Customize target for the percentage reading (D936). */
61017
61093
  var batteryCustomize = {
61018
61094
  version: 1,
61019
- advanced: "derive",
61020
61095
  targets: [{
61021
61096
  id: "percentage",
61022
61097
  cardinality: "scalar",
@@ -61025,7 +61100,7 @@ var batteryCustomize = {
61025
61100
  stateField: "percentage"
61026
61101
  },
61027
61102
  deviceTypes: BATTERY_DEVICE_TYPES,
61028
- apply: "add-or-replace",
61103
+ apply: "add",
61029
61104
  path: "percentage",
61030
61105
  sources: [{
61031
61106
  kind: "field",
@@ -61146,10 +61221,16 @@ onStatusChanged: { data: zod.z.object({
61146
61221
  /**
61147
61222
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
61148
61223
  */
61149
- history: { fields: { percentage: {
61150
- kind: "gauge",
61151
- unit: "%"
61152
- } } },
61224
+ history: { fields: {
61225
+ percentage: {
61226
+ kind: "gauge",
61227
+ unit: "%"
61228
+ },
61229
+ volts: {
61230
+ kind: "gauge",
61231
+ unit: "V"
61232
+ }
61233
+ } },
61153
61234
  customize: batteryCustomize,
61154
61235
  /** Clock fields: written, but excluded from the compare that decides
61155
61236
  * whether persisting is worth a SQLite commit. */
@@ -61198,12 +61279,7 @@ var binaryCapability = {
61198
61279
  history: { fields: { on: { kind: "boolean" } } },
61199
61280
  /** Clock fields: written, but excluded from the compare that decides
61200
61281
  * whether persisting is worth a SQLite commit. */
61201
- volatileStateFields: ["lastChangedAt"],
61202
- customize: {
61203
- version: 1,
61204
- advanced: "derive",
61205
- targets: []
61206
- }
61282
+ volatileStateFields: ["lastChangedAt"]
61207
61283
  };
61208
61284
  //#endregion
61209
61285
  //#region src/capabilities/brightness.cap.ts
@@ -61221,7 +61297,7 @@ var binaryCapability = {
61221
61297
  */
61222
61298
  var BrightnessStatusSchema = zod.z.object({
61223
61299
  /** Current level as 0..100 inclusive. Firmware-reported. */
61224
- percentage: zod.z.number().min(0).max(100),
61300
+ percentage: zod.z.number().min(0).max(100).meta(require_sleep.readingMeta("percentage")),
61225
61301
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
61226
61302
  lastChangedAt: zod.z.number()
61227
61303
  });
@@ -61820,21 +61896,21 @@ var ClimateControlStatusSchema = zod.z.object({
61820
61896
  /** Single setpoint in Celsius. Used by single-target modes
61821
61897
  * (heat / cool / dry). Null when the device is in a range mode
61822
61898
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
61823
- target: zod.z.number().nullable(),
61899
+ target: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
61824
61900
  /** Upper bound of the dual setpoint in Celsius. Populated only
61825
61901
  * in `heat_cool` mode. */
61826
- targetHigh: zod.z.number().nullable(),
61902
+ targetHigh: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
61827
61903
  /** Lower bound of the dual setpoint in Celsius. Populated only
61828
61904
  * in `heat_cool` mode. */
61829
- targetLow: zod.z.number().nullable(),
61905
+ targetLow: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
61830
61906
  /** Target relative humidity (0..100). Null when the device has no
61831
61907
  * humidity control. */
61832
- targetHumidity: zod.z.number().min(0).max(100).nullable(),
61908
+ targetHumidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity")),
61833
61909
  /** Current measured temperature in Celsius. Read-only — pushed by
61834
61910
  * the device's internal sensor. */
61835
- currentTemp: zod.z.number().nullable(),
61911
+ currentTemp: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
61836
61912
  /** Current measured relative humidity (0..100). Read-only. */
61837
- currentHumidity: zod.z.number().min(0).max(100).nullable(),
61913
+ currentHumidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity")),
61838
61914
  /** Vertical louver swing on/off. Null when the device has no vertical
61839
61915
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
61840
61916
  swingVertical: zod.z.boolean().nullable(),
@@ -61842,11 +61918,11 @@ var ClimateControlStatusSchema = zod.z.object({
61842
61918
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
61843
61919
  swingHorizontal: zod.z.boolean().nullable(),
61844
61920
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
61845
- minTemp: zod.z.number().nullable().optional(),
61921
+ minTemp: zod.z.number().nullable().optional().meta(require_sleep.readingMeta("temperature")),
61846
61922
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
61847
- maxTemp: zod.z.number().nullable().optional(),
61923
+ maxTemp: zod.z.number().nullable().optional().meta(require_sleep.readingMeta("temperature")),
61848
61924
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
61849
- tempStep: zod.z.number().positive().nullable().optional(),
61925
+ tempStep: zod.z.number().positive().nullable().optional().meta(require_sleep.readingMeta("temperature")),
61850
61926
  /** Ms epoch when the slice was last updated (push or command). */
61851
61927
  lastFetchedAt: zod.z.number()
61852
61928
  });
@@ -61990,17 +62066,17 @@ var climateControlCapability = {
61990
62066
  * modes to expose in the picker without a slice fetch.
61991
62067
  */
61992
62068
  var RgbTripletSchema = zod.z.object({
61993
- r: zod.z.number().int().min(0).max(255),
61994
- g: zod.z.number().int().min(0).max(255),
61995
- b: zod.z.number().int().min(0).max(255)
62069
+ r: zod.z.number().int().min(0).max(255).meta(require_sleep.readingMeta("none")),
62070
+ g: zod.z.number().int().min(0).max(255).meta(require_sleep.readingMeta("none")),
62071
+ b: zod.z.number().int().min(0).max(255).meta(require_sleep.readingMeta("none"))
61996
62072
  });
61997
62073
  var HsvTripletSchema = zod.z.object({
61998
62074
  /** Hue in degrees, 0..360. */
61999
- h: zod.z.number().min(0).max(360),
62075
+ h: zod.z.number().min(0).max(360).meta(require_sleep.readingMeta("none")),
62000
62076
  /** Saturation as 0..100 inclusive. */
62001
- s: zod.z.number().min(0).max(100),
62077
+ s: zod.z.number().min(0).max(100).meta(require_sleep.readingMeta("none")),
62002
62078
  /** Value/brightness as 0..100 inclusive. */
62003
- v: zod.z.number().min(0).max(100)
62079
+ v: zod.z.number().min(0).max(100).meta(require_sleep.readingMeta("none"))
62004
62080
  });
62005
62081
  var ColorInputSchema = zod.z.discriminatedUnion("mode", [
62006
62082
  zod.z.object({
@@ -62029,7 +62105,7 @@ var ColorStatusSchema = zod.z.object({
62029
62105
  /** Populated when `mode === 'hsv'`. */
62030
62106
  hsv: HsvTripletSchema.optional(),
62031
62107
  /** Populated when `mode === 'mired'`. */
62032
- mireds: zod.z.number().int().optional(),
62108
+ mireds: zod.z.number().int().optional().meta(require_sleep.readingMeta("colour-temperature")),
62033
62109
  /** Ms epoch of the last operator-driven change. */
62034
62110
  lastChangedAt: zod.z.number()
62035
62111
  });
@@ -62311,18 +62387,12 @@ var FORM_OF_KIND = {
62311
62387
  function consumableFormOf(item) {
62312
62388
  return item.form ?? FORM_OF_KIND[consumableKindOf(item)];
62313
62389
  }
62314
- /** Units a remaining-life counter is reported in. Closed: the panel renders
62315
- * each. */
62316
- var ConsumableRemainingUnitSchema = zod.z.enum([
62317
- "days",
62318
- "hours",
62319
- "cycles"
62320
- ]);
62321
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
62322
- * days). `value` null = unknown (D393). */
62390
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
62391
+ var ConsumableRemainingKindSchema = zod.z.enum(["duration", "count"]);
62392
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
62323
62393
  var ConsumableRemainingSchema = zod.z.object({
62324
- value: zod.z.number().min(0).nullable(),
62325
- unit: ConsumableRemainingUnitSchema
62394
+ value: zod.z.number().min(0).nullable().meta(require_sleep.readingMeta("device")),
62395
+ kind: ConsumableRemainingKindSchema
62326
62396
  });
62327
62397
  /** A single consumable item. Either a continuous `level` (remaining
62328
62398
  * life %, or fill % for a `supply` / `waste` item) or a discrete `status`
@@ -62335,7 +62405,7 @@ var ConsumableItemSchema = zod.z.object({
62335
62405
  /** Display name. */
62336
62406
  label: zod.z.string().min(1),
62337
62407
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
62338
- level: zod.z.number().min(0).max(100).nullable(),
62408
+ level: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
62339
62409
  /** The provider's discrete verdict when known (binary mode). */
62340
62410
  status: ConsumableStatusSchema.nullable(),
62341
62411
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -62417,8 +62487,8 @@ var consumablesCustomize = {
62417
62487
  transforms: [{ kind: "identity" }]
62418
62488
  }],
62419
62489
  literals: [{
62420
- path: "remaining.unit",
62421
- values: [...ConsumableRemainingUnitSchema.options]
62490
+ path: "remaining.kind",
62491
+ values: [...ConsumableRemainingKindSchema.options]
62422
62492
  }]
62423
62493
  },
62424
62494
  {
@@ -62547,7 +62617,6 @@ reset: require_sleep.method(zod.z.object({
62547
62617
  },
62548
62618
  "remaining.value": {
62549
62619
  kind: "gauge",
62550
- unitFrom: "remaining.unit",
62551
62620
  deadband: 1,
62552
62621
  displayName: "Remaining"
62553
62622
  }
@@ -62650,49 +62719,26 @@ var ControlKindSchema = zod.z.enum([
62650
62719
  * (empty array for the other kinds). It lives in the slice because
62651
62720
  * HA `select.*` entities may update their options at runtime.
62652
62721
  *
62653
- * The `unit`/`min`/`max`/`step` fields are meaningful for
62654
- * `kind === 'numeric'` and are absent (undefined) for all other
62655
- * kinds. They are populated live from HA attributes on each state
62656
- * push — consistent with `options` and the sensor-unit approach.
62722
+ * Numeric `min` / `max` / `step` live in the slice and share the device
62723
+ * quantity (`SourceInfo.quantity`, D944). Display precision is a read-time
62724
+ * preference. A datetime control's shape is `SourceInfo.dateTimeShape`.
62657
62725
  */
62658
62726
  var ControlStatusSchema = zod.z.object({
62659
62727
  kind: ControlKindSchema,
62660
62728
  /** Current value. Numeric kind → number; select/text/datetime → string.
62661
62729
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
62662
62730
  * or time-only — HA accepts all three forms). */
62663
- value: zod.z.union([zod.z.number(), zod.z.string()]),
62731
+ value: zod.z.union([zod.z.number().meta(require_sleep.readingMeta("device")), zod.z.string()]),
62664
62732
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
62665
62733
  options: zod.z.array(zod.z.string()),
62666
62734
  /** Ms epoch when the slice was last updated. */
62667
62735
  lastChangedAt: zod.z.number(),
62668
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
62669
- * Populated live from HA `attributes.unit_of_measurement` on each push.
62670
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
62671
- unit: zod.z.string().optional(),
62672
- /** Minimum allowed value. Populated live from HA `attributes.min`.
62673
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
62674
- min: zod.z.number().optional(),
62675
- /** Maximum allowed value. Populated live from HA `attributes.max`.
62676
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
62677
- max: zod.z.number().optional(),
62678
- /** Step increment. Populated live from HA `attributes.step`.
62679
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
62680
- step: zod.z.number().optional(),
62681
- /** Suggested decimal places for numeric display. Populated live from HA
62682
- * `attributes.suggested_display_precision`, or derived from `step` when the
62683
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
62684
- * other kinds. Falls back to auto-formatting when absent. */
62685
- precision: zod.z.number().int().min(0).max(10).optional(),
62686
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
62687
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
62688
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
62689
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
62690
- * `has_date`/`has_time` attributes on each push. */
62691
- format: zod.z.enum([
62692
- "date",
62693
- "time",
62694
- "datetime"
62695
- ]).optional()
62736
+ /** Minimum allowed value, in the device quantity's base unit. */
62737
+ min: zod.z.number().optional().meta(require_sleep.readingMeta("device")),
62738
+ /** Maximum allowed value, in the device quantity's base unit. */
62739
+ max: zod.z.number().optional().meta(require_sleep.readingMeta("device")),
62740
+ /** Step increment, in the device quantity's base unit. */
62741
+ step: zod.z.number().optional().meta(require_sleep.readingMeta("device"))
62696
62742
  });
62697
62743
  var ControlSetValueInputSchema = zod.z.discriminatedUnion("kind", [
62698
62744
  zod.z.object({
@@ -62782,9 +62828,9 @@ var CoverStatusSchema = zod.z.object({
62782
62828
  state: CoverStateSchema,
62783
62829
  /** 0 = fully closed, 100 = fully open. Null when the device has
62784
62830
  * no intermediate position surface. */
62785
- position: zod.z.number().min(0).max(100).nullable(),
62831
+ position: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
62786
62832
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
62787
- tiltPosition: zod.z.number().min(0).max(100).nullable(),
62833
+ tiltPosition: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
62788
62834
  /** Ms epoch when the slice was last updated. */
62789
62835
  lastChangedAt: zod.z.number()
62790
62836
  });
@@ -62889,9 +62935,9 @@ var NormalizedRangeSchema$1 = zod.z.object({
62889
62935
  var DayNightStatusSchema = zod.z.object({
62890
62936
  mode: DayNightModeSchema,
62891
62937
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
62892
- sensitivity: zod.z.number().optional(),
62938
+ sensitivity: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
62893
62939
  /** Delay before the IR-cut filter flips, in seconds. */
62894
- switchDelaySec: zod.z.number().optional(),
62940
+ switchDelaySec: zod.z.number().optional().meta(require_sleep.readingMeta("duration")),
62895
62941
  lastFetchedAt: zod.z.number()
62896
62942
  });
62897
62943
  /**
@@ -63124,11 +63170,11 @@ var DishwasherStatusSchema = zod.z.object({
63124
63170
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
63125
63171
  availablePrograms: zod.z.array(zod.z.string()),
63126
63172
  /** Seconds remaining on the active cycle, or null when not running / not known. */
63127
- remainingSeconds: zod.z.number().int().nonnegative().nullable(),
63173
+ remainingSeconds: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration")),
63128
63174
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
63129
- delayedStartSeconds: zod.z.number().int().nonnegative().nullable(),
63175
+ delayedStartSeconds: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration")),
63130
63176
  /** Cycle completion 0..100, or null when not applicable. */
63131
- progressPercent: zod.z.number().min(0).max(100).nullable(),
63177
+ progressPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
63132
63178
  /** True when the door is reported open; null when the door state is not known. */
63133
63179
  doorOpen: zod.z.boolean().nullable(),
63134
63180
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -63152,7 +63198,7 @@ var DishwasherStatusSchema = zod.z.object({
63152
63198
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
63153
63199
  machineCareDue: zod.z.boolean().nullable(),
63154
63200
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
63155
- machineCareRemainingRuns: zod.z.number().int().nonnegative().nullable(),
63201
+ machineCareRemainingRuns: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("count")),
63156
63202
  /** Option states: null when the appliance does not expose the option or has not said. */
63157
63203
  halfLoad: zod.z.boolean().nullable(),
63158
63204
  hygienePlus: zod.z.boolean().nullable(),
@@ -63167,7 +63213,7 @@ var DishwasherStatusSchema = zod.z.object({
63167
63213
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
63168
63214
  activeEvents: zod.z.array(zod.z.string()),
63169
63215
  /** Numeric error code when the appliance reports one; null otherwise. */
63170
- errorCode: zod.z.number().nullable(),
63216
+ errorCode: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
63171
63217
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
63172
63218
  errorLabel: zod.z.string().nullable(),
63173
63219
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -63383,6 +63429,7 @@ var doorbellCustomize = {
63383
63429
  applyAuthority: { kind: "composition-claim" },
63384
63430
  eventName: "onPressed",
63385
63431
  edges: ["rising"],
63432
+ defaultEdge: "rising",
63386
63433
  timestampPath: "lastPressedAt",
63387
63434
  counterPath: "pressCountSinceStart",
63388
63435
  highWater: "composer-advance",
@@ -63401,7 +63448,7 @@ var DoorbellStatusSchema = zod.z.object({
63401
63448
  /** Ms epoch of the last press. null = never observed since this provider started. */
63402
63449
  lastPressedAt: zod.z.number().nullable(),
63403
63450
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
63404
- pressCountSinceStart: zod.z.number()
63451
+ pressCountSinceStart: zod.z.number().meta(require_sleep.readingMeta("count"))
63405
63452
  });
63406
63453
  var DoorbellPressEventSchema = zod.z.object({
63407
63454
  deviceId: zod.z.number(),
@@ -63486,12 +63533,11 @@ var EnumSensorDateTimeFormatSchema = zod.z.enum([
63486
63533
  var EnumSensorStatusSchema = zod.z.object({
63487
63534
  value: zod.z.string(),
63488
63535
  /**
63489
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
63490
- * as a locale date/time/datetime; absent for plain enum sensors. HA
63491
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
63492
- * a time-only sensor → `'time'`.
63536
+ * Set when `value` is a timestamp rather than an enum state, so history
63537
+ * does not learn ISO strings as states. The display shape (date / time /
63538
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
63493
63539
  */
63494
- format: EnumSensorDateTimeFormatSchema.optional(),
63540
+ timestamp: zod.z.boolean().optional(),
63495
63541
  /** Ms epoch when the slice was last updated. */
63496
63542
  lastFetchedAt: zod.z.number()
63497
63543
  });
@@ -63518,11 +63564,11 @@ var enumSensorCapability = {
63518
63564
  * History (D850): the value as an `enum` series (an appliance's program or
63519
63565
  * operation state, an HVAC action). The option set is per device, so the
63520
63566
  * value list is LEARNED as values are first seen (bounded). A sensor with a
63521
- * `format` carries a timestamp, not a state, and is not recorded.
63567
+ * `timestamp` carries a clock, not a state, and is not recorded.
63522
63568
  */
63523
63569
  history: { fields: { value: {
63524
63570
  kind: "enum",
63525
- skipWhenSet: "format"
63571
+ skipWhenSet: "timestamp"
63526
63572
  } } },
63527
63573
  /** Clock fields: written, but excluded from the compare that decides
63528
63574
  * whether persisting is worth a SQLite commit. */
@@ -63544,13 +63590,13 @@ var EventFireSchema = zod.z.object({
63544
63590
  deviceId: zod.z.number(),
63545
63591
  eventType: zod.z.string(),
63546
63592
  data: zod.z.record(zod.z.string(), zod.z.unknown()).nullable(),
63547
- timestamp: zod.z.number(),
63548
- seq: zod.z.number()
63593
+ timestamp: zod.z.number().meta(require_sleep.readingMeta("none")),
63594
+ seq: zod.z.number().meta(require_sleep.readingMeta("count"))
63549
63595
  });
63550
63596
  var EventEmitterStatusSchema = zod.z.object({
63551
63597
  eventTypes: zod.z.array(zod.z.string()),
63552
63598
  lastEvent: EventFireSchema.nullable(),
63553
- eventCountSinceStart: zod.z.number()
63599
+ eventCountSinceStart: zod.z.number().meta(require_sleep.readingMeta("count"))
63554
63600
  });
63555
63601
  var eventEmitterCapability = {
63556
63602
  name: "event-emitter",
@@ -64007,10 +64053,10 @@ var FanDirectionSchema = zod.z.enum(["forward", "reverse"]);
64007
64053
  var FanControlStatusSchema = zod.z.object({
64008
64054
  /** Active speed as 0..100 inclusive. Null when the device has no
64009
64055
  * speed surface (single-speed fan). */
64010
- percentage: zod.z.number().min(0).max(100).nullable(),
64056
+ percentage: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
64011
64057
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
64012
64058
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
64013
- percentageStep: zod.z.number().positive().optional(),
64059
+ percentageStep: zod.z.number().positive().optional().meta(require_sleep.readingMeta("percentage")),
64014
64060
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
64015
64061
  * — empty string when no preset is active or supported. */
64016
64062
  preset: zod.z.string(),
@@ -64133,7 +64179,7 @@ var FeatureProbeStatusSchema = zod.z.object({
64133
64179
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
64134
64180
  model: zod.z.string().nullable(),
64135
64181
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
64136
- channelCount: zod.z.number().nullable(),
64182
+ channelCount: zod.z.number().nullable().meta(require_sleep.readingMeta("count")),
64137
64183
  /**
64138
64184
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
64139
64185
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -64264,7 +64310,7 @@ var gasCapability = {
64264
64310
  //#region src/capabilities/heating-valve.cap.ts
64265
64311
  var HeatingValveStatusSchema = zod.z.object({
64266
64312
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
64267
- position: zod.z.number().min(0).max(100).nullable(),
64313
+ position: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
64268
64314
  /** Ms epoch when the slice was last updated (push or poll). */
64269
64315
  lastFetchedAt: zod.z.number()
64270
64316
  });
@@ -64309,9 +64355,9 @@ var HumidifierStatusSchema = zod.z.object({
64309
64355
  /** Whether the humidifier is currently on. */
64310
64356
  on: zod.z.boolean(),
64311
64357
  /** Current measured relative humidity (0..100). Null when not reported. */
64312
- currentHumidity: zod.z.number().min(0).max(100).nullable(),
64358
+ currentHumidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity")),
64313
64359
  /** Target relative humidity (0..100). Null when no setpoint surface. */
64314
- targetHumidity: zod.z.number().min(0).max(100).nullable(),
64360
+ targetHumidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity")),
64315
64361
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
64316
64362
  * device has no mode surface. */
64317
64363
  mode: zod.z.string().nullable(),
@@ -64321,9 +64367,9 @@ var HumidifierStatusSchema = zod.z.object({
64321
64367
  * `idle` / `off`). Null when the device doesn't report it. */
64322
64368
  action: zod.z.string().nullable(),
64323
64369
  /** HA `min_humidity` attribute. Null → UI uses 0. */
64324
- minHumidity: zod.z.number().nullable(),
64370
+ minHumidity: zod.z.number().nullable().meta(require_sleep.readingMeta("humidity")),
64325
64371
  /** HA `max_humidity` attribute. Null → UI uses 100. */
64326
- maxHumidity: zod.z.number().nullable(),
64372
+ maxHumidity: zod.z.number().nullable().meta(require_sleep.readingMeta("humidity")),
64327
64373
  /** Ms epoch when the slice was last updated. */
64328
64374
  lastChangedAt: zod.z.number()
64329
64375
  });
@@ -64395,22 +64441,13 @@ var humidifierCapability = {
64395
64441
  * `sourceInfo`.
64396
64442
  */
64397
64443
  var HumiditySensorStatusSchema = zod.z.object({
64398
- /** Current relative humidity, 0..100. */
64399
- percent: zod.z.number().min(0).max(100).meta({
64400
- unit: "%",
64444
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
64445
+ percent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity", {
64446
+ precision: 0,
64401
64447
  label: "Humidity"
64402
- }),
64448
+ })),
64403
64449
  /** Ms epoch when the slice was last updated. */
64404
- lastFetchedAt: zod.z.number(),
64405
- /** Live display unit from the upstream source (e.g. HA
64406
- * `attributes.unit_of_measurement`). The UI prefers this over the
64407
- * role's canonical unit. Absent → fall back to the canonical unit. */
64408
- unit: zod.z.string().optional(),
64409
- /** Suggested decimal places for numeric display.
64410
- * Populated live from the upstream source when provided (e.g. HA
64411
- * `attributes.suggested_display_precision`). Falls back to
64412
- * auto-formatting when absent. */
64413
- precision: zod.z.number().int().min(0).max(10).optional()
64450
+ lastFetchedAt: zod.z.number()
64414
64451
  });
64415
64452
  var humiditySensorCapability = {
64416
64453
  name: "humidity-sensor",
@@ -64441,12 +64478,7 @@ var humiditySensorCapability = {
64441
64478
  } } },
64442
64479
  /** Clock fields: written, but excluded from the compare that decides
64443
64480
  * whether persisting is worth a SQLite commit. */
64444
- volatileStateFields: ["lastFetchedAt"],
64445
- customize: {
64446
- version: 1,
64447
- advanced: "derive",
64448
- targets: []
64449
- }
64481
+ volatileStateFields: ["lastFetchedAt"]
64450
64482
  };
64451
64483
  //#endregion
64452
64484
  //#region src/capabilities/image.cap.ts
@@ -64469,7 +64501,7 @@ var ImageStatusSchema = zod.z.object({
64469
64501
  * entity exposes no `entity_picture` (yet). */
64470
64502
  url: zod.z.string().nullable(),
64471
64503
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
64472
- lastUpdated: zod.z.number().nullable()
64504
+ lastUpdated: zod.z.number().nullable().meta(require_sleep.readingMeta("none"))
64473
64505
  });
64474
64506
  var imageCapability = {
64475
64507
  name: "image",
@@ -64559,19 +64591,19 @@ var NormalizedRangeSchema = zod.z.object({
64559
64591
  */
64560
64592
  var ImageSettingsStatusSchema = zod.z.object({
64561
64593
  /** Normalized 0–100. */
64562
- brightness: zod.z.number().optional(),
64594
+ brightness: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
64563
64595
  /** Normalized 0–100. */
64564
- contrast: zod.z.number().optional(),
64596
+ contrast: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
64565
64597
  /** Normalized 0–100. */
64566
- saturation: zod.z.number().optional(),
64598
+ saturation: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
64567
64599
  /** Normalized 0–100. */
64568
- sharpness: zod.z.number().optional(),
64600
+ sharpness: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
64569
64601
  mirror: zod.z.boolean().optional(),
64570
64602
  flip: zod.z.boolean().optional(),
64571
64603
  rotate: ImageRotateSchema.optional(),
64572
64604
  whiteBalance: WhiteBalanceModeSchema.optional(),
64573
64605
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
64574
- warmth: zod.z.number().optional(),
64606
+ warmth: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
64575
64607
  exposureMode: ExposureModeSchema.optional(),
64576
64608
  backlightMode: BacklightModeSchema.optional(),
64577
64609
  lastFetchedAt: zod.z.number()
@@ -64872,10 +64904,10 @@ var IntercomAbilitySchema = zod.z.object({
64872
64904
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
64873
64905
  codecs: zod.z.array(zod.z.string()),
64874
64906
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
64875
- sampleRate: zod.z.number(),
64907
+ sampleRate: zod.z.number().meta(require_sleep.readingMeta("none")),
64876
64908
  duplex: zod.z.enum(["half", "full"]),
64877
64909
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
64878
- maxBacklogMs: zod.z.number()
64910
+ maxBacklogMs: zod.z.number().meta(require_sleep.readingMeta("duration-ms"))
64879
64911
  });
64880
64912
  var IntercomStatusSchema = zod.z.object({
64881
64913
  /** True while a talk session is open. */
@@ -65104,13 +65136,13 @@ var LawnMowerControlStatusSchema = zod.z.object({
65104
65136
  activity: LawnMowerActivitySchema,
65105
65137
  /** 0..100 battery percentage. Null when the device has no battery
65106
65138
  * reading. */
65107
- batteryLevel: zod.z.number().min(0).max(100).nullable(),
65139
+ batteryLevel: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
65108
65140
  /** 0..100 mowing-completion percentage of the current task, or null when no
65109
65141
  * task is active / progress is unavailable. */
65110
- progressPercent: zod.z.number().min(0).max(100).nullable(),
65142
+ progressPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
65111
65143
  /** Current device/event code (dynamic — mostly status, sometimes an error),
65112
65144
  * or null when unknown. */
65113
- currentCode: zod.z.number().nullable(),
65145
+ currentCode: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
65114
65146
  /** Human label for {@link currentCode}, or null when undecodable. */
65115
65147
  currentCodeLabel: zod.z.string().nullable(),
65116
65148
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -65840,7 +65872,7 @@ var MediaPlayerStatusSchema = zod.z.object({
65840
65872
  state: MediaPlayerStateSchema,
65841
65873
  /** Volume as 0..100 inclusive. Null when the device has no volume
65842
65874
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
65843
- volumeLevel: zod.z.number().min(0).max(100).nullable(),
65875
+ volumeLevel: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
65844
65876
  /** Mute toggle distinct from volume=0. Null when no mute surface.
65845
65877
  * Pair with `DeviceFeature.MediaPlayerMute`. */
65846
65878
  isMuted: zod.z.boolean().nullable(),
@@ -65853,10 +65885,10 @@ var MediaPlayerStatusSchema = zod.z.object({
65853
65885
  currentMedia: MediaInfoSchema.nullable(),
65854
65886
  /** Current playback position in ms. Null when not seekable or
65855
65887
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
65856
- positionMs: zod.z.number().int().nonnegative().nullable(),
65888
+ positionMs: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration-ms")),
65857
65889
  /** Total duration of the current media in ms. Null when unknown
65858
65890
  * (live stream) or nothing is playing. */
65859
- durationMs: zod.z.number().int().nonnegative().nullable(),
65891
+ durationMs: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration-ms")),
65860
65892
  /** Shuffle toggle. Null when no shuffle surface.
65861
65893
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
65862
65894
  shuffle: zod.z.boolean().nullable(),
@@ -66236,7 +66268,7 @@ var motionCustomize = {
66236
66268
  eventName: "onMotionChanged"
66237
66269
  },
66238
66270
  deviceTypes: [require_err_msg.DeviceType.Camera],
66239
- apply: "replace",
66271
+ apply: "add",
66240
66272
  applyAuthority: {
66241
66273
  kind: "camera-sensors",
66242
66274
  timeoutField: "motionCooldownMs"
@@ -66333,7 +66365,7 @@ var MotionStatusSchema = zod.z.object({
66333
66365
  * it, every provider seeded it at boot, and every session left it
66334
66366
  * `detected: true` after its detach.
66335
66367
  */
66336
- autoClearAfterMs: zod.z.number().nullable()
66368
+ autoClearAfterMs: zod.z.number().nullable().meta(require_sleep.readingMeta("duration-ms"))
66337
66369
  });
66338
66370
  /**
66339
66371
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -66519,7 +66551,7 @@ var MotionZoneRegionSchema = zod.z.object({
66519
66551
  * the grid region(s). */
66520
66552
  var MotionZoneStatusSchema = zod.z.object({
66521
66553
  enabled: zod.z.boolean(),
66522
- sensitivity: zod.z.number(),
66554
+ sensitivity: zod.z.number().meta(require_sleep.readingMeta("none")),
66523
66555
  /** Grid region(s). Today exactly one `grid` shape. */
66524
66556
  regions: zod.z.array(MotionZoneRegionSchema),
66525
66557
  lastFetchedAt: zod.z.number()
@@ -67035,7 +67067,7 @@ var NavigationStatusSchema = zod.z.object({
67035
67067
  following: zod.z.boolean(),
67036
67068
  flash: zod.z.boolean(),
67037
67069
  lightMode: NavigationLightModeSchema,
67038
- lightLevel: zod.z.number().min(40).max(100),
67070
+ lightLevel: zod.z.number().min(40).max(100).meta(require_sleep.readingMeta("none")),
67039
67071
  /** Ms epoch when the slice was last updated. */
67040
67072
  lastChangedAt: zod.z.number()
67041
67073
  });
@@ -67186,13 +67218,13 @@ var NetworkLinkStatusSchema = zod.z.object({
67186
67218
  * one whose reading has not landed must not be drawn at 0 %. Consumers
67187
67219
  * SKIP a null rather than coerce it.
67188
67220
  */
67189
- signalPercent: zod.z.number().min(0).max(100).nullable(),
67221
+ signalPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
67190
67222
  /** Raw received signal strength in dBm, when the firmware reports one. */
67191
- rssiDbm: zod.z.number().optional(),
67223
+ rssiDbm: zod.z.number().optional().meta(require_sleep.readingMeta("signal-level")),
67192
67224
  /** Network name of a wireless link, when the firmware reports it. */
67193
67225
  ssid: zod.z.string().optional(),
67194
67226
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
67195
- lastUpdated: zod.z.number()
67227
+ lastUpdated: zod.z.number().meta(require_sleep.readingMeta("none"))
67196
67228
  });
67197
67229
  /** The slice a provider seeds before its first read: nothing is known yet. */
67198
67230
  var NETWORK_LINK_UNKNOWN = {
@@ -67668,7 +67700,7 @@ var NotifierStatusSchema = zod.z.object({
67668
67700
  lastError: zod.z.string().nullable(),
67669
67701
  /** Number of deliveries currently buffered server-side (rate-limited
67670
67702
  * notifiers may queue). 0 when the provider sends synchronously. */
67671
- queueDepth: zod.z.number().int().nonnegative(),
67703
+ queueDepth: zod.z.number().int().nonnegative().meta(require_sleep.readingMeta("count")),
67672
67704
  /** Per-feature capability matrix — authoritative source for the
67673
67705
  * compose-form field gating. */
67674
67706
  supports: NotifierSupportsSchema
@@ -67762,11 +67794,9 @@ onSent: { data: zod.z.object({
67762
67794
  * cap fits (HA `sensor` whose `device_class` we don't have a typed cap
67763
67795
  * for: water flow, distance, weight, frequency, signal strength, …).
67764
67796
  *
67765
- * The `unit` and `precision` fields carry the live display metadata that
67766
- * the upstream source provides with each state push (HA
67767
- * `attributes.unit_of_measurement` / `attributes.suggested_display_precision`).
67768
- * The UI reads them directly from the slice — they are the single source
67769
- * of truth for generic numeric rendering and do NOT live on `sourceInfo`.
67797
+ * The quantity is not on this slice. It is chosen when the device is
67798
+ * adopted and stored once on `SourceInfo.quantity` (D944). `value` is in
67799
+ * that quantity's base unit. `null` is a refused sample (D393).
67770
67800
  *
67771
67801
  * Prefer the typed alternatives (`temperature-sensor`, `humidity-sensor`,
67772
67802
  * `ambient-light-sensor`, `pressure-sensor`, `power-meter`,
@@ -67775,15 +67805,8 @@ onSent: { data: zod.z.object({
67775
67805
  * numeric values land here.
67776
67806
  */
67777
67807
  var NumericSensorStatusSchema = zod.z.object({
67778
- value: zod.z.number(),
67779
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
67780
- * Populated live from the upstream source on each state push.
67781
- * Absent for unitless sensors. */
67782
- unit: zod.z.string().optional(),
67783
- /** Suggested decimal places for numeric display.
67784
- * Populated live from the upstream source when provided.
67785
- * Falls back to auto-formatting when absent. */
67786
- precision: zod.z.number().int().min(0).max(10).optional(),
67808
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
67809
+ value: zod.z.number().nullable().meta(require_sleep.readingMeta("device")),
67787
67810
  /** Ms epoch when the slice was last updated. */
67788
67811
  lastFetchedAt: zod.z.number()
67789
67812
  });
@@ -67807,11 +67830,11 @@ var numericSensorCapability = {
67807
67830
  */
67808
67831
  durability: "restored",
67809
67832
  /**
67810
- * History (D850): recorded series. The unit is per device, read from the sibling `unit` field.
67833
+ * History (D850): recorded series. The unit is the device quantity's base,
67834
+ * resolved when the series is read — the cap has no single unit.
67811
67835
  */
67812
67836
  history: { fields: { value: {
67813
67837
  kind: "gauge",
67814
- unitFrom: "unit",
67815
67838
  minIntervalMs: 3e4
67816
67839
  } } },
67817
67840
  /** Clock fields: written, but excluded from the compare that decides
@@ -68268,23 +68291,26 @@ var PetFeederStatusSchema = zod.z.object({
68268
68291
  /** Food currently in the bowl (grams). Null when the device has not
68269
68292
  * reported a reading yet. On dual-hopper models this is the combined
68270
68293
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
68271
- foodLevel: zod.z.number().nullable(),
68294
+ foodLevel: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
68272
68295
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
68273
68296
  * single-hopper models. */
68274
- food1: zod.z.number().nullable(),
68297
+ food1: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
68275
68298
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
68276
68299
  * single-hopper models. */
68277
- food2: zod.z.number().nullable(),
68300
+ food2: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
68278
68301
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
68279
68302
  * (`device_class: problem`, on = low). True when the bowl is empty /
68280
68303
  * below the feeder's low threshold. */
68281
68304
  lowFood: zod.z.boolean(),
68282
68305
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
68283
68306
  * device has no battery reading. */
68284
- batteryPower: zod.z.number().min(0).max(100).nullable(),
68285
- /** Days of desiccant life remaining. Null when the model has no
68286
- * desiccant sensor. */
68287
- desiccantLeftDays: zod.z.number().nullable(),
68307
+ batteryPower: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
68308
+ /**
68309
+ * Desiccant life remaining, stored in seconds (duration base, D944).
68310
+ * Providers convert from the days the feeder reports. Null when the
68311
+ * model has no desiccant sensor.
68312
+ */
68313
+ desiccantLeftDays: zod.z.number().nullable().meta(require_sleep.readingMeta("duration")),
68288
68314
  /** True while a feed is in progress. */
68289
68315
  feeding: zod.z.boolean(),
68290
68316
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -68295,7 +68321,7 @@ var PetFeederStatusSchema = zod.z.object({
68295
68321
  * with `errorCode` for consumers that want the raw integer. */
68296
68322
  error: zod.z.string().nullable(),
68297
68323
  /** Raw device error code (0 / null = no error). */
68298
- errorCode: zod.z.number().nullable(),
68324
+ errorCode: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
68299
68325
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
68300
68326
  isDualHopper: zod.z.boolean(),
68301
68327
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -68305,7 +68331,7 @@ var PetFeederStatusSchema = zod.z.object({
68305
68331
  /** Play a chime when dispensing. */
68306
68332
  feedSound: zod.z.boolean(),
68307
68333
  /** Speaker / prompt volume level (device-scaled integer). */
68308
- volume: zod.z.number(),
68334
+ volume: zod.z.number().meta(require_sleep.readingMeta("none")),
68309
68335
  /** Ms epoch when the slice was last refreshed from the cloud. */
68310
68336
  lastFetchedAt: zod.z.number()
68311
68337
  });
@@ -68424,7 +68450,7 @@ var petFeederCapability = {
68424
68450
  },
68425
68451
  desiccantLeftDays: {
68426
68452
  kind: "gauge",
68427
- unit: "days"
68453
+ unit: "s"
68428
68454
  }
68429
68455
  } }
68430
68456
  };
@@ -69630,36 +69656,19 @@ var platformProbeCapability = {
69630
69656
  */
69631
69657
  var PowerMeterStatusSchema = zod.z.object({
69632
69658
  /** Instantaneous power draw in watts. */
69633
- watts: zod.z.number().optional().meta({
69634
- unit: "W",
69635
- label: "Power"
69636
- }),
69659
+ watts: zod.z.number().optional().meta(require_sleep.readingMeta("power", { label: "Power" })),
69637
69660
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
69638
- kwhTotal: zod.z.number().optional().meta({
69639
- unit: "kWh",
69640
- label: "Energy"
69641
- }),
69661
+ kwhTotal: zod.z.number().optional().meta(require_sleep.readingMeta("energy", { label: "Energy" })),
69642
69662
  /** Voltage in volts. */
69643
- volts: zod.z.number().optional(),
69663
+ volts: zod.z.number().optional().meta(require_sleep.readingMeta("voltage")),
69644
69664
  /** Current in amperes. */
69645
- amps: zod.z.number().optional(),
69646
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
69647
- va: zod.z.number().optional(),
69665
+ amps: zod.z.number().optional().meta(require_sleep.readingMeta("current")),
69666
+ /** Apparent power in volt-amperes. */
69667
+ va: zod.z.number().optional().meta(require_sleep.readingMeta("apparent-power")),
69648
69668
  /** Power factor (0..1) if reported. */
69649
- powerFactor: zod.z.number().min(0).max(1).optional(),
69669
+ powerFactor: zod.z.number().min(0).max(1).optional().meta(require_sleep.readingMeta("ratio")),
69650
69670
  /** Ms epoch when the slice was last updated. */
69651
- lastFetchedAt: zod.z.number(),
69652
- /** Live display unit of the single metric this slice carries (e.g. HA
69653
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
69654
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
69655
- * unit per slice is unambiguous. The UI prefers this over the role's
69656
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
69657
- unit: zod.z.string().optional(),
69658
- /** Suggested decimal places for numeric display.
69659
- * Populated live from the upstream source when provided (e.g. HA
69660
- * `attributes.suggested_display_precision`). Falls back to
69661
- * auto-formatting when absent. */
69662
- precision: zod.z.number().int().min(0).max(10).optional()
69671
+ lastFetchedAt: zod.z.number()
69663
69672
  });
69664
69673
  var powerMeterCapability = {
69665
69674
  name: "power-meter",
@@ -69723,12 +69732,7 @@ var powerMeterCapability = {
69723
69732
  } },
69724
69733
  /** Clock fields: written, but excluded from the compare that decides
69725
69734
  * whether persisting is worth a SQLite commit. */
69726
- volatileStateFields: ["lastFetchedAt"],
69727
- customize: {
69728
- version: 1,
69729
- advanced: "derive",
69730
- targets: []
69731
- }
69735
+ volatileStateFields: ["lastFetchedAt"]
69732
69736
  };
69733
69737
  //#endregion
69734
69738
  //#region src/capabilities/presence.cap.ts
@@ -69750,11 +69754,11 @@ var powerMeterCapability = {
69750
69754
  */
69751
69755
  var GpsLocationSchema = zod.z.object({
69752
69756
  /** Latitude in decimal degrees, -90..90. */
69753
- latitude: zod.z.number().min(-90).max(90),
69757
+ latitude: zod.z.number().min(-90).max(90).meta(require_sleep.readingMeta("angle")),
69754
69758
  /** Longitude in decimal degrees, -180..180. */
69755
- longitude: zod.z.number().min(-180).max(180),
69759
+ longitude: zod.z.number().min(-180).max(180).meta(require_sleep.readingMeta("angle")),
69756
69760
  /** Reported accuracy in meters (lower = better). */
69757
- accuracyMeters: zod.z.number().nonnegative()
69761
+ accuracyMeters: zod.z.number().nonnegative().meta(require_sleep.readingMeta("length"))
69758
69762
  });
69759
69763
  var PresenceStatusSchema = zod.z.object({
69760
69764
  /** `home` / `not_home` / any user-defined zone name. */
@@ -69766,7 +69770,7 @@ var PresenceStatusSchema = zod.z.object({
69766
69770
  gps: GpsLocationSchema.nullable(),
69767
69771
  /** Optional battery level of the tracking device (0..100). Null
69768
69772
  * when not reported. */
69769
- batteryPercent: zod.z.number().min(0).max(100).nullable(),
69773
+ batteryPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
69770
69774
  /** Ms epoch when the slice was last updated. */
69771
69775
  lastChangedAt: zod.z.number()
69772
69776
  });
@@ -69818,19 +69822,10 @@ var presenceCapability = {
69818
69822
  * `sourceInfo`.
69819
69823
  */
69820
69824
  var PressureSensorStatusSchema = zod.z.object({
69821
- /** Current pressure in hPa. */
69822
- hpa: zod.z.number(),
69825
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
69826
+ hpa: zod.z.number().nullable().meta(require_sleep.readingMeta("pressure", { precision: 0 })),
69823
69827
  /** Ms epoch when the slice was last updated. */
69824
- lastFetchedAt: zod.z.number(),
69825
- /** Live display unit from the upstream source (e.g. HA
69826
- * `attributes.unit_of_measurement`). The UI prefers this over the
69827
- * role's canonical unit. Absent → fall back to the canonical unit. */
69828
- unit: zod.z.string().optional(),
69829
- /** Suggested decimal places for numeric display.
69830
- * Populated live from the upstream source when provided (e.g. HA
69831
- * `attributes.suggested_display_precision`). Falls back to
69832
- * auto-formatting when absent. */
69833
- precision: zod.z.number().int().min(0).max(10).optional()
69828
+ lastFetchedAt: zod.z.number()
69834
69829
  });
69835
69830
  var pressureSensorCapability = {
69836
69831
  name: "pressure-sensor",
@@ -70397,8 +70392,8 @@ var ptzCapability = {
70397
70392
  var AutotrackTargetTypeSchema = zod.z.string().describe("Vendor target string (people/vehicle/pet); empty = camera default");
70398
70393
  var PtzAutotrackSettingsSchema = zod.z.object({
70399
70394
  targetType: AutotrackTargetTypeSchema,
70400
- stopDelaySeconds: zod.z.number().int().min(0).max(300),
70401
- disappearDelaySeconds: zod.z.number().int().min(0).max(300)
70395
+ stopDelaySeconds: zod.z.number().int().min(0).max(300).meta(require_sleep.readingMeta("duration")),
70396
+ disappearDelaySeconds: zod.z.number().int().min(0).max(300).meta(require_sleep.readingMeta("duration"))
70402
70397
  });
70403
70398
  /**
70404
70399
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -70565,7 +70560,7 @@ var RunningStateStatusSchema = zod.z.object({
70565
70560
  /** null = unknown — never coerced to false (D393). */
70566
70561
  running: zod.z.boolean().nullable(),
70567
70562
  /** Epoch ms the current boolean began; null while `running` is null. */
70568
- since: zod.z.number().nullable(),
70563
+ since: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
70569
70564
  source: RunningStateSourceSchema.meta({ label: "Source" }),
70570
70565
  phase: zod.z.string().nullable(),
70571
70566
  /** null until a reading has been published (D393). */
@@ -70580,7 +70575,6 @@ var RUNNING_DEVICE_TYPES = Object.values(require_err_msg.DeviceType);
70580
70575
  /** Generic Customize target for the running boolean (D936). */
70581
70576
  var runningCustomize = {
70582
70577
  version: 1,
70583
- advanced: "derive",
70584
70578
  targets: [{
70585
70579
  id: "running",
70586
70580
  cardinality: "scalar",
@@ -70600,7 +70594,7 @@ var runningCustomize = {
70600
70594
  require_err_msg.DeviceType.Thermostat,
70601
70595
  require_err_msg.DeviceType.PetFeeder
70602
70596
  ],
70603
- apply: "add-or-replace",
70597
+ apply: "add",
70604
70598
  path: "running",
70605
70599
  sources: [
70606
70600
  {
@@ -70616,6 +70610,7 @@ var runningCustomize = {
70616
70610
  requiredUnit: "W",
70617
70611
  transforms: [{
70618
70612
  kind: "threshold",
70613
+ comparisons: ["gte", "lte"],
70619
70614
  comparison: "gte",
70620
70615
  defaultThreshold: 5,
70621
70616
  defaultStartDwellMs: RUNNING_POWER_START_DELAY_MS,
@@ -72447,9 +72442,9 @@ var RecordTriggerSchema = zod.z.enum([
72447
72442
  */
72448
72443
  var RecordWindowSchema = zod.z.object({
72449
72444
  trigger: RecordTriggerSchema,
72450
- day: zod.z.number().int().min(0).max(6),
72451
- startMinute: zod.z.number().int().min(0).max(1439),
72452
- endMinute: zod.z.number().int().min(1).max(1440)
72445
+ day: zod.z.number().int().min(0).max(6).meta(require_sleep.readingMeta("none")),
72446
+ startMinute: zod.z.number().int().min(0).max(1439).meta(require_sleep.readingMeta("none")),
72447
+ endMinute: zod.z.number().int().min(1).max(1440).meta(require_sleep.readingMeta("none"))
72453
72448
  });
72454
72449
  /** Status of one physical volume, as the camera itself describes it. */
72455
72450
  var OnboardStorageVolumeSchema = zod.z.object({
@@ -72548,10 +72543,10 @@ var RecordingOnboardStatusSchema = zod.z.object({
72548
72543
  /** Master "record to the card at all" switch. */
72549
72544
  enabled: zod.z.boolean().nullable(),
72550
72545
  overwriteWhenFull: zod.z.boolean().nullable(),
72551
- preRecordSec: zod.z.number().nullable(),
72552
- postRecordSec: zod.z.number().nullable(),
72546
+ preRecordSec: zod.z.number().nullable().meta(require_sleep.readingMeta("duration")),
72547
+ postRecordSec: zod.z.number().nullable().meta(require_sleep.readingMeta("duration")),
72553
72548
  /** Length of one recorded file, in minutes. */
72554
- segmentMinutes: zod.z.number().nullable(),
72549
+ segmentMinutes: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
72555
72550
  /** The primary track's weekly windows, flattened. */
72556
72551
  windows: zod.z.array(RecordWindowSchema),
72557
72552
  /**
@@ -72567,7 +72562,7 @@ var RecordingOnboardStatusSchema = zod.z.object({
72567
72562
  * provider reporting a non-zero count also reports the schedule as not
72568
72563
  * writable.
72569
72564
  */
72570
- unreadableWindows: zod.z.number(),
72565
+ unreadableWindows: zod.z.number().meta(require_sleep.readingMeta("count")),
72571
72566
  /**
72572
72567
  * The camera is scheduled to record and has NO usable storage.
72573
72568
  *
@@ -73739,7 +73734,7 @@ var CamStreamDescriptorSchema = zod.z.object({
73739
73734
  url: zod.z.string().optional(),
73740
73735
  codec: zod.z.string().optional(),
73741
73736
  resolution: require_sleep.CamStreamResolutionSchema.optional(),
73742
- fps: zod.z.number().positive().optional(),
73737
+ fps: zod.z.number().positive().optional().meta(require_sleep.readingMeta("rate")),
73743
73738
  label: zod.z.string().optional(),
73744
73739
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
73745
73740
  deviceFeatures: zod.z.array(zod.z.string()).optional(),
@@ -73828,18 +73823,18 @@ var StreamProfileSchema = zod.z.enum([
73828
73823
  ]);
73829
73824
  /** The current encoding config of one stream profile. */
73830
73825
  var StreamProfileConfigSchema = zod.z.object({
73831
- width: zod.z.number(),
73832
- height: zod.z.number(),
73826
+ width: zod.z.number().meta(require_sleep.readingMeta("none")),
73827
+ height: zod.z.number().meta(require_sleep.readingMeta("none")),
73833
73828
  codec: zod.z.enum(["h264", "h265"]),
73834
- framerate: zod.z.number(),
73835
- bitrate: zod.z.number(),
73829
+ framerate: zod.z.number().meta(require_sleep.readingMeta("rate")),
73830
+ bitrate: zod.z.number().meta(require_sleep.readingMeta("bitrate")),
73836
73831
  bitrateMode: zod.z.enum(["vbr", "cbr"]).optional(),
73837
73832
  encoderProfile: zod.z.enum([
73838
73833
  "high",
73839
73834
  "main",
73840
73835
  "baseline"
73841
73836
  ]).optional(),
73842
- gop: zod.z.number().optional(),
73837
+ gop: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
73843
73838
  /**
73844
73839
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
73845
73840
  *
@@ -74321,12 +74316,7 @@ var switchCapability = {
74321
74316
  label: "On",
74322
74317
  kind: "boolean"
74323
74318
  }
74324
- }] },
74325
- customize: {
74326
- version: 1,
74327
- advanced: "derive",
74328
- targets: []
74329
- }
74319
+ }] }
74330
74320
  };
74331
74321
  //#endregion
74332
74322
  //#region src/system/server-name.ts
@@ -75074,35 +75064,21 @@ var tamperCapability = {
75074
75064
  * entries with `device_class: temperature` and any future native
75075
75065
  * thermometer.
75076
75066
  *
75077
- * Unit normalisation: providers convert to Celsius before storing.
75078
- * The slice value is always Celsius so cross-cap aggregators
75079
- * (climate-control's `currentTemp`, energy analytics) can compose
75080
- * without per-source unit fixups. The canonical display unit (`°C`) is
75081
- * a descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
75082
- * `sourceInfo`.
75067
+ * The number is Celsius (D944). Providers convert on ingest. A failed
75068
+ * conversion is `null`, never a guess. Display unit is a read-time preference.
75083
75069
  *
75084
75070
  * Status `lastFetchedAt` lets staleness-aware consumers detect a
75085
75071
  * frozen feed (provider hung) distinct from a "temperature hasn't
75086
75072
  * changed" steady state.
75087
75073
  */
75088
75074
  var TemperatureSensorStatusSchema = zod.z.object({
75089
- /** Current temperature in Celsius. */
75090
- celsius: zod.z.number().meta({
75091
- unit: "°C",
75075
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
75076
+ celsius: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature", {
75077
+ precision: 1,
75092
75078
  label: "Temperature"
75093
- }),
75079
+ })),
75094
75080
  /** Ms epoch when the slice was last updated (push or poll). */
75095
- lastFetchedAt: zod.z.number(),
75096
- /** Live display unit from the upstream source (e.g. HA
75097
- * `attributes.unit_of_measurement`). The UI prefers this over the
75098
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
75099
- * Absent → the UI falls back to the role's canonical unit. */
75100
- unit: zod.z.string().optional(),
75101
- /** Suggested decimal places for numeric display.
75102
- * Populated live from the upstream source when provided (e.g. HA
75103
- * `attributes.suggested_display_precision`). Falls back to
75104
- * auto-formatting when absent. */
75105
- precision: zod.z.number().int().min(0).max(10).optional()
75081
+ lastFetchedAt: zod.z.number()
75106
75082
  });
75107
75083
  var temperatureSensorCapability = {
75108
75084
  name: "temperature-sensor",
@@ -75133,12 +75109,7 @@ var temperatureSensorCapability = {
75133
75109
  } } },
75134
75110
  /** Clock fields: written, but excluded from the compare that decides
75135
75111
  * whether persisting is worth a SQLite commit. */
75136
- volatileStateFields: ["lastFetchedAt"],
75137
- customize: {
75138
- version: 1,
75139
- advanced: "derive",
75140
- targets: []
75141
- }
75112
+ volatileStateFields: ["lastFetchedAt"]
75142
75113
  };
75143
75114
  //#endregion
75144
75115
  //#region src/capabilities/toast.cap.ts
@@ -75492,16 +75463,16 @@ var VacuumControlStatusSchema = zod.z.object({
75492
75463
  state: VacuumStateSchema,
75493
75464
  /** 0..100 battery percentage. Null when the device has no battery
75494
75465
  * reading. */
75495
- batteryLevel: zod.z.number().min(0).max(100).nullable(),
75466
+ batteryLevel: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
75496
75467
  /** Current fan-speed token (provider-verbatim). Null when unknown or
75497
75468
  * the vacuum has no speed control. */
75498
75469
  fanSpeed: zod.z.string().nullable(),
75499
75470
  /** Speed tokens the hardware accepts — drives the UI selector. */
75500
75471
  availableFanSpeeds: zod.z.array(zod.z.string()),
75501
75472
  /** 0..100 cleaning-completion percentage of the current task, or null. */
75502
- progressPercent: zod.z.number().min(0).max(100).nullable(),
75473
+ progressPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
75503
75474
  /** Current error code (0 / null = no error). */
75504
- errorCode: zod.z.number().nullable(),
75475
+ errorCode: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
75505
75476
  /** Human label for {@link errorCode}, or null when none / undecodable. */
75506
75477
  errorLabel: zod.z.string().nullable(),
75507
75478
  /** Ms epoch when the slice was last updated. */
@@ -75599,7 +75570,7 @@ var ValveStatusSchema = zod.z.object({
75599
75570
  state: ValveStateSchema,
75600
75571
  /** 0 = fully closed, 100 = fully open. Null when the device has no
75601
75572
  * intermediate position surface. */
75602
- position: zod.z.number().min(0).max(100).nullable(),
75573
+ position: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
75603
75574
  /** Ms epoch when the slice was last updated. */
75604
75575
  lastChangedAt: zod.z.number()
75605
75576
  });
@@ -75711,9 +75682,9 @@ var vibrationCapability = {
75711
75682
  */
75712
75683
  var WaterHeaterStatusSchema = zod.z.object({
75713
75684
  /** Current measured temperature. Null when not reported. */
75714
- currentTemp: zod.z.number().nullable(),
75685
+ currentTemp: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
75715
75686
  /** Target temperature setpoint. Null when no setpoint surface. */
75716
- targetTemp: zod.z.number().nullable(),
75687
+ targetTemp: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
75717
75688
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
75718
75689
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
75719
75690
  * device reports an unknown state. */
@@ -75724,9 +75695,9 @@ var WaterHeaterStatusSchema = zod.z.object({
75724
75695
  * has no away surface. */
75725
75696
  away: zod.z.boolean().nullable(),
75726
75697
  /** HA `min_temp` attribute. Null when not reported. */
75727
- minTemp: zod.z.number().nullable(),
75698
+ minTemp: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
75728
75699
  /** HA `max_temp` attribute. Null when not reported. */
75729
- maxTemp: zod.z.number().nullable(),
75700
+ maxTemp: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
75730
75701
  /** Ms epoch when the slice was last updated. */
75731
75702
  lastChangedAt: zod.z.number()
75732
75703
  });
@@ -75813,25 +75784,18 @@ var waterHeaterCapability = {
75813
75784
  * Forecast deferred — current conditions only for v1.
75814
75785
  */
75815
75786
  var WeatherStatusSchema = zod.z.object({
75816
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
75817
- * when no condition has been reported yet. */
75787
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
75818
75788
  condition: zod.z.string().nullable(),
75819
- /** Current temperature in the reported unit. Null when not provided. */
75820
- temperature: zod.z.number().nullable(),
75821
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
75822
- temperatureUnit: zod.z.string().nullable(),
75789
+ /** Current temperature in Celsius. Null when not provided or refused. */
75790
+ temperature: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature", { precision: 1 })),
75823
75791
  /** Relative humidity (0..100). Null when not provided. */
75824
- humidity: zod.z.number().min(0).max(100).nullable(),
75825
- /** Barometric pressure in the reported unit. Null when not provided. */
75826
- pressure: zod.z.number().nullable(),
75827
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
75828
- pressureUnit: zod.z.string().nullable(),
75829
- /** Wind speed in the reported unit. Null when not provided. */
75830
- windSpeed: zod.z.number().nullable(),
75831
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
75832
- windSpeedUnit: zod.z.string().nullable(),
75792
+ humidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity")),
75793
+ /** Barometric pressure in hPa. Null when not provided or refused. */
75794
+ pressure: zod.z.number().nullable().meta(require_sleep.readingMeta("pressure")),
75795
+ /** Wind speed in metres per second. Null when not provided or refused. */
75796
+ windSpeed: zod.z.number().nullable().meta(require_sleep.readingMeta("speed")),
75833
75797
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
75834
- windBearing: zod.z.number().nullable(),
75798
+ windBearing: zod.z.number().nullable().meta(require_sleep.readingMeta("angle")),
75835
75799
  /** Ms epoch when the slice was last updated. */
75836
75800
  lastFetchedAt: zod.z.number()
75837
75801
  });
@@ -75859,12 +75823,12 @@ var weatherCapability = {
75859
75823
  */
75860
75824
  durability: "session",
75861
75825
  /**
75862
- * History (D850): recorded series. Units follow the sibling `*Unit` fields.
75826
+ * History (D850): recorded series. Numbers are the quantity base (D944).
75863
75827
  */
75864
75828
  history: { fields: {
75865
75829
  temperature: {
75866
75830
  kind: "gauge",
75867
- unitFrom: "temperatureUnit",
75831
+ unit: "°C",
75868
75832
  deadband: .2
75869
75833
  },
75870
75834
  humidity: {
@@ -75874,12 +75838,12 @@ var weatherCapability = {
75874
75838
  },
75875
75839
  pressure: {
75876
75840
  kind: "gauge",
75877
- unitFrom: "pressureUnit",
75841
+ unit: "hPa",
75878
75842
  deadband: .5
75879
75843
  },
75880
75844
  windSpeed: {
75881
75845
  kind: "gauge",
75882
- unitFrom: "windSpeedUnit",
75846
+ unit: "m/s",
75883
75847
  deadband: .5
75884
75848
  }
75885
75849
  } }
@@ -77318,6 +77282,11 @@ var UNIT_TABLE = {
77318
77282
  factor: 1609.344,
77319
77283
  offset: 0
77320
77284
  },
77285
+ "m²": {
77286
+ dimension: "area",
77287
+ factor: 1,
77288
+ offset: 0
77289
+ },
77321
77290
  lx: {
77322
77291
  dimension: "illuminance",
77323
77292
  factor: 1,
@@ -77422,6 +77391,121 @@ var UNIT_TABLE = {
77422
77391
  dimension: "percentage",
77423
77392
  factor: 1,
77424
77393
  offset: 0
77394
+ },
77395
+ L: {
77396
+ dimension: "volume",
77397
+ factor: 1,
77398
+ offset: 0
77399
+ },
77400
+ mL: {
77401
+ dimension: "volume",
77402
+ factor: .001,
77403
+ offset: 0
77404
+ },
77405
+ gal: {
77406
+ dimension: "volume",
77407
+ factor: 3.785411784,
77408
+ offset: 0
77409
+ },
77410
+ s: {
77411
+ dimension: "duration",
77412
+ factor: 1,
77413
+ offset: 0
77414
+ },
77415
+ ms: {
77416
+ dimension: "duration",
77417
+ factor: .001,
77418
+ offset: 0
77419
+ },
77420
+ min: {
77421
+ dimension: "duration",
77422
+ factor: 60,
77423
+ offset: 0
77424
+ },
77425
+ h: {
77426
+ dimension: "duration",
77427
+ factor: 3600,
77428
+ offset: 0
77429
+ },
77430
+ d: {
77431
+ dimension: "duration",
77432
+ factor: 86400,
77433
+ offset: 0
77434
+ },
77435
+ B: {
77436
+ dimension: "information",
77437
+ factor: 1,
77438
+ offset: 0
77439
+ },
77440
+ KiB: {
77441
+ dimension: "information",
77442
+ factor: 1024,
77443
+ offset: 0
77444
+ },
77445
+ MiB: {
77446
+ dimension: "information",
77447
+ factor: 1024 ** 2,
77448
+ offset: 0
77449
+ },
77450
+ GiB: {
77451
+ dimension: "information",
77452
+ factor: 1024 ** 3,
77453
+ offset: 0
77454
+ },
77455
+ TiB: {
77456
+ dimension: "information",
77457
+ factor: 1024 ** 4,
77458
+ offset: 0
77459
+ },
77460
+ kB: {
77461
+ dimension: "information",
77462
+ factor: 1e3,
77463
+ offset: 0
77464
+ },
77465
+ MB: {
77466
+ dimension: "information",
77467
+ factor: 1e6,
77468
+ offset: 0
77469
+ },
77470
+ GB: {
77471
+ dimension: "information",
77472
+ factor: 1e9,
77473
+ offset: 0
77474
+ },
77475
+ "°": {
77476
+ dimension: "angle",
77477
+ factor: 1,
77478
+ offset: 0
77479
+ },
77480
+ dBFS: {
77481
+ dimension: "sound-level",
77482
+ factor: 1,
77483
+ offset: 0
77484
+ },
77485
+ dBm: {
77486
+ dimension: "signal-level",
77487
+ factor: 1,
77488
+ offset: 0
77489
+ },
77490
+ mired: {
77491
+ dimension: "colour-temperature",
77492
+ factor: 1,
77493
+ offset: 0
77494
+ },
77495
+ kbps: {
77496
+ dimension: "bitrate",
77497
+ factor: 1,
77498
+ offset: 0
77499
+ },
77500
+ Mbps: {
77501
+ dimension: "bitrate",
77502
+ factor: 1e3,
77503
+ offset: 0
77504
+ },
77505
+ fps: {
77506
+ dimension: "rate",
77507
+ factor: 1,
77508
+ offset: 0
77425
77509
  }
77426
77510
  };
77427
77511
  //#endregion
@@ -77512,6 +77596,17 @@ function canConvertUnit(from, to) {
77512
77596
  * Every known unit spelling in a dimension (table insertion order — canonical
77513
77597
  * unit first). Powers the UI's same-dimension unit picker.
77514
77598
  */
77599
+ /**
77600
+ * Convert a difference, not a reading. Affine units (°F) shift an absolute
77601
+ * value by their offset; a step of 1°F is 5/9°C, so the offset is cancelled.
77602
+ */
77603
+ function tryConvertDelta(value, from, to) {
77604
+ if (from === to) return value;
77605
+ const origin = tryConvertUnit(0, from, to);
77606
+ const shifted = tryConvertUnit(value, from, to);
77607
+ if (origin === void 0 || shifted === void 0) return void 0;
77608
+ return shifted - origin;
77609
+ }
77515
77610
  function unitsForDimension(dimension) {
77516
77611
  const out = [];
77517
77612
  for (const [unit, spec] of Object.entries(UNIT_TABLE)) if (spec.dimension === dimension) out.push(unit);
@@ -77815,9 +77910,9 @@ function feederCustomizationExample(sources) {
77815
77910
  expr: "number(d)",
77816
77911
  bindings: days
77817
77912
  },
77818
- "remaining.unit": {
77913
+ "remaining.kind": {
77819
77914
  kind: "expression",
77820
- expr: "\"days\"",
77915
+ expr: "\"duration\"",
77821
77916
  bindings: {}
77822
77917
  },
77823
77918
  status: {
@@ -78083,19 +78178,9 @@ function convertUnitTargets(from) {
78083
78178
  //#region src/generated/customize-catalog.ts
78084
78179
  /**
78085
78180
  * Customize catalog, projected from every `*.cap.ts` `customize` block.
78086
- * 11 caps. Field keys are `runtimeState` paths. No Zod.
78181
+ * 5 caps. Field keys are `runtimeState` paths. No Zod.
78087
78182
  */
78088
78183
  var CAP_CUSTOMIZE_CATALOG = {
78089
- "ambient-light-sensor": {
78090
- version: 1,
78091
- targets: [],
78092
- advancedParameters: [{
78093
- path: "lux",
78094
- kind: "number",
78095
- label: "Illuminance",
78096
- unit: "lx"
78097
- }]
78098
- },
78099
78184
  "battery": {
78100
78185
  version: 1,
78101
78186
  targets: [{
@@ -78110,13 +78195,20 @@ var CAP_CUSTOMIZE_CATALOG = {
78110
78195
  require_err_msg.DeviceType.Container,
78111
78196
  require_err_msg.DeviceType.Thermostat
78112
78197
  ],
78113
- apply: "add-or-replace",
78198
+ apply: "add",
78114
78199
  vocabularyCap: "battery",
78115
78200
  vocabularyStateField: "percentage",
78116
78201
  sources: [{
78117
78202
  kind: "field",
78118
78203
  valueKinds: ["number", "string"],
78119
- transformKinds: ["identity", "linear"]
78204
+ transformKinds: ["identity", "linear"],
78205
+ transforms: [{ kind: "identity" }, {
78206
+ kind: "linear",
78207
+ defaultScale: 1,
78208
+ defaultOffset: 0,
78209
+ clampMin: 0,
78210
+ clampMax: 100
78211
+ }]
78120
78212
  }],
78121
78213
  sourceReport: { status: "assignment-status" },
78122
78214
  advancedExpression: true,
@@ -78142,38 +78234,6 @@ var CAP_CUSTOMIZE_CATALOG = {
78142
78234
  when: "add"
78143
78235
  }
78144
78236
  ]
78145
- }],
78146
- advancedParameters: [
78147
- {
78148
- path: "binary",
78149
- kind: "boolean",
78150
- label: "Binary indicator"
78151
- },
78152
- {
78153
- path: "charging",
78154
- kind: "enum",
78155
- label: "Charging source",
78156
- enumValues: [
78157
- "dc",
78158
- "solar",
78159
- "none",
78160
- "unknown"
78161
- ]
78162
- },
78163
- {
78164
- path: "sleeping",
78165
- kind: "boolean",
78166
- label: "Sleeping"
78167
- }
78168
- ]
78169
- },
78170
- "binary": {
78171
- version: 1,
78172
- targets: [],
78173
- advancedParameters: [{
78174
- path: "on",
78175
- kind: "boolean",
78176
- label: "On"
78177
78237
  }]
78178
78238
  },
78179
78239
  "consumables": {
@@ -78231,7 +78291,14 @@ var CAP_CUSTOMIZE_CATALOG = {
78231
78291
  nullable: true,
78232
78292
  sources: [{
78233
78293
  kind: "numeric",
78234
- transformKinds: ["identity", "linear"]
78294
+ transformKinds: ["identity", "linear"],
78295
+ transforms: [{ kind: "identity" }, {
78296
+ kind: "linear",
78297
+ defaultScale: 1,
78298
+ defaultOffset: 0,
78299
+ clampMin: 0,
78300
+ clampMax: 100
78301
+ }]
78235
78302
  }]
78236
78303
  },
78237
78304
  {
@@ -78240,15 +78307,12 @@ var CAP_CUSTOMIZE_CATALOG = {
78240
78307
  nullable: true,
78241
78308
  sources: [{
78242
78309
  kind: "numeric",
78243
- transformKinds: ["identity"]
78310
+ transformKinds: ["identity"],
78311
+ transforms: [{ kind: "identity" }]
78244
78312
  }],
78245
78313
  literals: [{
78246
- path: "remaining.unit",
78247
- values: [
78248
- "days",
78249
- "hours",
78250
- "cycles"
78251
- ]
78314
+ path: "remaining.kind",
78315
+ values: ["duration", "count"]
78252
78316
  }]
78253
78317
  },
78254
78318
  {
@@ -78264,7 +78328,18 @@ var CAP_CUSTOMIZE_CATALOG = {
78264
78328
  ],
78265
78329
  sources: [{
78266
78330
  kind: "enum",
78267
- transformKinds: ["enum-map"]
78331
+ transformKinds: ["enum-map"],
78332
+ transforms: [{
78333
+ kind: "enum-map",
78334
+ outputValues: [
78335
+ "ok",
78336
+ "low",
78337
+ "empty",
78338
+ "full",
78339
+ "replace"
78340
+ ],
78341
+ unmapped: null
78342
+ }]
78268
78343
  }]
78269
78344
  }
78270
78345
  ],
@@ -78295,8 +78370,7 @@ var CAP_CUSTOMIZE_CATALOG = {
78295
78370
  path: "lastFetchedAt",
78296
78371
  when: "add"
78297
78372
  }]
78298
- }],
78299
- advancedParameters: []
78373
+ }]
78300
78374
  },
78301
78375
  "doorbell": {
78302
78376
  version: 1,
@@ -78309,10 +78383,12 @@ var CAP_CUSTOMIZE_CATALOG = {
78309
78383
  vocabularyEvent: "onPressed",
78310
78384
  sources: [{
78311
78385
  kind: "binary",
78312
- transformKinds: []
78386
+ transformKinds: [],
78387
+ transforms: []
78313
78388
  }, {
78314
78389
  kind: "switch",
78315
- transformKinds: []
78390
+ transformKinds: [],
78391
+ transforms: []
78316
78392
  }],
78317
78393
  sourceReport: { status: "assignment-status" },
78318
78394
  advancedExpression: true,
@@ -78321,21 +78397,11 @@ var CAP_CUSTOMIZE_CATALOG = {
78321
78397
  eventName: "onPressed",
78322
78398
  combine: "any",
78323
78399
  edges: ["rising"],
78400
+ defaultEdge: "rising",
78324
78401
  timestampPath: "lastPressedAt",
78325
78402
  counterPath: "pressCountSinceStart",
78326
78403
  highWater: "composer-advance",
78327
78404
  debounceMs: 2e3
78328
- }],
78329
- advancedParameters: []
78330
- },
78331
- "humidity-sensor": {
78332
- version: 1,
78333
- targets: [],
78334
- advancedParameters: [{
78335
- path: "percent",
78336
- kind: "number",
78337
- label: "Humidity",
78338
- unit: "%"
78339
78405
  }]
78340
78406
  },
78341
78407
  "motion": {
@@ -78344,15 +78410,17 @@ var CAP_CUSTOMIZE_CATALOG = {
78344
78410
  id: "detected",
78345
78411
  cardinality: "event",
78346
78412
  deviceTypes: [require_err_msg.DeviceType.Camera],
78347
- apply: "replace",
78413
+ apply: "add",
78348
78414
  vocabularyCap: "motion",
78349
78415
  vocabularyEvent: "onMotionChanged",
78350
78416
  sources: [{
78351
78417
  kind: "binary",
78352
- transformKinds: []
78418
+ transformKinds: [],
78419
+ transforms: []
78353
78420
  }, {
78354
78421
  kind: "switch",
78355
- transformKinds: []
78422
+ transformKinds: [],
78423
+ transforms: []
78356
78424
  }],
78357
78425
  sourceReport: { status: "assignment-status" },
78358
78426
  advancedExpression: false,
@@ -78371,22 +78439,6 @@ var CAP_CUSTOMIZE_CATALOG = {
78371
78439
  "confirm",
78372
78440
  "replace"
78373
78441
  ]
78374
- }],
78375
- advancedParameters: []
78376
- },
78377
- "power-meter": {
78378
- version: 1,
78379
- targets: [],
78380
- advancedParameters: [{
78381
- path: "kwhTotal",
78382
- kind: "number",
78383
- label: "Energy",
78384
- unit: "kWh"
78385
- }, {
78386
- path: "watts",
78387
- kind: "number",
78388
- label: "Power",
78389
- unit: "W"
78390
78442
  }]
78391
78443
  },
78392
78444
  "running-state": {
@@ -78406,22 +78458,33 @@ var CAP_CUSTOMIZE_CATALOG = {
78406
78458
  require_err_msg.DeviceType.Thermostat,
78407
78459
  require_err_msg.DeviceType.PetFeeder
78408
78460
  ],
78409
- apply: "add-or-replace",
78461
+ apply: "add",
78410
78462
  vocabularyCap: "running-state",
78411
78463
  vocabularyStateField: "running",
78412
78464
  sources: [
78413
78465
  {
78414
78466
  kind: "switch",
78415
- transformKinds: ["identity"]
78467
+ transformKinds: ["identity"],
78468
+ transforms: [{ kind: "identity" }]
78416
78469
  },
78417
78470
  {
78418
78471
  kind: "binary",
78419
- transformKinds: ["identity", "invert"]
78472
+ transformKinds: ["identity", "invert"],
78473
+ transforms: [{ kind: "identity" }, { kind: "invert" }]
78420
78474
  },
78421
78475
  {
78422
78476
  kind: "numeric",
78423
78477
  requiredUnit: "W",
78424
- transformKinds: ["threshold"]
78478
+ transformKinds: ["threshold"],
78479
+ transforms: [{
78480
+ kind: "threshold",
78481
+ comparisons: ["gte", "lte"],
78482
+ comparison: "gte",
78483
+ defaultThreshold: 5,
78484
+ defaultStartDwellMs: 3e4,
78485
+ defaultStopDwellMs: 18e4,
78486
+ thresholdUnit: "W"
78487
+ }]
78425
78488
  }
78426
78489
  ],
78427
78490
  sourceReport: {
@@ -78458,29 +78521,26 @@ var CAP_CUSTOMIZE_CATALOG = {
78458
78521
  sourceChangedAt: "prefer"
78459
78522
  }
78460
78523
  ]
78461
- }],
78462
- advancedParameters: []
78463
- },
78464
- "switch": {
78465
- version: 1,
78466
- targets: [],
78467
- advancedParameters: [{
78468
- path: "on",
78469
- kind: "boolean",
78470
- label: "On"
78471
- }]
78472
- },
78473
- "temperature-sensor": {
78474
- version: 1,
78475
- targets: [],
78476
- advancedParameters: [{
78477
- path: "celsius",
78478
- kind: "number",
78479
- label: "Temperature",
78480
- unit: "°C"
78481
78524
  }]
78482
78525
  }
78483
78526
  };
78527
+ /**
78528
+ * Unit meta on runtime-state fields. The composed-device row reads this;
78529
+ * it is not an override list (D944).
78530
+ */
78531
+ var CAP_RUNTIME_FIELD_UNITS = {
78532
+ "ambient-light-sensor": { lux: "lx" },
78533
+ "battery": {
78534
+ percentage: "%",
78535
+ volts: "V"
78536
+ },
78537
+ "humidity-sensor": { percent: "%" },
78538
+ "power-meter": {
78539
+ watts: "W",
78540
+ kwhTotal: "kWh"
78541
+ },
78542
+ "temperature-sensor": { celsius: "°C" }
78543
+ };
78484
78544
  //#endregion
78485
78545
  //#region src/composition/customize/field-source.ts
78486
78546
  function fromRead(source, cap, fieldPath) {
@@ -78609,13 +78669,16 @@ function compilePulseOverride(input) {
78609
78669
  const timestampPath = target.timestampPath;
78610
78670
  const counterPath = target.counterPath;
78611
78671
  if (timestampPath === void 0 || counterPath === void 0 || !isIdentifier(timestampPath) || !isIdentifier(counterPath)) return fail$2("path");
78612
- if (!(target.edges ?? []).includes("rising")) return fail$2("edge");
78672
+ const edges = chosenEdges(input.draft.edges, target);
78673
+ if (edges === null) return fail$2("edge");
78613
78674
  const pairs = pairSources(input);
78614
78675
  if (!pairs.ok) return pairs;
78615
78676
  if (pairs.value.length === 0) return fail$2("sources");
78616
78677
  const debounce = unsignedToken(input.draft.debounceMs);
78617
78678
  if (debounce === null) return fail$2("debounce");
78618
78679
  const ordered = pairs.value.toSorted((a, b) => pairKey(a).localeCompare(pairKey(b)));
78680
+ const calls = edgeCalls(edges, ordered.length, debounce);
78681
+ if (calls === null) return fail$2("edge");
78619
78682
  return {
78620
78683
  ok: true,
78621
78684
  value: {
@@ -78623,7 +78686,7 @@ function compilePulseOverride(input) {
78623
78686
  cap: input.draft.cap,
78624
78687
  enabled: input.draft.enabled,
78625
78688
  fields: {
78626
- [timestampPath]: expression(pulseExpr(ordered.length, debounce), pulseBindings(ordered)),
78689
+ [timestampPath]: expression(pulseExpr(calls), pulseBindings(ordered)),
78627
78690
  [counterPath]: expression(`count(${timestampPath})`, { [timestampPath]: fromRead(input.device, input.draft.cap, timestampPath) })
78628
78691
  }
78629
78692
  }
@@ -78677,10 +78740,28 @@ function pairSources(input) {
78677
78740
  function pairKey(pair) {
78678
78741
  return `${pair.source.addonId}/${pair.source.stableId}#${pair.source.cap}#${pair.source.fieldPath}`;
78679
78742
  }
78680
- function pulseExpr(count, debounce) {
78681
- const roses = [];
78682
- for (let index = 0; index < count; index += 1) roses.push(`rose(active${index}, changed${index}, ${debounce})`);
78683
- return `latest(${roses.join(", ")})`;
78743
+ function chosenEdges(draftEdges, target) {
78744
+ const allowed = target.edges ?? [];
78745
+ const chosen = draftEdges ?? (target.defaultEdge !== void 0 ? [target.defaultEdge] : []);
78746
+ if (chosen.length === 0) return null;
78747
+ if (chosen.some((edge) => !allowed.includes(edge))) return null;
78748
+ return chosen;
78749
+ }
78750
+ function edgeCalls(edges, count, debounce) {
78751
+ const calls = [];
78752
+ for (let index = 0; index < count; index += 1) for (const edge of edges) {
78753
+ const call = edgeCall(edge, index, debounce);
78754
+ if (call === null) return null;
78755
+ calls.push(call);
78756
+ }
78757
+ return calls;
78758
+ }
78759
+ function edgeCall(edge, index, debounce) {
78760
+ if (edge === "rising") return `rose(active${index}, changed${index}, ${debounce})`;
78761
+ return null;
78762
+ }
78763
+ function pulseExpr(calls) {
78764
+ return `latest(${calls.join(", ")})`;
78684
78765
  }
78685
78766
  function pulseBindings(pairs) {
78686
78767
  const bindings = {};
@@ -96080,7 +96161,6 @@ var SENSOR_CAP_VOCABULARY = {
96080
96161
  field: "value",
96081
96162
  quantity: t("the reading", "il valore"),
96082
96163
  label: t("Reading", "Valore"),
96083
- unitField: "unit",
96084
96164
  icon: "gauge"
96085
96165
  }]
96086
96166
  },
@@ -96216,7 +96296,7 @@ var SENSOR_CAP_VOCABULARY = {
96216
96296
  field: "temperature",
96217
96297
  quantity: t("the outdoor temperature", "la temperatura esterna"),
96218
96298
  label: t("Temperature", "Temperatura"),
96219
- unitField: "temperatureUnit",
96299
+ unit: "°C",
96220
96300
  icon: "thermometer"
96221
96301
  },
96222
96302
  {
@@ -96232,7 +96312,7 @@ var SENSOR_CAP_VOCABULARY = {
96232
96312
  field: "windSpeed",
96233
96313
  quantity: t("the wind speed", "la velocità del vento"),
96234
96314
  label: t("Wind", "Vento"),
96235
- unitField: "windSpeedUnit",
96315
+ unit: "m/s",
96236
96316
  icon: "wind"
96237
96317
  },
96238
96318
  {
@@ -96240,7 +96320,7 @@ var SENSOR_CAP_VOCABULARY = {
96240
96320
  field: "pressure",
96241
96321
  quantity: t("the air pressure", "la pressione atmosferica"),
96242
96322
  label: t("Pressure", "Pressione"),
96243
- unitField: "pressureUnit",
96323
+ unit: "hPa",
96244
96324
  icon: "gauge"
96245
96325
  }
96246
96326
  ]
@@ -102773,7 +102853,7 @@ var BucketAccumulator = class {
102773
102853
  //#region src/generated/history-catalog.ts
102774
102854
  /**
102775
102855
  * Cap history catalog, projected from every `*.cap.ts` `history` block.
102776
- * 41 caps with series (74 series), 30 caps answered `'none'`.
102856
+ * 41 caps with series (75 series), 30 caps answered `'none'`.
102777
102857
  * Field keys are `runtimeState` paths (`a.b` into fixed objects only).
102778
102858
  */
102779
102859
  var HISTORY_CATALOG = {
@@ -102790,12 +102870,12 @@ var HISTORY_CATALOG = {
102790
102870
  },
102791
102871
  "pm25": {
102792
102872
  kind: "gauge",
102793
- unit: "ug/m3",
102873
+ unit: "µg/m³",
102794
102874
  deadband: 1
102795
102875
  },
102796
102876
  "pm10": {
102797
102877
  kind: "gauge",
102798
- unit: "ug/m3",
102878
+ unit: "µg/m³",
102799
102879
  deadband: 1
102800
102880
  },
102801
102881
  "aqi": {
@@ -102825,10 +102905,16 @@ var HISTORY_CATALOG = {
102825
102905
  deadbandPct: 10,
102826
102906
  minIntervalMs: 6e4
102827
102907
  } },
102828
- "battery": { "percentage": {
102829
- kind: "gauge",
102830
- unit: "%"
102831
- } },
102908
+ "battery": {
102909
+ "percentage": {
102910
+ kind: "gauge",
102911
+ unit: "%"
102912
+ },
102913
+ "volts": {
102914
+ kind: "gauge",
102915
+ unit: "V"
102916
+ }
102917
+ },
102832
102918
  "binary": { "on": { kind: "boolean" } },
102833
102919
  "brightness": { "percentage": {
102834
102920
  kind: "gauge",
@@ -102912,7 +102998,7 @@ var HISTORY_CATALOG = {
102912
102998
  },
102913
102999
  "enum-sensor": { "value": {
102914
103000
  kind: "enum",
102915
- skipWhenSet: "format"
103001
+ skipWhenSet: "timestamp"
102916
103002
  } },
102917
103003
  "fan-control": {
102918
103004
  "percentage": {
@@ -102985,7 +103071,6 @@ var HISTORY_CATALOG = {
102985
103071
  },
102986
103072
  "numeric-sensor": { "value": {
102987
103073
  kind: "gauge",
102988
- unitFrom: "unit",
102989
103074
  minIntervalMs: 3e4
102990
103075
  } },
102991
103076
  "pet-feeder": {
@@ -102999,7 +103084,7 @@ var HISTORY_CATALOG = {
102999
103084
  },
103000
103085
  "desiccantLeftDays": {
103001
103086
  kind: "gauge",
103002
- unit: "days"
103087
+ unit: "s"
103003
103088
  }
103004
103089
  },
103005
103090
  "power-meter": {
@@ -103103,7 +103188,7 @@ var HISTORY_CATALOG = {
103103
103188
  "weather": {
103104
103189
  "temperature": {
103105
103190
  kind: "gauge",
103106
- unitFrom: "temperatureUnit",
103191
+ unit: "°C",
103107
103192
  deadband: .2
103108
103193
  },
103109
103194
  "humidity": {
@@ -103113,12 +103198,12 @@ var HISTORY_CATALOG = {
103113
103198
  },
103114
103199
  "pressure": {
103115
103200
  kind: "gauge",
103116
- unitFrom: "pressureUnit",
103201
+ unit: "hPa",
103117
103202
  deadband: .5
103118
103203
  },
103119
103204
  "windSpeed": {
103120
103205
  kind: "gauge",
103121
- unitFrom: "windSpeedUnit",
103206
+ unit: "m/s",
103122
103207
  deadband: .5
103123
103208
  }
103124
103209
  }
@@ -103152,7 +103237,6 @@ var HISTORY_ITEM_CATALOG = { "consumables": {
103152
103237
  },
103153
103238
  "remaining.value": {
103154
103239
  kind: "gauge",
103155
- unitFrom: "remaining.unit",
103156
103240
  deadband: 1,
103157
103241
  displayName: "Remaining"
103158
103242
  }
@@ -103196,6 +103280,14 @@ function historyFieldsFor(capName) {
103196
103280
  return HISTORY_CATALOG[capName] ?? {};
103197
103281
  }
103198
103282
  //#endregion
103283
+ //#region src/capabilities/runtime-field-unit.ts
103284
+ /**
103285
+ * The unit a composed-device field row offers convert-unit from (D941, D944).
103286
+ */
103287
+ function runtimeFieldUnit(cap, path) {
103288
+ return CAP_RUNTIME_FIELD_UNITS[cap]?.[path];
103289
+ }
103290
+ //#endregion
103199
103291
  //#region src/composition/customize/override-draft.ts
103200
103292
  /**
103201
103293
  * A saved Customize assignment (D936). The schema is the type: callers use
@@ -103259,6 +103351,11 @@ var PulseOverrideDraftSchema = zod.z.object({
103259
103351
  targetId: zod.z.string().min(1),
103260
103352
  enabled: zod.z.boolean(),
103261
103353
  debounceMs: zod.z.number().nonnegative(),
103354
+ edges: zod.z.array(zod.z.enum([
103355
+ "rising",
103356
+ "button",
103357
+ "native-event"
103358
+ ])).min(1).optional(),
103262
103359
  sources: zod.z.array(OverrideSourceSchema).min(1)
103263
103360
  });
103264
103361
  var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: zod.z.enum([
@@ -112747,6 +112844,227 @@ var DEVICE_TYPE_INFO = { ["camera"]: {
112747
112844
  icon: "camera"
112748
112845
  } };
112749
112846
  //#endregion
112847
+ //#region src/units/threshold.ts
112848
+ /**
112849
+ * Move a threshold between the unit the operator sees and the stored base.
112850
+ * A failed conversion leaves the number unchanged — the recipe stays serialisable.
112851
+ */
112852
+ function thresholdToBase(value, displayUnit, baseUnit) {
112853
+ if (!Number.isFinite(value) || displayUnit === baseUnit) return value;
112854
+ return tryConvertUnit(value, displayUnit, baseUnit) ?? value;
112855
+ }
112856
+ function thresholdToDisplay(value, baseUnit, displayUnit) {
112857
+ if (!Number.isFinite(value) || baseUnit === displayUnit) return value;
112858
+ return tryConvertUnit(value, baseUnit, displayUnit) ?? value;
112859
+ }
112860
+ //#endregion
112861
+ //#region src/units/format-reading.ts
112862
+ /**
112863
+ * Render a stored base-unit reading (D944 §4). The preference defaults to
112864
+ * metric. An operator override is applied by the caller via `convertUnit`
112865
+ * before this, or by passing the already-chosen display unit through
112866
+ * `resolveSensorDisplay` — this function is the quantity → string step.
112867
+ */
112868
+ var DURATION_STEPS = [
112869
+ {
112870
+ value: 86400,
112871
+ unit: "d"
112872
+ },
112873
+ {
112874
+ value: 3600,
112875
+ unit: "h"
112876
+ },
112877
+ {
112878
+ value: 60,
112879
+ unit: "min"
112880
+ }
112881
+ ];
112882
+ var INFO_STEPS = [
112883
+ {
112884
+ factor: 1024 ** 4,
112885
+ unit: "TiB"
112886
+ },
112887
+ {
112888
+ factor: 1024 ** 3,
112889
+ unit: "GiB"
112890
+ },
112891
+ {
112892
+ factor: 1024 ** 2,
112893
+ unit: "MiB"
112894
+ },
112895
+ {
112896
+ factor: 1024,
112897
+ unit: "KiB"
112898
+ }
112899
+ ];
112900
+ function formatMagnitude(value, precision) {
112901
+ if (!Number.isFinite(value)) return "";
112902
+ if (precision !== void 0) return value.toFixed(precision);
112903
+ return Number.isInteger(value) ? String(value) : value.toFixed(1);
112904
+ }
112905
+ function scaleDuration(seconds) {
112906
+ const abs = Math.abs(seconds);
112907
+ for (const step of DURATION_STEPS) if (abs >= step.value) return {
112908
+ value: seconds / step.value,
112909
+ unit: step.unit
112910
+ };
112911
+ return {
112912
+ value: seconds,
112913
+ unit: "s"
112914
+ };
112915
+ }
112916
+ function scaleInformation(bytes) {
112917
+ const abs = Math.abs(bytes);
112918
+ for (const step of INFO_STEPS) if (abs >= step.factor) return {
112919
+ value: bytes / step.factor,
112920
+ unit: step.unit
112921
+ };
112922
+ return {
112923
+ value: bytes,
112924
+ unit: "B"
112925
+ };
112926
+ }
112927
+ function resolvedQuantity(input) {
112928
+ if (input.quantity === "device") return input.deviceQuantity ?? "none";
112929
+ return input.quantity;
112930
+ }
112931
+ function formatReading(input) {
112932
+ const quantity = resolvedQuantity(input);
112933
+ const precision = input.precision;
112934
+ if (!Number.isFinite(input.value)) return {
112935
+ text: "",
112936
+ unit: "",
112937
+ value: input.value
112938
+ };
112939
+ if (quantity === "duration" || quantity === "duration-ms") {
112940
+ const scaled = scaleDuration(quantity === "duration-ms" ? input.value / 1e3 : input.value);
112941
+ return {
112942
+ text: formatMagnitude(scaled.value, precision),
112943
+ unit: scaled.unit,
112944
+ value: scaled.value
112945
+ };
112946
+ }
112947
+ if (quantity === "information") {
112948
+ const scaled = scaleInformation(input.value);
112949
+ return {
112950
+ text: formatMagnitude(scaled.value, precision),
112951
+ unit: scaled.unit,
112952
+ value: scaled.value
112953
+ };
112954
+ }
112955
+ const preference = input.preference ?? "metric";
112956
+ const base = require_sleep.baseUnitOf(quantity);
112957
+ const display = require_sleep.displayUnitFor(quantity, preference);
112958
+ if (base === void 0 || display === void 0) return {
112959
+ text: formatMagnitude(input.value, precision),
112960
+ unit: "",
112961
+ value: input.value
112962
+ };
112963
+ const converted = tryConvertUnit(input.value, base, display);
112964
+ if (converted === void 0) return {
112965
+ text: formatMagnitude(input.value, precision),
112966
+ unit: base,
112967
+ value: input.value
112968
+ };
112969
+ return {
112970
+ text: formatMagnitude(converted, precision),
112971
+ unit: display,
112972
+ value: converted
112973
+ };
112974
+ }
112975
+ //#endregion
112976
+ //#region src/units/ingest-reading.ts
112977
+ /**
112978
+ * Convert an upstream number into the stored base of its quantity (D944 §4).
112979
+ * An unknown unit or a unit from another dimension is a refusal: the caller
112980
+ * logs it and publishes null (D393). This function never guesses.
112981
+ */
112982
+ var DIMENSION_QUANTITY = {
112983
+ temperature: "temperature",
112984
+ pressure: "pressure",
112985
+ speed: "speed",
112986
+ "precipitation-rate": "precipitation-rate",
112987
+ length: "length",
112988
+ area: "area",
112989
+ illuminance: "illuminance",
112990
+ irradiance: "irradiance",
112991
+ power: "power",
112992
+ "apparent-power": "apparent-power",
112993
+ energy: "energy",
112994
+ voltage: "voltage",
112995
+ current: "current",
112996
+ "concentration-volume": "concentration-volume",
112997
+ "concentration-mass": "concentration-mass",
112998
+ mass: "mass",
112999
+ percentage: "percentage",
113000
+ volume: "volume",
113001
+ duration: "duration",
113002
+ information: "information",
113003
+ angle: "angle",
113004
+ "sound-level": "sound-level",
113005
+ "signal-level": "signal-level",
113006
+ "colour-temperature": "colour-temperature",
113007
+ bitrate: "bitrate",
113008
+ rate: "rate"
113009
+ };
113010
+ /**
113011
+ * Quantity implied by a unit symbol. `ppb` and `ms` are the two symbols
113012
+ * whose stored base is not the dimension's canonical unit. `mm` is length;
113013
+ * precipitation is chosen by the caller (a device class), not by the symbol.
113014
+ */
113015
+ function quantityFromUnit(rawUnit) {
113016
+ const unit = normalizeUnit(rawUnit);
113017
+ if (unit === void 0) return void 0;
113018
+ if (unit === "ppb") return "concentration-ppb";
113019
+ if (unit === "ms") return "duration-ms";
113020
+ const dimension = unitDimension(unit);
113021
+ if (dimension === void 0) return void 0;
113022
+ return DIMENSION_QUANTITY[dimension];
113023
+ }
113024
+ function ingestReading(input) {
113025
+ if (!Number.isFinite(input.value)) return {
113026
+ ok: false,
113027
+ reason: "non-finite"
113028
+ };
113029
+ if (input.quantity === "device") return {
113030
+ ok: false,
113031
+ reason: "unknown-unit"
113032
+ };
113033
+ const raw = input.rawUnit?.trim();
113034
+ if (raw === void 0 || raw.length === 0) return {
113035
+ ok: true,
113036
+ value: input.value
113037
+ };
113038
+ const unit = normalizeUnit(raw);
113039
+ if (unit === void 0) return {
113040
+ ok: false,
113041
+ reason: "unknown-unit"
113042
+ };
113043
+ const base = require_sleep.baseUnitOf(input.quantity);
113044
+ if (base === void 0) {
113045
+ if (unitDimension(unit) !== void 0) return {
113046
+ ok: false,
113047
+ reason: "cross-dimension"
113048
+ };
113049
+ return {
113050
+ ok: false,
113051
+ reason: "unknown-unit"
113052
+ };
113053
+ }
113054
+ const converted = tryConvertUnit(input.value, unit, base);
113055
+ if (converted === void 0) return unitDimension(unit) === void 0 ? {
113056
+ ok: false,
113057
+ reason: "unknown-unit"
113058
+ } : {
113059
+ ok: false,
113060
+ reason: "cross-dimension"
113061
+ };
113062
+ return {
113063
+ ok: true,
113064
+ value: converted
113065
+ };
113066
+ }
113067
+ //#endregion
112750
113068
  //#region src/util/location-match.ts
112751
113069
  /**
112752
113070
  * Pure fuzzy matcher for adoption location import. Normalized
@@ -114373,6 +114691,7 @@ exports.CAP_CUSTOMIZE_CATALOG = CAP_CUSTOMIZE_CATALOG;
114373
114691
  exports.CAP_NAMES_WITH_STATUS = CAP_NAMES_WITH_STATUS;
114374
114692
  exports.CAP_NODE_PIN_CONTEXT_KEY = require_sleep.CAP_NODE_PIN_CONTEXT_KEY;
114375
114693
  exports.CAP_PROVIDER_KIND_MAP = CAP_PROVIDER_KIND_MAP;
114694
+ exports.CAP_RUNTIME_FIELD_UNITS = CAP_RUNTIME_FIELD_UNITS;
114376
114695
  exports.CAP_VOCABULARY = CAP_VOCABULARY;
114377
114696
  exports.CAP_VOCABULARY_BY_NAME = CAP_VOCABULARY_BY_NAME;
114378
114697
  exports.CATALOG_LANGS = CATALOG_LANGS;
@@ -114571,8 +114890,8 @@ exports.ConsumableFormSchema = ConsumableFormSchema;
114571
114890
  exports.ConsumableItemSchema = ConsumableItemSchema;
114572
114891
  exports.ConsumableKindSchema = ConsumableKindSchema;
114573
114892
  exports.ConsumableLevelSchema = ConsumableLevelSchema;
114893
+ exports.ConsumableRemainingKindSchema = ConsumableRemainingKindSchema;
114574
114894
  exports.ConsumableRemainingSchema = ConsumableRemainingSchema;
114575
- exports.ConsumableRemainingUnitSchema = ConsumableRemainingUnitSchema;
114576
114895
  exports.ConsumableStatusSchema = ConsumableStatusSchema;
114577
114896
  exports.ConsumablesStatusSchema = ConsumablesStatusSchema;
114578
114897
  exports.ContactStatusSchema = ContactStatusSchema;
@@ -114618,6 +114937,7 @@ exports.DEFAULT_CLUSTER_STEP_SETTINGS = DEFAULT_CLUSTER_STEP_SETTINGS;
114618
114937
  exports.DEFAULT_CORE_BLOCK_ENTRY = DEFAULT_CORE_BLOCK_ENTRY;
114619
114938
  exports.DEFAULT_DECODER_HWACCEL_CONFIG = DEFAULT_DECODER_HWACCEL_CONFIG;
114620
114939
  exports.DEFAULT_DETAIL_CROP_CONVENTION = DEFAULT_DETAIL_CROP_CONVENTION;
114940
+ exports.DEFAULT_DISPLAY_PREFERENCE = require_sleep.DEFAULT_DISPLAY_PREFERENCE;
114621
114941
  exports.DEFAULT_EVENTS_BAND_BUFFER_SEC = DEFAULT_EVENTS_BAND_BUFFER_SEC;
114622
114942
  exports.DEFAULT_EVENT_COLOR = DEFAULT_EVENT_COLOR;
114623
114943
  exports.DEFAULT_FEATURES = DEFAULT_FEATURES;
@@ -114676,6 +114996,7 @@ exports.DEVICE_TOMBSTONE_RETENTION_MS = DEVICE_TOMBSTONE_RETENTION_MS;
114676
114996
  exports.DEVICE_TYPE_CONTROL_KIND = DEVICE_TYPE_CONTROL_KIND;
114677
114997
  exports.DEVICE_TYPE_DO_CAPS = DEVICE_TYPE_DO_CAPS;
114678
114998
  exports.DEVICE_TYPE_INFO = DEVICE_TYPE_INFO;
114999
+ exports.DISPLAY_PREFERENCES = require_sleep.DISPLAY_PREFERENCES;
114679
115000
  exports.DISPLAY_PRESETS = DISPLAY_PRESETS;
114680
115001
  exports.DOORBELL_RISE_DEBOUNCE_MS = DOORBELL_RISE_DEBOUNCE_MS;
114681
115002
  exports.DataStoreDatabaseSchema = DataStoreDatabaseSchema;
@@ -115606,6 +115927,7 @@ exports.PtzPositionSchema = PtzPositionSchema;
115606
115927
  exports.PtzPresetFunctionSchema = PtzPresetFunctionSchema;
115607
115928
  exports.PtzPresetSchema = PtzPresetSchema;
115608
115929
  exports.PtzStatusSchema = PtzStatusSchema;
115930
+ exports.QUANTITY_BASE = require_sleep.QUANTITY_BASE;
115609
115931
  exports.QueryFilterSchema = QueryFilterSchema;
115610
115932
  exports.RATE_CONTROL_RELAXED = RATE_CONTROL_RELAXED;
115611
115933
  exports.RATE_CONTROL_TIGHT = RATE_CONTROL_TIGHT;
@@ -115684,6 +116006,7 @@ exports.ReadSegmentBytesResultSchema = ReadSegmentBytesResultSchema;
115684
116006
  exports.ReadWindowBytesResultSchema = ReadWindowBytesResultSchema;
115685
116007
  exports.ReadinessRegistry = require_sleep.ReadinessRegistry;
115686
116008
  exports.ReadinessTimeoutError = require_sleep.ReadinessTimeoutError;
116009
+ exports.ReadingQuantitySchema = require_sleep.ReadingQuantitySchema;
115687
116010
  exports.RebuildObjectEmbeddingsRefusalSchema = RebuildObjectEmbeddingsRefusalSchema;
115688
116011
  exports.RecentTracksPageSchema = RecentTracksPageSchema;
115689
116012
  exports.RecentTracksQueryInput = RecentTracksQueryInput;
@@ -116250,6 +116573,7 @@ exports.autoAssignProfiles = autoAssignProfiles;
116250
116573
  exports.automationControlCapability = automationControlCapability;
116251
116574
  exports.backupCapability = backupCapability;
116252
116575
  exports.bareAddonId = bareAddonId;
116576
+ exports.baseUnitOf = require_sleep.baseUnitOf;
116253
116577
  exports.batteryCapability = batteryCapability;
116254
116578
  exports.bestLocationMatch = bestLocationMatch;
116255
116579
  exports.binaryCapability = binaryCapability;
@@ -116469,6 +116793,7 @@ exports.diffStepSettings = diffStepSettings;
116469
116793
  exports.disarmRoseMemory = require_composition.disarmRoseMemory;
116470
116794
  exports.dishwasherCapability = dishwasherCapability;
116471
116795
  exports.displayPaletteCapability = displayPaletteCapability;
116796
+ exports.displayUnitFor = require_sleep.displayUnitFor;
116472
116797
  exports.doActionSelectValue = doActionSelectValue;
116473
116798
  exports.dominantClipLabels = dominantClipLabels;
116474
116799
  exports.doorbellCapability = doorbellCapability;
@@ -116555,6 +116880,7 @@ exports.formatMediaOwnerKey = formatMediaOwnerKey;
116555
116880
  exports.formatMinutes = formatMinutes;
116556
116881
  exports.formatPaletteViolations = formatPaletteViolations;
116557
116882
  exports.formatParkedFor = formatParkedFor;
116883
+ exports.formatReading = formatReading;
116558
116884
  exports.formatSecretScope = require_sleep.formatSecretScope;
116559
116885
  exports.formulaProblem = formulaProblem;
116560
116886
  exports.freshResults = freshResults;
@@ -116579,6 +116905,7 @@ exports.heatingValveCapability = heatingValveCapability;
116579
116905
  exports.hfModelUrl = require_sleep.hfModelUrl;
116580
116906
  exports.historyFieldsFor = historyFieldsFor;
116581
116907
  exports.historyItemFieldPath = require_sleep.historyItemFieldPath;
116908
+ exports.historyUnitFor = require_sleep.historyUnitFor;
116582
116909
  exports.htmlToText = htmlToText;
116583
116910
  exports.hubAddressHostname = hubAddressHostname;
116584
116911
  exports.hubMotionNativeProvider = hubMotionNativeProvider;
@@ -116593,6 +116920,7 @@ exports.imageModesForKind = imageModesForKind;
116593
116920
  exports.imageSettingsCapability = imageSettingsCapability;
116594
116921
  exports.inferExpressionKinds = inferExpressionKinds;
116595
116922
  exports.inferModelProvider = inferModelProvider;
116923
+ exports.ingestReading = ingestReading;
116596
116924
  exports.initialPoolMemoryState = initialPoolMemoryState;
116597
116925
  exports.integrationsCapability = integrationsCapability;
116598
116926
  exports.intercomCapability = intercomCapability;
@@ -116647,6 +116975,7 @@ exports.isOauthLandingReason = isOauthLandingReason;
116647
116975
  exports.isObjectInput = isObjectInput;
116648
116976
  exports.isOccupancyRule = isOccupancyRule;
116649
116977
  exports.isOwnerOnlyEventCategory = require_event_category.isOwnerOnlyEventCategory;
116978
+ exports.isReadingQuantity = require_sleep.isReadingQuantity;
116650
116979
  exports.isRemovedModelId = isRemovedModelId;
116651
116980
  exports.isRestoredCap = isRestoredCap;
116652
116981
  exports.isRetiredClearKey = isRetiredClearKey;
@@ -116877,6 +117206,7 @@ exports.ptzAutotrackCapability = ptzAutotrackCapability;
116877
117206
  exports.ptzCapability = ptzCapability;
116878
117207
  exports.publicEventCategories = require_event_category.publicEventCategories;
116879
117208
  exports.pythonScriptForBackend = pythonScriptForBackend;
117209
+ exports.quantityFromUnit = quantityFromUnit;
116880
117210
  exports.rankStreams = rankStreams;
116881
117211
  exports.readClusterStepModels = readClusterStepModels;
116882
117212
  exports.readClusterStepSettings = readClusterStepSettings;
@@ -116895,6 +117225,7 @@ exports.readRecoveryRecipeForSteps = readRecoveryRecipeForSteps;
116895
117225
  exports.readTimelapseGeneratedAt = readTimelapseGeneratedAt;
116896
117226
  exports.readUseRootModel = readUseRootModel;
116897
117227
  exports.readinessKey = require_sleep.readinessKey;
117228
+ exports.readingMeta = require_sleep.readingMeta;
116898
117229
  exports.rebootCapability = rebootCapability;
116899
117230
  exports.recommendNode = recommendNode;
116900
117231
  exports.reconcileBlockDevices = reconcileBlockDevices;
@@ -116995,6 +117326,7 @@ exports.runningFromMediaPlayer = runningFromMediaPlayer;
116995
117326
  exports.runningFromVacuumState = runningFromVacuumState;
116996
117327
  exports.runningStateCapability = runningStateCapability;
116997
117328
  exports.runtimeDevices = runtimeDevices;
117329
+ exports.runtimeFieldUnit = runtimeFieldUnit;
116998
117330
  exports.runtimeStatePolicyFor = runtimeStatePolicyFor;
116999
117331
  exports.sameDoFamily = sameDoFamily;
117000
117332
  exports.sameRead = sameRead;
@@ -117114,6 +117446,8 @@ exports.templateVarSystemEventApplies = templateVarSystemEventApplies;
117114
117446
  exports.templateVarsFor = templateVarsFor;
117115
117447
  exports.terminalSessionCapability = terminalSessionCapability;
117116
117448
  exports.textToHtml = textToHtml;
117449
+ exports.thresholdToBase = thresholdToBase;
117450
+ exports.thresholdToDisplay = thresholdToDisplay;
117117
117451
  exports.timeOfDayToMinutes = timeOfDayToMinutes;
117118
117452
  exports.toAppLinkHubOrigin = toAppLinkHubOrigin;
117119
117453
  exports.toDeviceSummary = toDeviceSummary;
@@ -117129,6 +117463,7 @@ exports.trainingCapability = trainingCapability;
117129
117463
  exports.trainingProviderCapability = trainingProviderCapability;
117130
117464
  exports.transcodeBody = transcodeBody;
117131
117465
  exports.transformSecretWrite = require_sleep.transformSecretWrite;
117466
+ exports.tryConvertDelta = tryConvertDelta;
117132
117467
  exports.tryConvertUnit = tryConvertUnit;
117133
117468
  exports.turnProviderCapability = turnProviderCapability;
117134
117469
  exports.unitCompanionField = unitCompanionField;