@camstack/addon-pipeline 1.2.626 → 1.2.628

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 (35) hide show
  1. package/dist/audio-analyzer/index.js +3 -3
  2. package/dist/audio-analyzer/index.mjs +3 -3
  3. package/dist/{custom-model-catalog-CacTUROW.mjs → custom-model-catalog-Bu1Lc-iF.mjs} +2 -2
  4. package/dist/{custom-model-catalog-CgxVHzSZ.js → custom-model-catalog-D2oFV46F.js} +2 -2
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-CtbRj-Dn.js → dist-BjySFF52.js} +289 -428
  8. package/dist/{dist-Bg9whnXR.mjs → dist-DNW4WfVK.mjs} +289 -428
  9. package/dist/{model-catalogs-D04DNvMI.mjs → model-catalogs-CCHnV0wA.mjs} +1 -1
  10. package/dist/{model-catalogs-BQqKruf-.js → model-catalogs-CwuCl3IY.js} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node-PivXOnUx.js → node-CLDkYxRi.js} +1 -1
  14. package/dist/{node-CfgKBxdH.mjs → node-CiT7lJCO.mjs} +1 -1
  15. package/dist/pipeline-runner/index.js +3 -3
  16. package/dist/pipeline-runner/index.mjs +3 -3
  17. package/dist/{process-memory-Z6v2Fm60.mjs → process-memory-6dtW_uwK.mjs} +1 -1
  18. package/dist/{process-memory-68U4DnuZ.js → process-memory-D5WNQP42.js} +1 -1
  19. package/dist/recorder/index.js +2 -2
  20. package/dist/recorder/index.mjs +2 -2
  21. package/dist/{segment-demux-js-CRzvBMVr.mjs → segment-demux-js-CL6CBu1v.mjs} +1 -1
  22. package/dist/{segment-demux-js-BcnG-zVD.js → segment-demux-js-DDI4hgPB.js} +1 -1
  23. package/dist/stream-broker/_stub.js +2 -2
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B9jSVeoa.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DrEPZZ6A.mjs} +3 -3
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-aLQXb88Y.mjs +26 -0
  26. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CEiM-boT.mjs +26 -0
  27. package/dist/stream-broker/demux-worker-child.js +1 -1
  28. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  29. package/dist/stream-broker/{hostInit-C_J8Mxe2.mjs → hostInit-CsSJvo8w.mjs} +3 -3
  30. package/dist/stream-broker/index.js +3 -3
  31. package/dist/stream-broker/index.mjs +3 -3
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/package.json +7 -2
  34. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D-GlPMcf.mjs +0 -26
  35. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CPQe09h1.mjs +0 -26
@@ -5567,7 +5567,7 @@ var ZodIssueCode = {
5567
5567
  var ZodFirstPartyTypeKind;
5568
5568
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5569
5569
  //#endregion
5570
- //#region ../types/dist/sleep-BErAxhul.mjs
5570
+ //#region ../types/dist/sleep-CARd6wZF.mjs
5571
5571
  /**
5572
5572
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5573
5573
  * window to float samples (D455).
@@ -8315,6 +8315,52 @@ function defineRecordKinds(specs) {
8315
8315
  }
8316
8316
  return out;
8317
8317
  }
8318
+ var READING_QUANTITIES = [
8319
+ "temperature",
8320
+ "pressure",
8321
+ "speed",
8322
+ "precipitation",
8323
+ "precipitation-rate",
8324
+ "length",
8325
+ "area",
8326
+ "illuminance",
8327
+ "irradiance",
8328
+ "power",
8329
+ "energy",
8330
+ "voltage",
8331
+ "current",
8332
+ "apparent-power",
8333
+ "humidity",
8334
+ "percentage",
8335
+ "concentration-volume",
8336
+ "concentration-mass",
8337
+ "concentration-ppb",
8338
+ "volume",
8339
+ "duration",
8340
+ "duration-ms",
8341
+ "information",
8342
+ "sound-level",
8343
+ "signal-level",
8344
+ "colour-temperature",
8345
+ "angle",
8346
+ "bitrate",
8347
+ "rate",
8348
+ "mass",
8349
+ "index",
8350
+ "ratio",
8351
+ "count",
8352
+ "none",
8353
+ "device"
8354
+ ];
8355
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8356
+ new Set(READING_QUANTITIES);
8357
+ function readingMeta(quantity, extras) {
8358
+ return {
8359
+ quantity,
8360
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8361
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8362
+ };
8363
+ }
8318
8364
  /** Shared Zod schemas used across streaming capabilities. */
8319
8365
  var CamProfileSchema = _enum([
8320
8366
  "high",
@@ -8348,8 +8394,8 @@ var CamStreamKindSchema = _enum([
8348
8394
  "derived"
8349
8395
  ]);
8350
8396
  var CamStreamResolutionSchema = object({
8351
- width: number().int().positive(),
8352
- height: number().int().positive()
8397
+ width: number().int().positive().meta(readingMeta("none")),
8398
+ height: number().int().positive().meta(readingMeta("none"))
8353
8399
  });
8354
8400
  var CameraStreamSchema = object({
8355
8401
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -9135,14 +9181,6 @@ Object.freeze({
9135
9181
  {
9136
9182
  "path": "aqi",
9137
9183
  "valueType": "number"
9138
- },
9139
- {
9140
- "path": "unit",
9141
- "valueType": "string"
9142
- },
9143
- {
9144
- "path": "precision",
9145
- "valueType": "number"
9146
9184
  }
9147
9185
  ],
9148
9186
  "alarm-panel": [
@@ -9178,20 +9216,10 @@ Object.freeze({
9178
9216
  "valueType": "boolean"
9179
9217
  }
9180
9218
  ],
9181
- "ambient-light-sensor": [
9182
- {
9183
- "path": "lux",
9184
- "valueType": "number"
9185
- },
9186
- {
9187
- "path": "unit",
9188
- "valueType": "string"
9189
- },
9190
- {
9191
- "path": "precision",
9192
- "valueType": "number"
9193
- }
9194
- ],
9219
+ "ambient-light-sensor": [{
9220
+ "path": "lux",
9221
+ "valueType": "number"
9222
+ }],
9195
9223
  "audio-metrics": [
9196
9224
  {
9197
9225
  "path": "ts",
@@ -9271,6 +9299,10 @@ Object.freeze({
9271
9299
  "path": "lastUpdated",
9272
9300
  "valueType": "number"
9273
9301
  },
9302
+ {
9303
+ "path": "volts",
9304
+ "valueType": "number"
9305
+ },
9274
9306
  {
9275
9307
  "path": "lastContactAt",
9276
9308
  "valueType": "number"
@@ -9513,10 +9545,6 @@ Object.freeze({
9513
9545
  "datetime"
9514
9546
  ]
9515
9547
  },
9516
- {
9517
- "path": "unit",
9518
- "valueType": "string"
9519
- },
9520
9548
  {
9521
9549
  "path": "min",
9522
9550
  "valueType": "number"
@@ -9528,19 +9556,6 @@ Object.freeze({
9528
9556
  {
9529
9557
  "path": "step",
9530
9558
  "valueType": "number"
9531
- },
9532
- {
9533
- "path": "precision",
9534
- "valueType": "number"
9535
- },
9536
- {
9537
- "path": "format",
9538
- "valueType": "enum",
9539
- "enumValues": [
9540
- "date",
9541
- "time",
9542
- "datetime"
9543
- ]
9544
9559
  }
9545
9560
  ],
9546
9561
  "cover": [
@@ -9781,13 +9796,8 @@ Object.freeze({
9781
9796
  "path": "value",
9782
9797
  "valueType": "string"
9783
9798
  }, {
9784
- "path": "format",
9785
- "valueType": "enum",
9786
- "enumValues": [
9787
- "date",
9788
- "time",
9789
- "datetime"
9790
- ]
9799
+ "path": "timestamp",
9800
+ "valueType": "boolean"
9791
9801
  }],
9792
9802
  "event-emitter": [
9793
9803
  {
@@ -9894,20 +9904,10 @@ Object.freeze({
9894
9904
  "valueType": "number"
9895
9905
  }
9896
9906
  ],
9897
- "humidity-sensor": [
9898
- {
9899
- "path": "percent",
9900
- "valueType": "number"
9901
- },
9902
- {
9903
- "path": "unit",
9904
- "valueType": "string"
9905
- },
9906
- {
9907
- "path": "precision",
9908
- "valueType": "number"
9909
- }
9910
- ],
9907
+ "humidity-sensor": [{
9908
+ "path": "percent",
9909
+ "valueType": "number"
9910
+ }],
9911
9911
  "image": [{
9912
9912
  "path": "url",
9913
9913
  "valueType": "string"
@@ -10246,20 +10246,10 @@ Object.freeze({
10246
10246
  "valueType": "boolean"
10247
10247
  }
10248
10248
  ],
10249
- "numeric-sensor": [
10250
- {
10251
- "path": "value",
10252
- "valueType": "number"
10253
- },
10254
- {
10255
- "path": "unit",
10256
- "valueType": "string"
10257
- },
10258
- {
10259
- "path": "precision",
10260
- "valueType": "number"
10261
- }
10262
- ],
10249
+ "numeric-sensor": [{
10250
+ "path": "value",
10251
+ "valueType": "number"
10252
+ }],
10263
10253
  "pet-feeder": [
10264
10254
  {
10265
10255
  "path": "foodLevel",
@@ -10351,14 +10341,6 @@ Object.freeze({
10351
10341
  {
10352
10342
  "path": "powerFactor",
10353
10343
  "valueType": "number"
10354
- },
10355
- {
10356
- "path": "unit",
10357
- "valueType": "string"
10358
- },
10359
- {
10360
- "path": "precision",
10361
- "valueType": "number"
10362
10344
  }
10363
10345
  ],
10364
10346
  "presence": [
@@ -10387,20 +10369,10 @@ Object.freeze({
10387
10369
  "valueType": "number"
10388
10370
  }
10389
10371
  ],
10390
- "pressure-sensor": [
10391
- {
10392
- "path": "hpa",
10393
- "valueType": "number"
10394
- },
10395
- {
10396
- "path": "unit",
10397
- "valueType": "string"
10398
- },
10399
- {
10400
- "path": "precision",
10401
- "valueType": "number"
10402
- }
10403
- ],
10372
+ "pressure-sensor": [{
10373
+ "path": "hpa",
10374
+ "valueType": "number"
10375
+ }],
10404
10376
  "privacy-mask": [{
10405
10377
  "path": "enabled",
10406
10378
  "valueType": "boolean"
@@ -10711,20 +10683,10 @@ Object.freeze({
10711
10683
  "path": "tampered",
10712
10684
  "valueType": "boolean"
10713
10685
  }],
10714
- "temperature-sensor": [
10715
- {
10716
- "path": "celsius",
10717
- "valueType": "number"
10718
- },
10719
- {
10720
- "path": "unit",
10721
- "valueType": "string"
10722
- },
10723
- {
10724
- "path": "precision",
10725
- "valueType": "number"
10726
- }
10727
- ],
10686
+ "temperature-sensor": [{
10687
+ "path": "celsius",
10688
+ "valueType": "number"
10689
+ }],
10728
10690
  "update": [
10729
10691
  {
10730
10692
  "path": "currentVersion",
@@ -10835,10 +10797,6 @@ Object.freeze({
10835
10797
  "path": "temperature",
10836
10798
  "valueType": "number"
10837
10799
  },
10838
- {
10839
- "path": "temperatureUnit",
10840
- "valueType": "string"
10841
- },
10842
10800
  {
10843
10801
  "path": "humidity",
10844
10802
  "valueType": "number"
@@ -10847,18 +10805,10 @@ Object.freeze({
10847
10805
  "path": "pressure",
10848
10806
  "valueType": "number"
10849
10807
  },
10850
- {
10851
- "path": "pressureUnit",
10852
- "valueType": "string"
10853
- },
10854
10808
  {
10855
10809
  "path": "windSpeed",
10856
10810
  "valueType": "number"
10857
10811
  },
10858
- {
10859
- "path": "windSpeedUnit",
10860
- "valueType": "string"
10861
- },
10862
10812
  {
10863
10813
  "path": "windBearing",
10864
10814
  "valueType": "number"
@@ -21032,7 +20982,8 @@ var CompositionProblemSchema = object({
21032
20982
  "item-field-reserved",
21033
20983
  "target-missing",
21034
20984
  "target-unreadable",
21035
- "not-replaceable"
20985
+ "not-replaceable",
20986
+ "native-capability-replace"
21036
20987
  ]),
21037
20988
  path: string(),
21038
20989
  message: string()
@@ -21090,9 +21041,11 @@ var CompositionFieldPlanSchema = object({
21090
21041
  reads: array(CompositionFieldReadSchema)
21091
21042
  });
21092
21043
  /**
21093
- * What a feature does to its target (D663): `new` builds a capability on a new
21094
- * device; on an existing device it `add`s a capability the device lacks, or
21095
- * `replace`s named fields of one it has natively.
21044
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
21045
+ * new device; on an existing device it `add`s a capability the device lacks.
21046
+ * Replacing a capability the device already serves is refused
21047
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
21048
+ * wire for a claim the hub already holds.
21096
21049
  */
21097
21050
  var CompositionFeatureModeSchema = _enum([
21098
21051
  "new",
@@ -23009,12 +22962,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
23009
22962
  * providers detect renames by matching `uniqueId` and patch `id`
23010
22963
  * in-place so dispatch keeps working.
23011
22964
  *
23012
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
23013
- * `precision`) live in the cap STATUS SLICE so they flow live with every
23014
- * state push and are the single source of truth. The `device_class` tag is
23015
- * an HA-specific input that is consumed at the discovery boundary
23016
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
23017
- * stored after that point.
22965
+ * **Display unit and precision are NOT stored here.** They are a read-time
22966
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
22967
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
22968
+ * neither is repeated on each sample. The `device_class` tag is an
22969
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
22970
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
23018
22971
  *
23019
22972
  * The `raw` escape hatch carries provider-specific attributes that don't
23020
22973
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -23028,6 +22981,14 @@ var SourceInfoSchema = object({
23028
22981
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
23029
22982
  * Used to detect rename when `id` changes. */
23030
22983
  uniqueId: string().optional(),
22984
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
22985
+ quantity: ReadingQuantitySchema.optional(),
22986
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
22987
+ dateTimeShape: _enum([
22988
+ "date",
22989
+ "time",
22990
+ "datetime"
22991
+ ]).optional(),
23031
22992
  /** Provider-specific extras that don't fit the structured slots.
23032
22993
  * Whitelist what goes here per provider — do NOT dump entire HA
23033
22994
  * attribute blobs (would explode the meta JSON column). */
@@ -43944,7 +43905,8 @@ method(object({
43944
43905
  "enum"
43945
43906
  ]),
43946
43907
  enumValues: array(string()).optional(),
43947
- item: boolean().optional()
43908
+ item: boolean().optional(),
43909
+ quantity: ReadingQuantitySchema.optional()
43948
43910
  })).readonly(),
43949
43911
  itemArray: object({
43950
43912
  path: string(),
@@ -48154,8 +48116,8 @@ var MaskPolygonShapeSchema = object({
48154
48116
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
48155
48117
  var MaskGridShapeSchema = object({
48156
48118
  kind: literal("grid"),
48157
- gridWidth: number(),
48158
- gridHeight: number(),
48119
+ gridWidth: number().meta(readingMeta("none")),
48120
+ gridHeight: number().meta(readingMeta("none")),
48159
48121
  cells: array(boolean())
48160
48122
  });
48161
48123
  discriminatedUnion("kind", [
@@ -60389,46 +60351,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
60389
60351
  }), LogStreamEntrySchema, { kind: "subscription" });
60390
60352
  object({
60391
60353
  /** Carbon dioxide concentration in ppm. */
60392
- co2Ppm: number().min(0).optional(),
60354
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
60393
60355
  /** Total volatile organic compounds in ppb. */
60394
- vocPpb: number().min(0).optional(),
60356
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
60395
60357
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
60396
- pm25: number().min(0).optional(),
60358
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
60397
60359
  /** Particulate matter ≤ 10 μm in µg/m³. */
60398
- pm10: number().min(0).optional(),
60360
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
60399
60361
  /** Composite AQI value (typically 0..500). */
60400
- aqi: number().optional(),
60362
+ aqi: number().optional().meta(readingMeta("index")),
60401
60363
  /** Ms epoch when the slice was last updated. */
60402
- lastFetchedAt: number(),
60403
- /** Live display unit of the single metric this slice carries (e.g. HA
60404
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
60405
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
60406
- * per slice is unambiguous. */
60407
- unit: string().optional(),
60408
- /** Suggested decimal places for numeric display.
60409
- * Populated live from the upstream source when provided (e.g. HA
60410
- * `attributes.suggested_display_precision`). Falls back to
60411
- * auto-formatting when absent. */
60412
- precision: number().int().min(0).max(10).optional()
60364
+ lastFetchedAt: number()
60413
60365
  });
60414
60366
  DeviceType.Sensor;
60415
60367
  object({
60416
- /** Current illuminance in lux (lx). */
60417
- lux: number().min(0).meta({
60418
- unit: "lx",
60368
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
60369
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
60370
+ precision: 0,
60419
60371
  label: "Illuminance"
60420
- }),
60372
+ })),
60421
60373
  /** Ms epoch when the slice was last updated. */
60422
- lastFetchedAt: number(),
60423
- /** Live display unit from the upstream source (e.g. HA
60424
- * `attributes.unit_of_measurement`). The UI prefers this over the
60425
- * role's canonical unit. Absent → fall back to the canonical unit. */
60426
- unit: string().optional(),
60427
- /** Suggested decimal places for numeric display.
60428
- * Populated live from the upstream source when provided (e.g. HA
60429
- * `attributes.suggested_display_precision`). Falls back to
60430
- * auto-formatting when absent. */
60431
- precision: number().int().min(0).max(10).optional()
60374
+ lastFetchedAt: number()
60432
60375
  });
60433
60376
  DeviceType.Sensor;
60434
60377
  /**
@@ -60573,10 +60516,7 @@ var BatteryStatusSchema = object({
60573
60516
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
60574
60517
  * declines to band a non-finite reading, which is the correct shape.
60575
60518
  */
60576
- percentage: number().min(0).max(100).nullable().meta({
60577
- unit: "%",
60578
- label: "Charge"
60579
- }),
60519
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
60580
60520
  /**
60581
60521
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
60582
60522
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -60603,7 +60543,14 @@ var BatteryStatusSchema = object({
60603
60543
  */
60604
60544
  sleeping: boolean().meta({ label: "Sleeping" }),
60605
60545
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60606
- lastUpdated: number(),
60546
+ lastUpdated: number().meta(readingMeta("none")),
60547
+ /**
60548
+ * Battery voltage in volts, for a cell the firmware reports as voltage
60549
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
60550
+ * that only report charge. A second reading on this cap, not a second
60551
+ * `numeric-sensor` on the same device.
60552
+ */
60553
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
60607
60554
  /**
60608
60555
  * Ms epoch of the last time the device PROVED it was reachable — a
60609
60556
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -60655,7 +60602,6 @@ var BATTERY_DEVICE_TYPES = [
60655
60602
  /** Generic Customize target for the percentage reading (D936). */
60656
60603
  var batteryCustomize = {
60657
60604
  version: 1,
60658
- advanced: "derive",
60659
60605
  targets: [{
60660
60606
  id: "percentage",
60661
60607
  cardinality: "scalar",
@@ -60664,7 +60610,7 @@ var batteryCustomize = {
60664
60610
  stateField: "percentage"
60665
60611
  },
60666
60612
  deviceTypes: BATTERY_DEVICE_TYPES,
60667
- apply: "add-or-replace",
60613
+ apply: "add",
60668
60614
  path: "percentage",
60669
60615
  sources: [{
60670
60616
  kind: "field",
@@ -60785,10 +60731,16 @@ onStatusChanged: { data: object({
60785
60731
  /**
60786
60732
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
60787
60733
  */
60788
- history: { fields: { percentage: {
60789
- kind: "gauge",
60790
- unit: "%"
60791
- } } },
60734
+ history: { fields: {
60735
+ percentage: {
60736
+ kind: "gauge",
60737
+ unit: "%"
60738
+ },
60739
+ volts: {
60740
+ kind: "gauge",
60741
+ unit: "V"
60742
+ }
60743
+ } },
60792
60744
  customize: batteryCustomize,
60793
60745
  /** Clock fields: written, but excluded from the compare that decides
60794
60746
  * whether persisting is worth a SQLite commit. */
@@ -60802,7 +60754,7 @@ object({
60802
60754
  DeviceType.Sensor;
60803
60755
  object({
60804
60756
  /** Current level as 0..100 inclusive. Firmware-reported. */
60805
- percentage: number().min(0).max(100),
60757
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
60806
60758
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
60807
60759
  lastChangedAt: number()
60808
60760
  });
@@ -61187,21 +61139,21 @@ object({
61187
61139
  /** Single setpoint in Celsius. Used by single-target modes
61188
61140
  * (heat / cool / dry). Null when the device is in a range mode
61189
61141
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
61190
- target: number().nullable(),
61142
+ target: number().nullable().meta(readingMeta("temperature")),
61191
61143
  /** Upper bound of the dual setpoint in Celsius. Populated only
61192
61144
  * in `heat_cool` mode. */
61193
- targetHigh: number().nullable(),
61145
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
61194
61146
  /** Lower bound of the dual setpoint in Celsius. Populated only
61195
61147
  * in `heat_cool` mode. */
61196
- targetLow: number().nullable(),
61148
+ targetLow: number().nullable().meta(readingMeta("temperature")),
61197
61149
  /** Target relative humidity (0..100). Null when the device has no
61198
61150
  * humidity control. */
61199
- targetHumidity: number().min(0).max(100).nullable(),
61151
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
61200
61152
  /** Current measured temperature in Celsius. Read-only — pushed by
61201
61153
  * the device's internal sensor. */
61202
- currentTemp: number().nullable(),
61154
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
61203
61155
  /** Current measured relative humidity (0..100). Read-only. */
61204
- currentHumidity: number().min(0).max(100).nullable(),
61156
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
61205
61157
  /** Vertical louver swing on/off. Null when the device has no vertical
61206
61158
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
61207
61159
  swingVertical: boolean().nullable(),
@@ -61209,11 +61161,11 @@ object({
61209
61161
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
61210
61162
  swingHorizontal: boolean().nullable(),
61211
61163
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
61212
- minTemp: number().nullable().optional(),
61164
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
61213
61165
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
61214
- maxTemp: number().nullable().optional(),
61166
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
61215
61167
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
61216
- tempStep: number().positive().nullable().optional(),
61168
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
61217
61169
  /** Ms epoch when the slice was last updated (push or command). */
61218
61170
  lastFetchedAt: number()
61219
61171
  });
@@ -61292,17 +61244,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
61292
61244
  * modes to expose in the picker without a slice fetch.
61293
61245
  */
61294
61246
  var RgbTripletSchema = object({
61295
- r: number().int().min(0).max(255),
61296
- g: number().int().min(0).max(255),
61297
- b: number().int().min(0).max(255)
61247
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
61248
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
61249
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
61298
61250
  });
61299
61251
  var HsvTripletSchema = object({
61300
61252
  /** Hue in degrees, 0..360. */
61301
- h: number().min(0).max(360),
61253
+ h: number().min(0).max(360).meta(readingMeta("none")),
61302
61254
  /** Saturation as 0..100 inclusive. */
61303
- s: number().min(0).max(100),
61255
+ s: number().min(0).max(100).meta(readingMeta("none")),
61304
61256
  /** Value/brightness as 0..100 inclusive. */
61305
- v: number().min(0).max(100)
61257
+ v: number().min(0).max(100).meta(readingMeta("none"))
61306
61258
  });
61307
61259
  var ColorInputSchema = discriminatedUnion("mode", [
61308
61260
  object({
@@ -61331,7 +61283,7 @@ object({
61331
61283
  /** Populated when `mode === 'hsv'`. */
61332
61284
  hsv: HsvTripletSchema.optional(),
61333
61285
  /** Populated when `mode === 'mired'`. */
61334
- mireds: number().int().optional(),
61286
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
61335
61287
  /** Ms epoch of the last operator-driven change. */
61336
61288
  lastChangedAt: number()
61337
61289
  });
@@ -61455,18 +61407,12 @@ _enum([
61455
61407
  "empty",
61456
61408
  "full"
61457
61409
  ]);
61458
- /** Units a remaining-life counter is reported in. Closed: the panel renders
61459
- * each. */
61460
- var ConsumableRemainingUnitSchema = _enum([
61461
- "days",
61462
- "hours",
61463
- "cycles"
61464
- ]);
61465
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
61466
- * days). `value` null = unknown (D393). */
61410
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
61411
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
61412
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
61467
61413
  var ConsumableRemainingSchema = object({
61468
- value: number().min(0).nullable(),
61469
- unit: ConsumableRemainingUnitSchema
61414
+ value: number().min(0).nullable().meta(readingMeta("device")),
61415
+ kind: ConsumableRemainingKindSchema
61470
61416
  });
61471
61417
  var ConsumablesStatusSchema = object({
61472
61418
  items: array(object({
@@ -61475,7 +61421,7 @@ var ConsumablesStatusSchema = object({
61475
61421
  /** Display name. */
61476
61422
  label: string().min(1),
61477
61423
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
61478
- level: number().min(0).max(100).nullable(),
61424
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61479
61425
  /** The provider's discrete verdict when known (binary mode). */
61480
61426
  status: ConsumableStatusSchema.nullable(),
61481
61427
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -61492,7 +61438,7 @@ var ConsumablesStatusSchema = object({
61492
61438
  })),
61493
61439
  lastChangedAt: number()
61494
61440
  });
61495
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
61441
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
61496
61442
  Object.values(DeviceType), method(object({
61497
61443
  deviceId: number().int().nonnegative(),
61498
61444
  key: string().min(1)
@@ -61517,39 +61463,17 @@ object({
61517
61463
  /** Current value. Numeric kind → number; select/text/datetime → string.
61518
61464
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
61519
61465
  * or time-only — HA accepts all three forms). */
61520
- value: union$1([number(), string()]),
61466
+ value: union$1([number().meta(readingMeta("device")), string()]),
61521
61467
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
61522
61468
  options: array(string()),
61523
61469
  /** Ms epoch when the slice was last updated. */
61524
61470
  lastChangedAt: number(),
61525
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
61526
- * Populated live from HA `attributes.unit_of_measurement` on each push.
61527
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61528
- unit: string().optional(),
61529
- /** Minimum allowed value. Populated live from HA `attributes.min`.
61530
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61531
- min: number().optional(),
61532
- /** Maximum allowed value. Populated live from HA `attributes.max`.
61533
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61534
- max: number().optional(),
61535
- /** Step increment. Populated live from HA `attributes.step`.
61536
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61537
- step: number().optional(),
61538
- /** Suggested decimal places for numeric display. Populated live from HA
61539
- * `attributes.suggested_display_precision`, or derived from `step` when the
61540
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
61541
- * other kinds. Falls back to auto-formatting when absent. */
61542
- precision: number().int().min(0).max(10).optional(),
61543
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
61544
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
61545
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
61546
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
61547
- * `has_date`/`has_time` attributes on each push. */
61548
- format: _enum([
61549
- "date",
61550
- "time",
61551
- "datetime"
61552
- ]).optional()
61471
+ /** Minimum allowed value, in the device quantity's base unit. */
61472
+ min: number().optional().meta(readingMeta("device")),
61473
+ /** Maximum allowed value, in the device quantity's base unit. */
61474
+ max: number().optional().meta(readingMeta("device")),
61475
+ /** Step increment, in the device quantity's base unit. */
61476
+ step: number().optional().meta(readingMeta("device"))
61553
61477
  });
61554
61478
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
61555
61479
  object({
@@ -61587,9 +61511,9 @@ object({
61587
61511
  ]),
61588
61512
  /** 0 = fully closed, 100 = fully open. Null when the device has
61589
61513
  * no intermediate position surface. */
61590
- position: number().min(0).max(100).nullable(),
61514
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61591
61515
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
61592
- tiltPosition: number().min(0).max(100).nullable(),
61516
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61593
61517
  /** Ms epoch when the slice was last updated. */
61594
61518
  lastChangedAt: number()
61595
61519
  });
@@ -61647,9 +61571,9 @@ var NormalizedRangeSchema$1 = object({
61647
61571
  object({
61648
61572
  mode: DayNightModeSchema,
61649
61573
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
61650
- sensitivity: number().optional(),
61574
+ sensitivity: number().optional().meta(readingMeta("none")),
61651
61575
  /** Delay before the IR-cut filter flips, in seconds. */
61652
- switchDelaySec: number().optional(),
61576
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
61653
61577
  lastFetchedAt: number()
61654
61578
  });
61655
61579
  /**
@@ -61804,11 +61728,11 @@ object({
61804
61728
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
61805
61729
  availablePrograms: array(string()),
61806
61730
  /** Seconds remaining on the active cycle, or null when not running / not known. */
61807
- remainingSeconds: number().int().nonnegative().nullable(),
61731
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
61808
61732
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
61809
- delayedStartSeconds: number().int().nonnegative().nullable(),
61733
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
61810
61734
  /** Cycle completion 0..100, or null when not applicable. */
61811
- progressPercent: number().min(0).max(100).nullable(),
61735
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61812
61736
  /** True when the door is reported open; null when the door state is not known. */
61813
61737
  doorOpen: boolean().nullable(),
61814
61738
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -61832,7 +61756,7 @@ object({
61832
61756
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
61833
61757
  machineCareDue: boolean().nullable(),
61834
61758
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
61835
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
61759
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
61836
61760
  /** Option states: null when the appliance does not expose the option or has not said. */
61837
61761
  halfLoad: boolean().nullable(),
61838
61762
  hygienePlus: boolean().nullable(),
@@ -61847,7 +61771,7 @@ object({
61847
61771
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
61848
61772
  activeEvents: array(string()),
61849
61773
  /** Numeric error code when the appliance reports one; null otherwise. */
61850
- errorCode: number().nullable(),
61774
+ errorCode: number().nullable().meta(readingMeta("none")),
61851
61775
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
61852
61776
  errorLabel: string().nullable(),
61853
61777
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -61993,24 +61917,14 @@ object({
61993
61917
  /** Ms epoch of the last press. null = never observed since this provider started. */
61994
61918
  lastPressedAt: number().nullable(),
61995
61919
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
61996
- pressCountSinceStart: number()
61920
+ pressCountSinceStart: number().meta(readingMeta("count"))
61997
61921
  });
61998
61922
  object({
61999
61923
  deviceId: number(),
62000
61924
  timestamp: number()
62001
61925
  });
62002
61926
  DeviceType.Button, DeviceType.Camera;
62003
- /**
62004
- * Enum-state sensor — a string value picked from a finite option set.
62005
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
62006
- * states, weather conditions, contact-source channels, mode strings).
62007
- * The option set is stable across observations; it is persisted in the
62008
- * device config blob (via the control slice at adoption) and is NOT
62009
- * stored in `sourceInfo`.
62010
- */
62011
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
62012
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
62013
- var EnumSensorDateTimeFormatSchema = _enum([
61927
+ _enum([
62014
61928
  "date",
62015
61929
  "time",
62016
61930
  "datetime"
@@ -62018,12 +61932,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
62018
61932
  object({
62019
61933
  value: string(),
62020
61934
  /**
62021
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
62022
- * as a locale date/time/datetime; absent for plain enum sensors. HA
62023
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
62024
- * a time-only sensor → `'time'`.
61935
+ * Set when `value` is a timestamp rather than an enum state, so history
61936
+ * does not learn ISO strings as states. The display shape (date / time /
61937
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
62025
61938
  */
62026
- format: EnumSensorDateTimeFormatSchema.optional(),
61939
+ timestamp: boolean().optional(),
62027
61940
  /** Ms epoch when the slice was last updated. */
62028
61941
  lastFetchedAt: number()
62029
61942
  });
@@ -62042,13 +61955,13 @@ var EventFireSchema = object({
62042
61955
  deviceId: number(),
62043
61956
  eventType: string(),
62044
61957
  data: record(string(), unknown()).nullable(),
62045
- timestamp: number(),
62046
- seq: number()
61958
+ timestamp: number().meta(readingMeta("none")),
61959
+ seq: number().meta(readingMeta("count"))
62047
61960
  });
62048
61961
  object({
62049
61962
  eventTypes: array(string()),
62050
61963
  lastEvent: EventFireSchema.nullable(),
62051
- eventCountSinceStart: number()
61964
+ eventCountSinceStart: number().meta(readingMeta("count"))
62052
61965
  });
62053
61966
  DeviceType.EventEmitter;
62054
61967
  var IdentitySchema = object({
@@ -62437,10 +62350,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
62437
62350
  object({
62438
62351
  /** Active speed as 0..100 inclusive. Null when the device has no
62439
62352
  * speed surface (single-speed fan). */
62440
- percentage: number().min(0).max(100).nullable(),
62353
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62441
62354
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
62442
62355
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
62443
- percentageStep: number().positive().optional(),
62356
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
62444
62357
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
62445
62358
  * — empty string when no preset is active or supported. */
62446
62359
  preset: string(),
@@ -62522,7 +62435,7 @@ var FeatureProbeStatusSchema = object({
62522
62435
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
62523
62436
  model: string().nullable(),
62524
62437
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
62525
- channelCount: number().nullable(),
62438
+ channelCount: number().nullable().meta(readingMeta("count")),
62526
62439
  /**
62527
62440
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
62528
62441
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -62556,7 +62469,7 @@ object({
62556
62469
  DeviceType.Sensor;
62557
62470
  object({
62558
62471
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
62559
- position: number().min(0).max(100).nullable(),
62472
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62560
62473
  /** Ms epoch when the slice was last updated (push or poll). */
62561
62474
  lastFetchedAt: number()
62562
62475
  });
@@ -62565,9 +62478,9 @@ object({
62565
62478
  /** Whether the humidifier is currently on. */
62566
62479
  on: boolean(),
62567
62480
  /** Current measured relative humidity (0..100). Null when not reported. */
62568
- currentHumidity: number().min(0).max(100).nullable(),
62481
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
62569
62482
  /** Target relative humidity (0..100). Null when no setpoint surface. */
62570
- targetHumidity: number().min(0).max(100).nullable(),
62483
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
62571
62484
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
62572
62485
  * device has no mode surface. */
62573
62486
  mode: string().nullable(),
@@ -62577,9 +62490,9 @@ object({
62577
62490
  * `idle` / `off`). Null when the device doesn't report it. */
62578
62491
  action: string().nullable(),
62579
62492
  /** HA `min_humidity` attribute. Null → UI uses 0. */
62580
- minHumidity: number().nullable(),
62493
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
62581
62494
  /** HA `max_humidity` attribute. Null → UI uses 100. */
62582
- maxHumidity: number().nullable(),
62495
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
62583
62496
  /** Ms epoch when the slice was last updated. */
62584
62497
  lastChangedAt: number()
62585
62498
  });
@@ -62603,22 +62516,13 @@ DeviceType.Humidifier, method(object({
62603
62516
  auth: "protected"
62604
62517
  });
62605
62518
  object({
62606
- /** Current relative humidity, 0..100. */
62607
- percent: number().min(0).max(100).meta({
62608
- unit: "%",
62519
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
62520
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
62521
+ precision: 0,
62609
62522
  label: "Humidity"
62610
- }),
62523
+ })),
62611
62524
  /** Ms epoch when the slice was last updated. */
62612
- lastFetchedAt: number(),
62613
- /** Live display unit from the upstream source (e.g. HA
62614
- * `attributes.unit_of_measurement`). The UI prefers this over the
62615
- * role's canonical unit. Absent → fall back to the canonical unit. */
62616
- unit: string().optional(),
62617
- /** Suggested decimal places for numeric display.
62618
- * Populated live from the upstream source when provided (e.g. HA
62619
- * `attributes.suggested_display_precision`). Falls back to
62620
- * auto-formatting when absent. */
62621
- precision: number().int().min(0).max(10).optional()
62525
+ lastFetchedAt: number()
62622
62526
  });
62623
62527
  DeviceType.Sensor, DeviceType.Thermostat;
62624
62528
  object({
@@ -62626,7 +62530,7 @@ object({
62626
62530
  * entity exposes no `entity_picture` (yet). */
62627
62531
  url: string().nullable(),
62628
62532
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
62629
- lastUpdated: number().nullable()
62533
+ lastUpdated: number().nullable().meta(readingMeta("none"))
62630
62534
  });
62631
62535
  DeviceType.Image;
62632
62536
  /**
@@ -62682,19 +62586,19 @@ var NormalizedRangeSchema = object({
62682
62586
  });
62683
62587
  object({
62684
62588
  /** Normalized 0–100. */
62685
- brightness: number().optional(),
62589
+ brightness: number().optional().meta(readingMeta("none")),
62686
62590
  /** Normalized 0–100. */
62687
- contrast: number().optional(),
62591
+ contrast: number().optional().meta(readingMeta("none")),
62688
62592
  /** Normalized 0–100. */
62689
- saturation: number().optional(),
62593
+ saturation: number().optional().meta(readingMeta("none")),
62690
62594
  /** Normalized 0–100. */
62691
- sharpness: number().optional(),
62595
+ sharpness: number().optional().meta(readingMeta("none")),
62692
62596
  mirror: boolean().optional(),
62693
62597
  flip: boolean().optional(),
62694
62598
  rotate: ImageRotateSchema.optional(),
62695
62599
  whiteBalance: WhiteBalanceModeSchema.optional(),
62696
62600
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
62697
- warmth: number().optional(),
62601
+ warmth: number().optional().meta(readingMeta("none")),
62698
62602
  exposureMode: ExposureModeSchema.optional(),
62699
62603
  backlightMode: BacklightModeSchema.optional(),
62700
62604
  lastFetchedAt: number()
@@ -62939,10 +62843,10 @@ var IntercomAbilitySchema = object({
62939
62843
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
62940
62844
  codecs: array(string()),
62941
62845
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
62942
- sampleRate: number(),
62846
+ sampleRate: number().meta(readingMeta("none")),
62943
62847
  duplex: _enum(["half", "full"]),
62944
62848
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
62945
- maxBacklogMs: number()
62849
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
62946
62850
  });
62947
62851
  var IntercomStatusSchema = object({
62948
62852
  /** True while a talk session is open. */
@@ -63053,13 +62957,13 @@ object({
63053
62957
  activity: LawnMowerActivitySchema,
63054
62958
  /** 0..100 battery percentage. Null when the device has no battery
63055
62959
  * reading. */
63056
- batteryLevel: number().min(0).max(100).nullable(),
62960
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63057
62961
  /** 0..100 mowing-completion percentage of the current task, or null when no
63058
62962
  * task is active / progress is unavailable. */
63059
- progressPercent: number().min(0).max(100).nullable(),
62963
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63060
62964
  /** Current device/event code (dynamic — mostly status, sometimes an error),
63061
62965
  * or null when unknown. */
63062
- currentCode: number().nullable(),
62966
+ currentCode: number().nullable().meta(readingMeta("none")),
63063
62967
  /** Human label for {@link currentCode}, or null when undecodable. */
63064
62968
  currentCodeLabel: string().nullable(),
63065
62969
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -63521,7 +63425,7 @@ object({
63521
63425
  state: MediaPlayerStateSchema,
63522
63426
  /** Volume as 0..100 inclusive. Null when the device has no volume
63523
63427
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
63524
- volumeLevel: number().min(0).max(100).nullable(),
63428
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63525
63429
  /** Mute toggle distinct from volume=0. Null when no mute surface.
63526
63430
  * Pair with `DeviceFeature.MediaPlayerMute`. */
63527
63431
  isMuted: boolean().nullable(),
@@ -63534,10 +63438,10 @@ object({
63534
63438
  currentMedia: MediaInfoSchema.nullable(),
63535
63439
  /** Current playback position in ms. Null when not seekable or
63536
63440
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
63537
- positionMs: number().int().nonnegative().nullable(),
63441
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
63538
63442
  /** Total duration of the current media in ms. Null when unknown
63539
63443
  * (live stream) or nothing is playing. */
63540
- durationMs: number().int().nonnegative().nullable(),
63444
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
63541
63445
  /** Shuffle toggle. Null when no shuffle surface.
63542
63446
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
63543
63447
  shuffle: boolean().nullable(),
@@ -63837,7 +63741,7 @@ object({
63837
63741
  * it, every provider seeded it at boot, and every session left it
63838
63742
  * `detected: true` after its detach.
63839
63743
  */
63840
- autoClearAfterMs: number().nullable()
63744
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
63841
63745
  });
63842
63746
  /**
63843
63747
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -63887,7 +63791,7 @@ var MotionZoneRegionSchema = object({
63887
63791
  });
63888
63792
  object({
63889
63793
  enabled: boolean(),
63890
- sensitivity: number(),
63794
+ sensitivity: number().meta(readingMeta("none")),
63891
63795
  /** Grid region(s). Today exactly one `grid` shape. */
63892
63796
  regions: array(MotionZoneRegionSchema),
63893
63797
  lastFetchedAt: number()
@@ -64191,7 +64095,7 @@ var NavigationStatusSchema = object({
64191
64095
  following: boolean(),
64192
64096
  flash: boolean(),
64193
64097
  lightMode: NavigationLightModeSchema,
64194
- lightLevel: number().min(40).max(100),
64098
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
64195
64099
  /** Ms epoch when the slice was last updated. */
64196
64100
  lastChangedAt: number()
64197
64101
  });
@@ -64237,13 +64141,13 @@ var NetworkLinkStatusSchema = object({
64237
64141
  * one whose reading has not landed must not be drawn at 0 %. Consumers
64238
64142
  * SKIP a null rather than coerce it.
64239
64143
  */
64240
- signalPercent: number().min(0).max(100).nullable(),
64144
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64241
64145
  /** Raw received signal strength in dBm, when the firmware reports one. */
64242
- rssiDbm: number().optional(),
64146
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
64243
64147
  /** Network name of a wireless link, when the firmware reports it. */
64244
64148
  ssid: string().optional(),
64245
64149
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
64246
- lastUpdated: number()
64150
+ lastUpdated: number().meta(readingMeta("none"))
64247
64151
  });
64248
64152
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
64249
64153
  deviceId: number(),
@@ -64562,7 +64466,7 @@ object({
64562
64466
  lastError: string().nullable(),
64563
64467
  /** Number of deliveries currently buffered server-side (rate-limited
64564
64468
  * notifiers may queue). 0 when the provider sends synchronously. */
64565
- queueDepth: number().int().nonnegative(),
64469
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
64566
64470
  /** Per-feature capability matrix — authoritative source for the
64567
64471
  * compose-form field gating. */
64568
64472
  supports: NotifierSupportsSchema
@@ -64612,15 +64516,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
64612
64516
  acceptedAt: number()
64613
64517
  });
64614
64518
  object({
64615
- value: number(),
64616
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
64617
- * Populated live from the upstream source on each state push.
64618
- * Absent for unitless sensors. */
64619
- unit: string().optional(),
64620
- /** Suggested decimal places for numeric display.
64621
- * Populated live from the upstream source when provided.
64622
- * Falls back to auto-formatting when absent. */
64623
- precision: number().int().min(0).max(10).optional(),
64519
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
64520
+ value: number().nullable().meta(readingMeta("device")),
64624
64521
  /** Ms epoch when the slice was last updated. */
64625
64522
  lastFetchedAt: number()
64626
64523
  });
@@ -64910,23 +64807,26 @@ object({
64910
64807
  /** Food currently in the bowl (grams). Null when the device has not
64911
64808
  * reported a reading yet. On dual-hopper models this is the combined
64912
64809
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
64913
- foodLevel: number().nullable(),
64810
+ foodLevel: number().nullable().meta(readingMeta("none")),
64914
64811
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
64915
64812
  * single-hopper models. */
64916
- food1: number().nullable(),
64813
+ food1: number().nullable().meta(readingMeta("none")),
64917
64814
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
64918
64815
  * single-hopper models. */
64919
- food2: number().nullable(),
64816
+ food2: number().nullable().meta(readingMeta("none")),
64920
64817
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
64921
64818
  * (`device_class: problem`, on = low). True when the bowl is empty /
64922
64819
  * below the feeder's low threshold. */
64923
64820
  lowFood: boolean(),
64924
64821
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
64925
64822
  * device has no battery reading. */
64926
- batteryPower: number().min(0).max(100).nullable(),
64927
- /** Days of desiccant life remaining. Null when the model has no
64928
- * desiccant sensor. */
64929
- desiccantLeftDays: number().nullable(),
64823
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64824
+ /**
64825
+ * Desiccant life remaining, stored in seconds (duration base, D944).
64826
+ * Providers convert from the days the feeder reports. Null when the
64827
+ * model has no desiccant sensor.
64828
+ */
64829
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
64930
64830
  /** True while a feed is in progress. */
64931
64831
  feeding: boolean(),
64932
64832
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -64937,7 +64837,7 @@ object({
64937
64837
  * with `errorCode` for consumers that want the raw integer. */
64938
64838
  error: string().nullable(),
64939
64839
  /** Raw device error code (0 / null = no error). */
64940
- errorCode: number().nullable(),
64840
+ errorCode: number().nullable().meta(readingMeta("none")),
64941
64841
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
64942
64842
  isDualHopper: boolean(),
64943
64843
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -64947,7 +64847,7 @@ object({
64947
64847
  /** Play a chime when dispensing. */
64948
64848
  feedSound: boolean(),
64949
64849
  /** Speaker / prompt volume level (device-scaled integer). */
64950
- volume: number(),
64850
+ volume: number().meta(readingMeta("none")),
64951
64851
  /** Ms epoch when the slice was last refreshed from the cloud. */
64952
64852
  lastFetchedAt: number()
64953
64853
  });
@@ -65384,36 +65284,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
65384
65284
  });
65385
65285
  object({
65386
65286
  /** Instantaneous power draw in watts. */
65387
- watts: number().optional().meta({
65388
- unit: "W",
65389
- label: "Power"
65390
- }),
65287
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
65391
65288
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
65392
- kwhTotal: number().optional().meta({
65393
- unit: "kWh",
65394
- label: "Energy"
65395
- }),
65289
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
65396
65290
  /** Voltage in volts. */
65397
- volts: number().optional(),
65291
+ volts: number().optional().meta(readingMeta("voltage")),
65398
65292
  /** Current in amperes. */
65399
- amps: number().optional(),
65400
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
65401
- va: number().optional(),
65293
+ amps: number().optional().meta(readingMeta("current")),
65294
+ /** Apparent power in volt-amperes. */
65295
+ va: number().optional().meta(readingMeta("apparent-power")),
65402
65296
  /** Power factor (0..1) if reported. */
65403
- powerFactor: number().min(0).max(1).optional(),
65297
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
65404
65298
  /** Ms epoch when the slice was last updated. */
65405
- lastFetchedAt: number(),
65406
- /** Live display unit of the single metric this slice carries (e.g. HA
65407
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
65408
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
65409
- * unit per slice is unambiguous. The UI prefers this over the role's
65410
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
65411
- unit: string().optional(),
65412
- /** Suggested decimal places for numeric display.
65413
- * Populated live from the upstream source when provided (e.g. HA
65414
- * `attributes.suggested_display_precision`). Falls back to
65415
- * auto-formatting when absent. */
65416
- precision: number().int().min(0).max(10).optional()
65299
+ lastFetchedAt: number()
65417
65300
  });
65418
65301
  DeviceType.Sensor;
65419
65302
  /**
@@ -65434,11 +65317,11 @@ DeviceType.Sensor;
65434
65317
  */
65435
65318
  var GpsLocationSchema = object({
65436
65319
  /** Latitude in decimal degrees, -90..90. */
65437
- latitude: number().min(-90).max(90),
65320
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
65438
65321
  /** Longitude in decimal degrees, -180..180. */
65439
- longitude: number().min(-180).max(180),
65322
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
65440
65323
  /** Reported accuracy in meters (lower = better). */
65441
- accuracyMeters: number().nonnegative()
65324
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
65442
65325
  });
65443
65326
  object({
65444
65327
  /** `home` / `not_home` / any user-defined zone name. */
@@ -65450,25 +65333,16 @@ object({
65450
65333
  gps: GpsLocationSchema.nullable(),
65451
65334
  /** Optional battery level of the tracking device (0..100). Null
65452
65335
  * when not reported. */
65453
- batteryPercent: number().min(0).max(100).nullable(),
65336
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65454
65337
  /** Ms epoch when the slice was last updated. */
65455
65338
  lastChangedAt: number()
65456
65339
  });
65457
65340
  DeviceType.Presence;
65458
65341
  object({
65459
- /** Current pressure in hPa. */
65460
- hpa: number(),
65342
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
65343
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
65461
65344
  /** Ms epoch when the slice was last updated. */
65462
- lastFetchedAt: number(),
65463
- /** Live display unit from the upstream source (e.g. HA
65464
- * `attributes.unit_of_measurement`). The UI prefers this over the
65465
- * role's canonical unit. Absent → fall back to the canonical unit. */
65466
- unit: string().optional(),
65467
- /** Suggested decimal places for numeric display.
65468
- * Populated live from the upstream source when provided (e.g. HA
65469
- * `attributes.suggested_display_precision`). Falls back to
65470
- * auto-formatting when absent. */
65471
- precision: number().int().min(0).max(10).optional()
65345
+ lastFetchedAt: number()
65472
65346
  });
65473
65347
  DeviceType.Sensor;
65474
65348
  /**
@@ -65801,8 +65675,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
65801
65675
  }), _void(), { kind: "mutation" });
65802
65676
  var PtzAutotrackSettingsSchema = object({
65803
65677
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
65804
- stopDelaySeconds: number().int().min(0).max(300),
65805
- disappearDelaySeconds: number().int().min(0).max(300)
65678
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
65679
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
65806
65680
  });
65807
65681
  /**
65808
65682
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -65859,7 +65733,7 @@ object({
65859
65733
  /** null = unknown — never coerced to false (D393). */
65860
65734
  running: boolean().nullable(),
65861
65735
  /** Epoch ms the current boolean began; null while `running` is null. */
65862
- since: number().nullable(),
65736
+ since: number().nullable().meta(readingMeta("none")),
65863
65737
  source: RunningStateSourceSchema.meta({ label: "Source" }),
65864
65738
  phase: string().nullable(),
65865
65739
  /** null until a reading has been published (D393). */
@@ -67547,9 +67421,9 @@ var RecordTriggerSchema = _enum([
67547
67421
  */
67548
67422
  var RecordWindowSchema = object({
67549
67423
  trigger: RecordTriggerSchema,
67550
- day: number().int().min(0).max(6),
67551
- startMinute: number().int().min(0).max(1439),
67552
- endMinute: number().int().min(1).max(1440)
67424
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
67425
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
67426
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
67553
67427
  });
67554
67428
  /** Status of one physical volume, as the camera itself describes it. */
67555
67429
  var OnboardStorageVolumeSchema = object({
@@ -67618,10 +67492,10 @@ object({
67618
67492
  /** Master "record to the card at all" switch. */
67619
67493
  enabled: boolean().nullable(),
67620
67494
  overwriteWhenFull: boolean().nullable(),
67621
- preRecordSec: number().nullable(),
67622
- postRecordSec: number().nullable(),
67495
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
67496
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
67623
67497
  /** Length of one recorded file, in minutes. */
67624
- segmentMinutes: number().nullable(),
67498
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
67625
67499
  /** The primary track's weekly windows, flattened. */
67626
67500
  windows: array(RecordWindowSchema),
67627
67501
  /**
@@ -67637,7 +67511,7 @@ object({
67637
67511
  * provider reporting a non-zero count also reports the schedule as not
67638
67512
  * writable.
67639
67513
  */
67640
- unreadableWindows: number(),
67514
+ unreadableWindows: number().meta(readingMeta("count")),
67641
67515
  /**
67642
67516
  * The camera is scheduled to record and has NO usable storage.
67643
67517
  *
@@ -68308,7 +68182,7 @@ var CamStreamDescriptorSchema = object({
68308
68182
  url: string().optional(),
68309
68183
  codec: string().optional(),
68310
68184
  resolution: CamStreamResolutionSchema.optional(),
68311
- fps: number().positive().optional(),
68185
+ fps: number().positive().optional().meta(readingMeta("rate")),
68312
68186
  label: string().optional(),
68313
68187
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
68314
68188
  deviceFeatures: array(string()).optional(),
@@ -68338,18 +68212,18 @@ var StreamProfileSchema = _enum([
68338
68212
  ]);
68339
68213
  /** The current encoding config of one stream profile. */
68340
68214
  var StreamProfileConfigSchema = object({
68341
- width: number(),
68342
- height: number(),
68215
+ width: number().meta(readingMeta("none")),
68216
+ height: number().meta(readingMeta("none")),
68343
68217
  codec: _enum(["h264", "h265"]),
68344
- framerate: number(),
68345
- bitrate: number(),
68218
+ framerate: number().meta(readingMeta("rate")),
68219
+ bitrate: number().meta(readingMeta("bitrate")),
68346
68220
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
68347
68221
  encoderProfile: _enum([
68348
68222
  "high",
68349
68223
  "main",
68350
68224
  "baseline"
68351
68225
  ]).optional(),
68352
- gop: number().optional(),
68226
+ gop: number().optional().meta(readingMeta("none")),
68353
68227
  /**
68354
68228
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
68355
68229
  *
@@ -68992,23 +68866,13 @@ object({
68992
68866
  });
68993
68867
  DeviceType.Sensor;
68994
68868
  object({
68995
- /** Current temperature in Celsius. */
68996
- celsius: number().meta({
68997
- unit: "°C",
68869
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
68870
+ celsius: number().nullable().meta(readingMeta("temperature", {
68871
+ precision: 1,
68998
68872
  label: "Temperature"
68999
- }),
68873
+ })),
69000
68874
  /** Ms epoch when the slice was last updated (push or poll). */
69001
- lastFetchedAt: number(),
69002
- /** Live display unit from the upstream source (e.g. HA
69003
- * `attributes.unit_of_measurement`). The UI prefers this over the
69004
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
69005
- * Absent → the UI falls back to the role's canonical unit. */
69006
- unit: string().optional(),
69007
- /** Suggested decimal places for numeric display.
69008
- * Populated live from the upstream source when provided (e.g. HA
69009
- * `attributes.suggested_display_precision`). Falls back to
69010
- * auto-formatting when absent. */
69011
- precision: number().int().min(0).max(10).optional()
68875
+ lastFetchedAt: number()
69012
68876
  });
69013
68877
  DeviceType.Sensor, DeviceType.Thermostat;
69014
68878
  /**
@@ -69260,16 +69124,16 @@ object({
69260
69124
  ]),
69261
69125
  /** 0..100 battery percentage. Null when the device has no battery
69262
69126
  * reading. */
69263
- batteryLevel: number().min(0).max(100).nullable(),
69127
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
69264
69128
  /** Current fan-speed token (provider-verbatim). Null when unknown or
69265
69129
  * the vacuum has no speed control. */
69266
69130
  fanSpeed: string().nullable(),
69267
69131
  /** Speed tokens the hardware accepts — drives the UI selector. */
69268
69132
  availableFanSpeeds: array(string()),
69269
69133
  /** 0..100 cleaning-completion percentage of the current task, or null. */
69270
- progressPercent: number().min(0).max(100).nullable(),
69134
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
69271
69135
  /** Current error code (0 / null = no error). */
69272
- errorCode: number().nullable(),
69136
+ errorCode: number().nullable().meta(readingMeta("none")),
69273
69137
  /** Human label for {@link errorCode}, or null when none / undecodable. */
69274
69138
  errorLabel: string().nullable(),
69275
69139
  /** Ms epoch when the slice was last updated. */
@@ -69308,7 +69172,7 @@ object({
69308
69172
  ]),
69309
69173
  /** 0 = fully closed, 100 = fully open. Null when the device has no
69310
69174
  * intermediate position surface. */
69311
- position: number().min(0).max(100).nullable(),
69175
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
69312
69176
  /** Ms epoch when the slice was last updated. */
69313
69177
  lastChangedAt: number()
69314
69178
  });
@@ -69336,9 +69200,9 @@ object({
69336
69200
  DeviceType.Sensor;
69337
69201
  object({
69338
69202
  /** Current measured temperature. Null when not reported. */
69339
- currentTemp: number().nullable(),
69203
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
69340
69204
  /** Target temperature setpoint. Null when no setpoint surface. */
69341
- targetTemp: number().nullable(),
69205
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
69342
69206
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
69343
69207
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
69344
69208
  * device reports an unknown state. */
@@ -69349,9 +69213,9 @@ object({
69349
69213
  * has no away surface. */
69350
69214
  away: boolean().nullable(),
69351
69215
  /** HA `min_temp` attribute. Null when not reported. */
69352
- minTemp: number().nullable(),
69216
+ minTemp: number().nullable().meta(readingMeta("temperature")),
69353
69217
  /** HA `max_temp` attribute. Null when not reported. */
69354
- maxTemp: number().nullable(),
69218
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
69355
69219
  /** Ms epoch when the slice was last updated. */
69356
69220
  lastChangedAt: number()
69357
69221
  });
@@ -69375,25 +69239,18 @@ DeviceType.WaterHeater, method(object({
69375
69239
  auth: "protected"
69376
69240
  });
69377
69241
  object({
69378
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
69379
- * when no condition has been reported yet. */
69242
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
69380
69243
  condition: string().nullable(),
69381
- /** Current temperature in the reported unit. Null when not provided. */
69382
- temperature: number().nullable(),
69383
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
69384
- temperatureUnit: string().nullable(),
69244
+ /** Current temperature in Celsius. Null when not provided or refused. */
69245
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
69385
69246
  /** Relative humidity (0..100). Null when not provided. */
69386
- humidity: number().min(0).max(100).nullable(),
69387
- /** Barometric pressure in the reported unit. Null when not provided. */
69388
- pressure: number().nullable(),
69389
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
69390
- pressureUnit: string().nullable(),
69391
- /** Wind speed in the reported unit. Null when not provided. */
69392
- windSpeed: number().nullable(),
69393
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
69394
- windSpeedUnit: string().nullable(),
69247
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
69248
+ /** Barometric pressure in hPa. Null when not provided or refused. */
69249
+ pressure: number().nullable().meta(readingMeta("pressure")),
69250
+ /** Wind speed in metres per second. Null when not provided or refused. */
69251
+ windSpeed: number().nullable().meta(readingMeta("speed")),
69395
69252
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
69396
- windBearing: number().nullable(),
69253
+ windBearing: number().nullable().meta(readingMeta("angle")),
69397
69254
  /** Ms epoch when the slice was last updated. */
69398
69255
  lastFetchedAt: number()
69399
69256
  });
@@ -83326,7 +83183,6 @@ var SENSOR_CAP_VOCABULARY = {
83326
83183
  field: "value",
83327
83184
  quantity: t("the reading", "il valore"),
83328
83185
  label: t("Reading", "Valore"),
83329
- unitField: "unit",
83330
83186
  icon: "gauge"
83331
83187
  }]
83332
83188
  },
@@ -83462,7 +83318,7 @@ var SENSOR_CAP_VOCABULARY = {
83462
83318
  field: "temperature",
83463
83319
  quantity: t("the outdoor temperature", "la temperatura esterna"),
83464
83320
  label: t("Temperature", "Temperatura"),
83465
- unitField: "temperatureUnit",
83321
+ unit: "°C",
83466
83322
  icon: "thermometer"
83467
83323
  },
83468
83324
  {
@@ -83478,7 +83334,7 @@ var SENSOR_CAP_VOCABULARY = {
83478
83334
  field: "windSpeed",
83479
83335
  quantity: t("the wind speed", "la velocità del vento"),
83480
83336
  label: t("Wind", "Vento"),
83481
- unitField: "windSpeedUnit",
83337
+ unit: "m/s",
83482
83338
  icon: "wind"
83483
83339
  },
83484
83340
  {
@@ -83486,7 +83342,7 @@ var SENSOR_CAP_VOCABULARY = {
83486
83342
  field: "pressure",
83487
83343
  quantity: t("the air pressure", "la pressione atmosferica"),
83488
83344
  label: t("Pressure", "Pressione"),
83489
- unitField: "pressureUnit",
83345
+ unit: "hPa",
83490
83346
  icon: "gauge"
83491
83347
  }
83492
83348
  ]
@@ -86623,6 +86479,11 @@ var PulseOverrideDraftSchema = object({
86623
86479
  targetId: string().min(1),
86624
86480
  enabled: boolean(),
86625
86481
  debounceMs: number().nonnegative(),
86482
+ edges: array(_enum([
86483
+ "rising",
86484
+ "button",
86485
+ "native-event"
86486
+ ])).min(1).optional(),
86626
86487
  sources: array(OverrideSourceSchema).min(1)
86627
86488
  });
86628
86489
  var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([