@camstack/addon-post-analysis 1.2.635 → 1.2.637

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 (15) hide show
  1. package/dist/{clip-model-registry-CySP4L5r.mjs → clip-model-registry-DF_J6DGP.mjs} +319 -495
  2. package/dist/{clip-model-registry-BHSAAWz-.js → clip-model-registry-GzUKh4GO.js} +319 -495
  3. package/dist/embedding-encoder/index.js +1 -1
  4. package/dist/embedding-encoder/index.mjs +1 -1
  5. package/dist/pipeline-analytics/_stub.js +2 -2
  6. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Cnd14w_u.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CD4U2vp3.mjs} +3 -3
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CLg3GhZM.mjs +26 -0
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Bcrs7B9G.mjs +26 -0
  9. package/dist/pipeline-analytics/{hostInit-DxMujleG.mjs → hostInit-Bdn1ny7s.mjs} +3 -3
  10. package/dist/pipeline-analytics/index.js +1 -1
  11. package/dist/pipeline-analytics/index.mjs +1 -1
  12. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  13. package/package.json +7 -2
  14. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-j6sD2jfA.mjs +0 -26
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-MOlfkem8.mjs +0 -26
@@ -5663,7 +5663,7 @@ var ZodIssueCode = {
5663
5663
  var ZodFirstPartyTypeKind;
5664
5664
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5665
5665
  //#endregion
5666
- //#region ../types/dist/sleep-BErAxhul.mjs
5666
+ //#region ../types/dist/sleep-CARd6wZF.mjs
5667
5667
  /**
5668
5668
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5669
5669
  * window to float samples (D455).
@@ -8363,6 +8363,52 @@ function defineRecordKinds(specs) {
8363
8363
  }
8364
8364
  return out;
8365
8365
  }
8366
+ var READING_QUANTITIES = [
8367
+ "temperature",
8368
+ "pressure",
8369
+ "speed",
8370
+ "precipitation",
8371
+ "precipitation-rate",
8372
+ "length",
8373
+ "area",
8374
+ "illuminance",
8375
+ "irradiance",
8376
+ "power",
8377
+ "energy",
8378
+ "voltage",
8379
+ "current",
8380
+ "apparent-power",
8381
+ "humidity",
8382
+ "percentage",
8383
+ "concentration-volume",
8384
+ "concentration-mass",
8385
+ "concentration-ppb",
8386
+ "volume",
8387
+ "duration",
8388
+ "duration-ms",
8389
+ "information",
8390
+ "sound-level",
8391
+ "signal-level",
8392
+ "colour-temperature",
8393
+ "angle",
8394
+ "bitrate",
8395
+ "rate",
8396
+ "mass",
8397
+ "index",
8398
+ "ratio",
8399
+ "count",
8400
+ "none",
8401
+ "device"
8402
+ ];
8403
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8404
+ new Set(READING_QUANTITIES);
8405
+ function readingMeta(quantity, extras) {
8406
+ return {
8407
+ quantity,
8408
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8409
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8410
+ };
8411
+ }
8366
8412
  /** Shared Zod schemas used across streaming capabilities. */
8367
8413
  var CamProfileSchema = _enum([
8368
8414
  "high",
@@ -8390,8 +8436,8 @@ var CamStreamKindSchema = _enum([
8390
8436
  "derived"
8391
8437
  ]);
8392
8438
  var CamStreamResolutionSchema = object({
8393
- width: number().int().positive(),
8394
- height: number().int().positive()
8439
+ width: number().int().positive().meta(readingMeta("none")),
8440
+ height: number().int().positive().meta(readingMeta("none"))
8395
8441
  });
8396
8442
  var CameraStreamSchema = object({
8397
8443
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -9120,14 +9166,6 @@ Object.freeze({
9120
9166
  {
9121
9167
  "path": "aqi",
9122
9168
  "valueType": "number"
9123
- },
9124
- {
9125
- "path": "unit",
9126
- "valueType": "string"
9127
- },
9128
- {
9129
- "path": "precision",
9130
- "valueType": "number"
9131
9169
  }
9132
9170
  ],
9133
9171
  "alarm-panel": [
@@ -9163,20 +9201,10 @@ Object.freeze({
9163
9201
  "valueType": "boolean"
9164
9202
  }
9165
9203
  ],
9166
- "ambient-light-sensor": [
9167
- {
9168
- "path": "lux",
9169
- "valueType": "number"
9170
- },
9171
- {
9172
- "path": "unit",
9173
- "valueType": "string"
9174
- },
9175
- {
9176
- "path": "precision",
9177
- "valueType": "number"
9178
- }
9179
- ],
9204
+ "ambient-light-sensor": [{
9205
+ "path": "lux",
9206
+ "valueType": "number"
9207
+ }],
9180
9208
  "audio-metrics": [
9181
9209
  {
9182
9210
  "path": "ts",
@@ -9256,6 +9284,10 @@ Object.freeze({
9256
9284
  "path": "lastUpdated",
9257
9285
  "valueType": "number"
9258
9286
  },
9287
+ {
9288
+ "path": "volts",
9289
+ "valueType": "number"
9290
+ },
9259
9291
  {
9260
9292
  "path": "lastContactAt",
9261
9293
  "valueType": "number"
@@ -9498,10 +9530,6 @@ Object.freeze({
9498
9530
  "datetime"
9499
9531
  ]
9500
9532
  },
9501
- {
9502
- "path": "unit",
9503
- "valueType": "string"
9504
- },
9505
9533
  {
9506
9534
  "path": "min",
9507
9535
  "valueType": "number"
@@ -9513,19 +9541,6 @@ Object.freeze({
9513
9541
  {
9514
9542
  "path": "step",
9515
9543
  "valueType": "number"
9516
- },
9517
- {
9518
- "path": "precision",
9519
- "valueType": "number"
9520
- },
9521
- {
9522
- "path": "format",
9523
- "valueType": "enum",
9524
- "enumValues": [
9525
- "date",
9526
- "time",
9527
- "datetime"
9528
- ]
9529
9544
  }
9530
9545
  ],
9531
9546
  "cover": [
@@ -9766,13 +9781,8 @@ Object.freeze({
9766
9781
  "path": "value",
9767
9782
  "valueType": "string"
9768
9783
  }, {
9769
- "path": "format",
9770
- "valueType": "enum",
9771
- "enumValues": [
9772
- "date",
9773
- "time",
9774
- "datetime"
9775
- ]
9784
+ "path": "timestamp",
9785
+ "valueType": "boolean"
9776
9786
  }],
9777
9787
  "event-emitter": [
9778
9788
  {
@@ -9879,20 +9889,10 @@ Object.freeze({
9879
9889
  "valueType": "number"
9880
9890
  }
9881
9891
  ],
9882
- "humidity-sensor": [
9883
- {
9884
- "path": "percent",
9885
- "valueType": "number"
9886
- },
9887
- {
9888
- "path": "unit",
9889
- "valueType": "string"
9890
- },
9891
- {
9892
- "path": "precision",
9893
- "valueType": "number"
9894
- }
9895
- ],
9892
+ "humidity-sensor": [{
9893
+ "path": "percent",
9894
+ "valueType": "number"
9895
+ }],
9896
9896
  "image": [{
9897
9897
  "path": "url",
9898
9898
  "valueType": "string"
@@ -10231,20 +10231,10 @@ Object.freeze({
10231
10231
  "valueType": "boolean"
10232
10232
  }
10233
10233
  ],
10234
- "numeric-sensor": [
10235
- {
10236
- "path": "value",
10237
- "valueType": "number"
10238
- },
10239
- {
10240
- "path": "unit",
10241
- "valueType": "string"
10242
- },
10243
- {
10244
- "path": "precision",
10245
- "valueType": "number"
10246
- }
10247
- ],
10234
+ "numeric-sensor": [{
10235
+ "path": "value",
10236
+ "valueType": "number"
10237
+ }],
10248
10238
  "pet-feeder": [
10249
10239
  {
10250
10240
  "path": "foodLevel",
@@ -10336,14 +10326,6 @@ Object.freeze({
10336
10326
  {
10337
10327
  "path": "powerFactor",
10338
10328
  "valueType": "number"
10339
- },
10340
- {
10341
- "path": "unit",
10342
- "valueType": "string"
10343
- },
10344
- {
10345
- "path": "precision",
10346
- "valueType": "number"
10347
10329
  }
10348
10330
  ],
10349
10331
  "presence": [
@@ -10372,20 +10354,10 @@ Object.freeze({
10372
10354
  "valueType": "number"
10373
10355
  }
10374
10356
  ],
10375
- "pressure-sensor": [
10376
- {
10377
- "path": "hpa",
10378
- "valueType": "number"
10379
- },
10380
- {
10381
- "path": "unit",
10382
- "valueType": "string"
10383
- },
10384
- {
10385
- "path": "precision",
10386
- "valueType": "number"
10387
- }
10388
- ],
10357
+ "pressure-sensor": [{
10358
+ "path": "hpa",
10359
+ "valueType": "number"
10360
+ }],
10389
10361
  "privacy-mask": [{
10390
10362
  "path": "enabled",
10391
10363
  "valueType": "boolean"
@@ -10696,20 +10668,10 @@ Object.freeze({
10696
10668
  "path": "tampered",
10697
10669
  "valueType": "boolean"
10698
10670
  }],
10699
- "temperature-sensor": [
10700
- {
10701
- "path": "celsius",
10702
- "valueType": "number"
10703
- },
10704
- {
10705
- "path": "unit",
10706
- "valueType": "string"
10707
- },
10708
- {
10709
- "path": "precision",
10710
- "valueType": "number"
10711
- }
10712
- ],
10671
+ "temperature-sensor": [{
10672
+ "path": "celsius",
10673
+ "valueType": "number"
10674
+ }],
10713
10675
  "update": [
10714
10676
  {
10715
10677
  "path": "currentVersion",
@@ -10820,10 +10782,6 @@ Object.freeze({
10820
10782
  "path": "temperature",
10821
10783
  "valueType": "number"
10822
10784
  },
10823
- {
10824
- "path": "temperatureUnit",
10825
- "valueType": "string"
10826
- },
10827
10785
  {
10828
10786
  "path": "humidity",
10829
10787
  "valueType": "number"
@@ -10832,18 +10790,10 @@ Object.freeze({
10832
10790
  "path": "pressure",
10833
10791
  "valueType": "number"
10834
10792
  },
10835
- {
10836
- "path": "pressureUnit",
10837
- "valueType": "string"
10838
- },
10839
10793
  {
10840
10794
  "path": "windSpeed",
10841
10795
  "valueType": "number"
10842
10796
  },
10843
- {
10844
- "path": "windSpeedUnit",
10845
- "valueType": "string"
10846
- },
10847
10797
  {
10848
10798
  "path": "windBearing",
10849
10799
  "valueType": "number"
@@ -19391,7 +19341,8 @@ var CompositionProblemSchema = object({
19391
19341
  "item-field-reserved",
19392
19342
  "target-missing",
19393
19343
  "target-unreadable",
19394
- "not-replaceable"
19344
+ "not-replaceable",
19345
+ "native-capability-replace"
19395
19346
  ]),
19396
19347
  path: string(),
19397
19348
  message: string()
@@ -19449,9 +19400,11 @@ var CompositionFieldPlanSchema = object({
19449
19400
  reads: array(CompositionFieldReadSchema)
19450
19401
  });
19451
19402
  /**
19452
- * What a feature does to its target (D663): `new` builds a capability on a new
19453
- * device; on an existing device it `add`s a capability the device lacks, or
19454
- * `replace`s named fields of one it has natively.
19403
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
19404
+ * new device; on an existing device it `add`s a capability the device lacks.
19405
+ * Replacing a capability the device already serves is refused
19406
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
19407
+ * wire for a claim the hub already holds.
19455
19408
  */
19456
19409
  var CompositionFeatureModeSchema = _enum([
19457
19410
  "new",
@@ -21344,12 +21297,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
21344
21297
  * providers detect renames by matching `uniqueId` and patch `id`
21345
21298
  * in-place so dispatch keeps working.
21346
21299
  *
21347
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
21348
- * `precision`) live in the cap STATUS SLICE so they flow live with every
21349
- * state push and are the single source of truth. The `device_class` tag is
21350
- * an HA-specific input that is consumed at the discovery boundary
21351
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
21352
- * stored after that point.
21300
+ * **Display unit and precision are NOT stored here.** They are a read-time
21301
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
21302
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
21303
+ * neither is repeated on each sample. The `device_class` tag is an
21304
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
21305
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
21353
21306
  *
21354
21307
  * The `raw` escape hatch carries provider-specific attributes that don't
21355
21308
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -21363,6 +21316,14 @@ var SourceInfoSchema = object({
21363
21316
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
21364
21317
  * Used to detect rename when `id` changes. */
21365
21318
  uniqueId: string().optional(),
21319
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
21320
+ quantity: ReadingQuantitySchema.optional(),
21321
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
21322
+ dateTimeShape: _enum([
21323
+ "date",
21324
+ "time",
21325
+ "datetime"
21326
+ ]).optional(),
21366
21327
  /** Provider-specific extras that don't fit the structured slots.
21367
21328
  * Whitelist what goes here per provider — do NOT dump entire HA
21368
21329
  * attribute blobs (would explode the meta JSON column). */
@@ -42390,7 +42351,8 @@ method(object({
42390
42351
  "enum"
42391
42352
  ]),
42392
42353
  enumValues: array(string()).optional(),
42393
- item: boolean().optional()
42354
+ item: boolean().optional(),
42355
+ quantity: ReadingQuantitySchema.optional()
42394
42356
  })).readonly(),
42395
42357
  itemArray: object({
42396
42358
  path: string(),
@@ -46457,8 +46419,8 @@ var MaskPolygonShapeSchema = object({
46457
46419
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
46458
46420
  var MaskGridShapeSchema = object({
46459
46421
  kind: literal("grid"),
46460
- gridWidth: number(),
46461
- gridHeight: number(),
46422
+ gridWidth: number().meta(readingMeta("none")),
46423
+ gridHeight: number().meta(readingMeta("none")),
46462
46424
  cells: array(boolean())
46463
46425
  });
46464
46426
  discriminatedUnion("kind", [
@@ -59784,27 +59746,17 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
59784
59746
  */
59785
59747
  var AirQualitySensorStatusSchema = object({
59786
59748
  /** Carbon dioxide concentration in ppm. */
59787
- co2Ppm: number().min(0).optional(),
59749
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
59788
59750
  /** Total volatile organic compounds in ppb. */
59789
- vocPpb: number().min(0).optional(),
59751
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
59790
59752
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
59791
- pm25: number().min(0).optional(),
59753
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
59792
59754
  /** Particulate matter ≤ 10 μm in µg/m³. */
59793
- pm10: number().min(0).optional(),
59755
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
59794
59756
  /** Composite AQI value (typically 0..500). */
59795
- aqi: number().optional(),
59757
+ aqi: number().optional().meta(readingMeta("index")),
59796
59758
  /** Ms epoch when the slice was last updated. */
59797
- lastFetchedAt: number(),
59798
- /** Live display unit of the single metric this slice carries (e.g. HA
59799
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
59800
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
59801
- * per slice is unambiguous. */
59802
- unit: string().optional(),
59803
- /** Suggested decimal places for numeric display.
59804
- * Populated live from the upstream source when provided (e.g. HA
59805
- * `attributes.suggested_display_precision`). Falls back to
59806
- * auto-formatting when absent. */
59807
- precision: number().int().min(0).max(10).optional()
59759
+ lastFetchedAt: number()
59808
59760
  });
59809
59761
  var airQualitySensorCapability = {
59810
59762
  name: "air-quality-sensor",
@@ -59841,12 +59793,12 @@ var airQualitySensorCapability = {
59841
59793
  },
59842
59794
  pm25: {
59843
59795
  kind: "gauge",
59844
- unit: "ug/m3",
59796
+ unit: "µg/m³",
59845
59797
  deadband: 1
59846
59798
  },
59847
59799
  pm10: {
59848
59800
  kind: "gauge",
59849
- unit: "ug/m3",
59801
+ unit: "µg/m³",
59850
59802
  deadband: 1
59851
59803
  },
59852
59804
  aqi: {
@@ -59864,22 +59816,13 @@ var airQualitySensorCapability = {
59864
59816
  * entries with `device_class: illuminance`.
59865
59817
  */
59866
59818
  var AmbientLightSensorStatusSchema = object({
59867
- /** Current illuminance in lux (lx). */
59868
- lux: number().min(0).meta({
59869
- unit: "lx",
59819
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
59820
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
59821
+ precision: 0,
59870
59822
  label: "Illuminance"
59871
- }),
59823
+ })),
59872
59824
  /** Ms epoch when the slice was last updated. */
59873
- lastFetchedAt: number(),
59874
- /** Live display unit from the upstream source (e.g. HA
59875
- * `attributes.unit_of_measurement`). The UI prefers this over the
59876
- * role's canonical unit. Absent → fall back to the canonical unit. */
59877
- unit: string().optional(),
59878
- /** Suggested decimal places for numeric display.
59879
- * Populated live from the upstream source when provided (e.g. HA
59880
- * `attributes.suggested_display_precision`). Falls back to
59881
- * auto-formatting when absent. */
59882
- precision: number().int().min(0).max(10).optional()
59825
+ lastFetchedAt: number()
59883
59826
  });
59884
59827
  var ambientLightSensorCapability = {
59885
59828
  name: "ambient-light-sensor",
@@ -59911,12 +59854,7 @@ var ambientLightSensorCapability = {
59911
59854
  } } },
59912
59855
  /** Clock fields: written, but excluded from the compare that decides
59913
59856
  * whether persisting is worth a SQLite commit. */
59914
- volatileStateFields: ["lastFetchedAt"],
59915
- customize: {
59916
- version: 1,
59917
- advanced: "derive",
59918
- targets: []
59919
- }
59857
+ volatileStateFields: ["lastFetchedAt"]
59920
59858
  };
59921
59859
  /**
59922
59860
  * Per-class audio metrics aggregated over a sliding window.
@@ -60143,10 +60081,7 @@ var BatteryStatusSchema = object({
60143
60081
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
60144
60082
  * declines to band a non-finite reading, which is the correct shape.
60145
60083
  */
60146
- percentage: number().min(0).max(100).nullable().meta({
60147
- unit: "%",
60148
- label: "Charge"
60149
- }),
60084
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
60150
60085
  /**
60151
60086
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
60152
60087
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -60173,7 +60108,14 @@ var BatteryStatusSchema = object({
60173
60108
  */
60174
60109
  sleeping: boolean().meta({ label: "Sleeping" }),
60175
60110
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60176
- lastUpdated: number(),
60111
+ lastUpdated: number().meta(readingMeta("none")),
60112
+ /**
60113
+ * Battery voltage in volts, for a cell the firmware reports as voltage
60114
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
60115
+ * that only report charge. A second reading on this cap, not a second
60116
+ * `numeric-sensor` on the same device.
60117
+ */
60118
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
60177
60119
  /**
60178
60120
  * Ms epoch of the last time the device PROVED it was reachable — a
60179
60121
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -60225,7 +60167,6 @@ var BATTERY_DEVICE_TYPES = [
60225
60167
  /** Generic Customize target for the percentage reading (D936). */
60226
60168
  var batteryCustomize = {
60227
60169
  version: 1,
60228
- advanced: "derive",
60229
60170
  targets: [{
60230
60171
  id: "percentage",
60231
60172
  cardinality: "scalar",
@@ -60234,7 +60175,7 @@ var batteryCustomize = {
60234
60175
  stateField: "percentage"
60235
60176
  },
60236
60177
  deviceTypes: BATTERY_DEVICE_TYPES,
60237
- apply: "add-or-replace",
60178
+ apply: "add",
60238
60179
  path: "percentage",
60239
60180
  sources: [{
60240
60181
  kind: "field",
@@ -60355,10 +60296,16 @@ onStatusChanged: { data: object({
60355
60296
  /**
60356
60297
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
60357
60298
  */
60358
- history: { fields: { percentage: {
60359
- kind: "gauge",
60360
- unit: "%"
60361
- } } },
60299
+ history: { fields: {
60300
+ percentage: {
60301
+ kind: "gauge",
60302
+ unit: "%"
60303
+ },
60304
+ volts: {
60305
+ kind: "gauge",
60306
+ unit: "V"
60307
+ }
60308
+ } },
60362
60309
  customize: batteryCustomize,
60363
60310
  /** Clock fields: written, but excluded from the compare that decides
60364
60311
  * whether persisting is worth a SQLite commit. */
@@ -60405,12 +60352,7 @@ var binaryCapability = {
60405
60352
  history: { fields: { on: { kind: "boolean" } } },
60406
60353
  /** Clock fields: written, but excluded from the compare that decides
60407
60354
  * whether persisting is worth a SQLite commit. */
60408
- volatileStateFields: ["lastChangedAt"],
60409
- customize: {
60410
- version: 1,
60411
- advanced: "derive",
60412
- targets: []
60413
- }
60355
+ volatileStateFields: ["lastChangedAt"]
60414
60356
  };
60415
60357
  /**
60416
60358
  * Dimmable-light brightness control. Co-exists with `switch` on the
@@ -60426,7 +60368,7 @@ var binaryCapability = {
60426
60368
  */
60427
60369
  var BrightnessStatusSchema = object({
60428
60370
  /** Current level as 0..100 inclusive. Firmware-reported. */
60429
- percentage: number().min(0).max(100),
60371
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
60430
60372
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
60431
60373
  lastChangedAt: number()
60432
60374
  });
@@ -60907,21 +60849,21 @@ var ClimateControlStatusSchema = object({
60907
60849
  /** Single setpoint in Celsius. Used by single-target modes
60908
60850
  * (heat / cool / dry). Null when the device is in a range mode
60909
60851
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
60910
- target: number().nullable(),
60852
+ target: number().nullable().meta(readingMeta("temperature")),
60911
60853
  /** Upper bound of the dual setpoint in Celsius. Populated only
60912
60854
  * in `heat_cool` mode. */
60913
- targetHigh: number().nullable(),
60855
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
60914
60856
  /** Lower bound of the dual setpoint in Celsius. Populated only
60915
60857
  * in `heat_cool` mode. */
60916
- targetLow: number().nullable(),
60858
+ targetLow: number().nullable().meta(readingMeta("temperature")),
60917
60859
  /** Target relative humidity (0..100). Null when the device has no
60918
60860
  * humidity control. */
60919
- targetHumidity: number().min(0).max(100).nullable(),
60861
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
60920
60862
  /** Current measured temperature in Celsius. Read-only — pushed by
60921
60863
  * the device's internal sensor. */
60922
- currentTemp: number().nullable(),
60864
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
60923
60865
  /** Current measured relative humidity (0..100). Read-only. */
60924
- currentHumidity: number().min(0).max(100).nullable(),
60866
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
60925
60867
  /** Vertical louver swing on/off. Null when the device has no vertical
60926
60868
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
60927
60869
  swingVertical: boolean().nullable(),
@@ -60929,11 +60871,11 @@ var ClimateControlStatusSchema = object({
60929
60871
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
60930
60872
  swingHorizontal: boolean().nullable(),
60931
60873
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
60932
- minTemp: number().nullable().optional(),
60874
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
60933
60875
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
60934
- maxTemp: number().nullable().optional(),
60876
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
60935
60877
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
60936
- tempStep: number().positive().nullable().optional(),
60878
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
60937
60879
  /** Ms epoch when the slice was last updated (push or command). */
60938
60880
  lastFetchedAt: number()
60939
60881
  });
@@ -61075,17 +61017,17 @@ var climateControlCapability = {
61075
61017
  * modes to expose in the picker without a slice fetch.
61076
61018
  */
61077
61019
  var RgbTripletSchema = object({
61078
- r: number().int().min(0).max(255),
61079
- g: number().int().min(0).max(255),
61080
- b: number().int().min(0).max(255)
61020
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
61021
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
61022
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
61081
61023
  });
61082
61024
  var HsvTripletSchema = object({
61083
61025
  /** Hue in degrees, 0..360. */
61084
- h: number().min(0).max(360),
61026
+ h: number().min(0).max(360).meta(readingMeta("none")),
61085
61027
  /** Saturation as 0..100 inclusive. */
61086
- s: number().min(0).max(100),
61028
+ s: number().min(0).max(100).meta(readingMeta("none")),
61087
61029
  /** Value/brightness as 0..100 inclusive. */
61088
- v: number().min(0).max(100)
61030
+ v: number().min(0).max(100).meta(readingMeta("none"))
61089
61031
  });
61090
61032
  var ColorInputSchema = discriminatedUnion("mode", [
61091
61033
  object({
@@ -61114,7 +61056,7 @@ var ColorStatusSchema = object({
61114
61056
  /** Populated when `mode === 'hsv'`. */
61115
61057
  hsv: HsvTripletSchema.optional(),
61116
61058
  /** Populated when `mode === 'mired'`. */
61117
- mireds: number().int().optional(),
61059
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
61118
61060
  /** Ms epoch of the last operator-driven change. */
61119
61061
  lastChangedAt: number()
61120
61062
  });
@@ -61323,18 +61265,12 @@ var FORM_OF_KIND = {
61323
61265
  function consumableFormOf(item) {
61324
61266
  return item.form ?? FORM_OF_KIND[consumableKindOf(item)];
61325
61267
  }
61326
- /** Units a remaining-life counter is reported in. Closed: the panel renders
61327
- * each. */
61328
- var ConsumableRemainingUnitSchema = _enum([
61329
- "days",
61330
- "hours",
61331
- "cycles"
61332
- ]);
61333
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
61334
- * days). `value` null = unknown (D393). */
61268
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
61269
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
61270
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
61335
61271
  var ConsumableRemainingSchema = object({
61336
- value: number().min(0).nullable(),
61337
- unit: ConsumableRemainingUnitSchema
61272
+ value: number().min(0).nullable().meta(readingMeta("device")),
61273
+ kind: ConsumableRemainingKindSchema
61338
61274
  });
61339
61275
  /** A single consumable item. Either a continuous `level` (remaining
61340
61276
  * life %, or fill % for a `supply` / `waste` item) or a discrete `status`
@@ -61347,7 +61283,7 @@ var ConsumableItemSchema = object({
61347
61283
  /** Display name. */
61348
61284
  label: string().min(1),
61349
61285
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
61350
- level: number().min(0).max(100).nullable(),
61286
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61351
61287
  /** The provider's discrete verdict when known (binary mode). */
61352
61288
  status: ConsumableStatusSchema.nullable(),
61353
61289
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -61429,8 +61365,8 @@ var consumablesCustomize = {
61429
61365
  transforms: [{ kind: "identity" }]
61430
61366
  }],
61431
61367
  literals: [{
61432
- path: "remaining.unit",
61433
- values: [...ConsumableRemainingUnitSchema.options]
61368
+ path: "remaining.kind",
61369
+ values: [...ConsumableRemainingKindSchema.options]
61434
61370
  }]
61435
61371
  },
61436
61372
  {
@@ -61559,7 +61495,6 @@ reset: method(object({
61559
61495
  },
61560
61496
  "remaining.value": {
61561
61497
  kind: "gauge",
61562
- unitFrom: "remaining.unit",
61563
61498
  deadband: 1,
61564
61499
  displayName: "Remaining"
61565
61500
  }
@@ -61629,10 +61564,9 @@ var contactCapability = {
61629
61564
  * (empty array for the other kinds). It lives in the slice because
61630
61565
  * HA `select.*` entities may update their options at runtime.
61631
61566
  *
61632
- * The `unit`/`min`/`max`/`step` fields are meaningful for
61633
- * `kind === 'numeric'` and are absent (undefined) for all other
61634
- * kinds. They are populated live from HA attributes on each state
61635
- * push — consistent with `options` and the sensor-unit approach.
61567
+ * Numeric `min` / `max` / `step` live in the slice and share the device
61568
+ * quantity (`SourceInfo.quantity`, D944). Display precision is a read-time
61569
+ * preference. A datetime control's shape is `SourceInfo.dateTimeShape`.
61636
61570
  */
61637
61571
  var ControlStatusSchema = object({
61638
61572
  kind: _enum([
@@ -61644,39 +61578,17 @@ var ControlStatusSchema = object({
61644
61578
  /** Current value. Numeric kind → number; select/text/datetime → string.
61645
61579
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
61646
61580
  * or time-only — HA accepts all three forms). */
61647
- value: union([number(), string()]),
61581
+ value: union([number().meta(readingMeta("device")), string()]),
61648
61582
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
61649
61583
  options: array(string()),
61650
61584
  /** Ms epoch when the slice was last updated. */
61651
61585
  lastChangedAt: number(),
61652
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
61653
- * Populated live from HA `attributes.unit_of_measurement` on each push.
61654
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61655
- unit: string().optional(),
61656
- /** Minimum allowed value. Populated live from HA `attributes.min`.
61657
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61658
- min: number().optional(),
61659
- /** Maximum allowed value. Populated live from HA `attributes.max`.
61660
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61661
- max: number().optional(),
61662
- /** Step increment. Populated live from HA `attributes.step`.
61663
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61664
- step: number().optional(),
61665
- /** Suggested decimal places for numeric display. Populated live from HA
61666
- * `attributes.suggested_display_precision`, or derived from `step` when the
61667
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
61668
- * other kinds. Falls back to auto-formatting when absent. */
61669
- precision: number().int().min(0).max(10).optional(),
61670
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
61671
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
61672
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
61673
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
61674
- * `has_date`/`has_time` attributes on each push. */
61675
- format: _enum([
61676
- "date",
61677
- "time",
61678
- "datetime"
61679
- ]).optional()
61586
+ /** Minimum allowed value, in the device quantity's base unit. */
61587
+ min: number().optional().meta(readingMeta("device")),
61588
+ /** Maximum allowed value, in the device quantity's base unit. */
61589
+ max: number().optional().meta(readingMeta("device")),
61590
+ /** Step increment, in the device quantity's base unit. */
61591
+ step: number().optional().meta(readingMeta("device"))
61680
61592
  });
61681
61593
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
61682
61594
  object({
@@ -61764,9 +61676,9 @@ var CoverStatusSchema = object({
61764
61676
  state: CoverStateSchema,
61765
61677
  /** 0 = fully closed, 100 = fully open. Null when the device has
61766
61678
  * no intermediate position surface. */
61767
- position: number().min(0).max(100).nullable(),
61679
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61768
61680
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
61769
- tiltPosition: number().min(0).max(100).nullable(),
61681
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61770
61682
  /** Ms epoch when the slice was last updated. */
61771
61683
  lastChangedAt: number()
61772
61684
  });
@@ -61869,9 +61781,9 @@ var NormalizedRangeSchema$1 = object({
61869
61781
  var DayNightStatusSchema = object({
61870
61782
  mode: DayNightModeSchema,
61871
61783
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
61872
- sensitivity: number().optional(),
61784
+ sensitivity: number().optional().meta(readingMeta("none")),
61873
61785
  /** Delay before the IR-cut filter flips, in seconds. */
61874
- switchDelaySec: number().optional(),
61786
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
61875
61787
  lastFetchedAt: number()
61876
61788
  });
61877
61789
  /**
@@ -62089,11 +62001,11 @@ var DishwasherStatusSchema = object({
62089
62001
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
62090
62002
  availablePrograms: array(string()),
62091
62003
  /** Seconds remaining on the active cycle, or null when not running / not known. */
62092
- remainingSeconds: number().int().nonnegative().nullable(),
62004
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62093
62005
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
62094
- delayedStartSeconds: number().int().nonnegative().nullable(),
62006
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62095
62007
  /** Cycle completion 0..100, or null when not applicable. */
62096
- progressPercent: number().min(0).max(100).nullable(),
62008
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62097
62009
  /** True when the door is reported open; null when the door state is not known. */
62098
62010
  doorOpen: boolean().nullable(),
62099
62011
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -62117,7 +62029,7 @@ var DishwasherStatusSchema = object({
62117
62029
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
62118
62030
  machineCareDue: boolean().nullable(),
62119
62031
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
62120
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
62032
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
62121
62033
  /** Option states: null when the appliance does not expose the option or has not said. */
62122
62034
  halfLoad: boolean().nullable(),
62123
62035
  hygienePlus: boolean().nullable(),
@@ -62132,7 +62044,7 @@ var DishwasherStatusSchema = object({
62132
62044
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
62133
62045
  activeEvents: array(string()),
62134
62046
  /** Numeric error code when the appliance reports one; null otherwise. */
62135
- errorCode: number().nullable(),
62047
+ errorCode: number().nullable().meta(readingMeta("none")),
62136
62048
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
62137
62049
  errorLabel: string().nullable(),
62138
62050
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -62344,6 +62256,7 @@ var doorbellCustomize = {
62344
62256
  applyAuthority: { kind: "composition-claim" },
62345
62257
  eventName: "onPressed",
62346
62258
  edges: ["rising"],
62259
+ defaultEdge: "rising",
62347
62260
  timestampPath: "lastPressedAt",
62348
62261
  counterPath: "pressCountSinceStart",
62349
62262
  highWater: "composer-advance",
@@ -62362,7 +62275,7 @@ var DoorbellStatusSchema = object({
62362
62275
  /** Ms epoch of the last press. null = never observed since this provider started. */
62363
62276
  lastPressedAt: number().nullable(),
62364
62277
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
62365
- pressCountSinceStart: number()
62278
+ pressCountSinceStart: number().meta(readingMeta("count"))
62366
62279
  });
62367
62280
  var DoorbellPressEventSchema = object({
62368
62281
  deviceId: number(),
@@ -62427,17 +62340,7 @@ onPressed: {
62427
62340
  history: "none",
62428
62341
  customize: doorbellCustomize
62429
62342
  };
62430
- /**
62431
- * Enum-state sensor — a string value picked from a finite option set.
62432
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
62433
- * states, weather conditions, contact-source channels, mode strings).
62434
- * The option set is stable across observations; it is persisted in the
62435
- * device config blob (via the control slice at adoption) and is NOT
62436
- * stored in `sourceInfo`.
62437
- */
62438
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
62439
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
62440
- var EnumSensorDateTimeFormatSchema = _enum([
62343
+ _enum([
62441
62344
  "date",
62442
62345
  "time",
62443
62346
  "datetime"
@@ -62445,12 +62348,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
62445
62348
  var EnumSensorStatusSchema = object({
62446
62349
  value: string(),
62447
62350
  /**
62448
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
62449
- * as a locale date/time/datetime; absent for plain enum sensors. HA
62450
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
62451
- * a time-only sensor → `'time'`.
62351
+ * Set when `value` is a timestamp rather than an enum state, so history
62352
+ * does not learn ISO strings as states. The display shape (date / time /
62353
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
62452
62354
  */
62453
- format: EnumSensorDateTimeFormatSchema.optional(),
62355
+ timestamp: boolean().optional(),
62454
62356
  /** Ms epoch when the slice was last updated. */
62455
62357
  lastFetchedAt: number()
62456
62358
  });
@@ -62477,11 +62379,11 @@ var enumSensorCapability = {
62477
62379
  * History (D850): the value as an `enum` series (an appliance's program or
62478
62380
  * operation state, an HVAC action). The option set is per device, so the
62479
62381
  * value list is LEARNED as values are first seen (bounded). A sensor with a
62480
- * `format` carries a timestamp, not a state, and is not recorded.
62382
+ * `timestamp` carries a clock, not a state, and is not recorded.
62481
62383
  */
62482
62384
  history: { fields: { value: {
62483
62385
  kind: "enum",
62484
- skipWhenSet: "format"
62386
+ skipWhenSet: "timestamp"
62485
62387
  } } },
62486
62388
  /** Clock fields: written, but excluded from the compare that decides
62487
62389
  * whether persisting is worth a SQLite commit. */
@@ -62501,13 +62403,13 @@ var EventFireSchema = object({
62501
62403
  deviceId: number(),
62502
62404
  eventType: string(),
62503
62405
  data: record(string(), unknown()).nullable(),
62504
- timestamp: number(),
62505
- seq: number()
62406
+ timestamp: number().meta(readingMeta("none")),
62407
+ seq: number().meta(readingMeta("count"))
62506
62408
  });
62507
62409
  var EventEmitterStatusSchema = object({
62508
62410
  eventTypes: array(string()),
62509
62411
  lastEvent: EventFireSchema.nullable(),
62510
- eventCountSinceStart: number()
62412
+ eventCountSinceStart: number().meta(readingMeta("count"))
62511
62413
  });
62512
62414
  var eventEmitterCapability = {
62513
62415
  name: "event-emitter",
@@ -62960,10 +62862,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
62960
62862
  var FanControlStatusSchema = object({
62961
62863
  /** Active speed as 0..100 inclusive. Null when the device has no
62962
62864
  * speed surface (single-speed fan). */
62963
- percentage: number().min(0).max(100).nullable(),
62865
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62964
62866
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
62965
62867
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
62966
- percentageStep: number().positive().optional(),
62868
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
62967
62869
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
62968
62870
  * — empty string when no preset is active or supported. */
62969
62871
  preset: string(),
@@ -63084,7 +62986,7 @@ var FeatureProbeStatusSchema = object({
63084
62986
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
63085
62987
  model: string().nullable(),
63086
62988
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
63087
- channelCount: number().nullable(),
62989
+ channelCount: number().nullable().meta(readingMeta("count")),
63088
62990
  /**
63089
62991
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
63090
62992
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -63209,7 +63111,7 @@ var gasCapability = {
63209
63111
  };
63210
63112
  var HeatingValveStatusSchema = object({
63211
63113
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
63212
- position: number().min(0).max(100).nullable(),
63114
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63213
63115
  /** Ms epoch when the slice was last updated (push or poll). */
63214
63116
  lastFetchedAt: number()
63215
63117
  });
@@ -63252,9 +63154,9 @@ var HumidifierStatusSchema = object({
63252
63154
  /** Whether the humidifier is currently on. */
63253
63155
  on: boolean(),
63254
63156
  /** Current measured relative humidity (0..100). Null when not reported. */
63255
- currentHumidity: number().min(0).max(100).nullable(),
63157
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
63256
63158
  /** Target relative humidity (0..100). Null when no setpoint surface. */
63257
- targetHumidity: number().min(0).max(100).nullable(),
63159
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
63258
63160
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
63259
63161
  * device has no mode surface. */
63260
63162
  mode: string().nullable(),
@@ -63264,9 +63166,9 @@ var HumidifierStatusSchema = object({
63264
63166
  * `idle` / `off`). Null when the device doesn't report it. */
63265
63167
  action: string().nullable(),
63266
63168
  /** HA `min_humidity` attribute. Null → UI uses 0. */
63267
- minHumidity: number().nullable(),
63169
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
63268
63170
  /** HA `max_humidity` attribute. Null → UI uses 100. */
63269
- maxHumidity: number().nullable(),
63171
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
63270
63172
  /** Ms epoch when the slice was last updated. */
63271
63173
  lastChangedAt: number()
63272
63174
  });
@@ -63336,22 +63238,13 @@ var humidifierCapability = {
63336
63238
  * `sourceInfo`.
63337
63239
  */
63338
63240
  var HumiditySensorStatusSchema = object({
63339
- /** Current relative humidity, 0..100. */
63340
- percent: number().min(0).max(100).meta({
63341
- unit: "%",
63241
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
63242
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63243
+ precision: 0,
63342
63244
  label: "Humidity"
63343
- }),
63245
+ })),
63344
63246
  /** Ms epoch when the slice was last updated. */
63345
- lastFetchedAt: number(),
63346
- /** Live display unit from the upstream source (e.g. HA
63347
- * `attributes.unit_of_measurement`). The UI prefers this over the
63348
- * role's canonical unit. Absent → fall back to the canonical unit. */
63349
- unit: string().optional(),
63350
- /** Suggested decimal places for numeric display.
63351
- * Populated live from the upstream source when provided (e.g. HA
63352
- * `attributes.suggested_display_precision`). Falls back to
63353
- * auto-formatting when absent. */
63354
- precision: number().int().min(0).max(10).optional()
63247
+ lastFetchedAt: number()
63355
63248
  });
63356
63249
  var humiditySensorCapability = {
63357
63250
  name: "humidity-sensor",
@@ -63382,12 +63275,7 @@ var humiditySensorCapability = {
63382
63275
  } } },
63383
63276
  /** Clock fields: written, but excluded from the compare that decides
63384
63277
  * whether persisting is worth a SQLite commit. */
63385
- volatileStateFields: ["lastFetchedAt"],
63386
- customize: {
63387
- version: 1,
63388
- advanced: "derive",
63389
- targets: []
63390
- }
63278
+ volatileStateFields: ["lastFetchedAt"]
63391
63279
  };
63392
63280
  /**
63393
63281
  * Image display cap. Models a single still image exposed by an integration —
@@ -63408,7 +63296,7 @@ var ImageStatusSchema = object({
63408
63296
  * entity exposes no `entity_picture` (yet). */
63409
63297
  url: string().nullable(),
63410
63298
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
63411
- lastUpdated: number().nullable()
63299
+ lastUpdated: number().nullable().meta(readingMeta("none"))
63412
63300
  });
63413
63301
  var imageCapability = {
63414
63302
  name: "image",
@@ -63496,19 +63384,19 @@ var NormalizedRangeSchema = object({
63496
63384
  */
63497
63385
  var ImageSettingsStatusSchema = object({
63498
63386
  /** Normalized 0–100. */
63499
- brightness: number().optional(),
63387
+ brightness: number().optional().meta(readingMeta("none")),
63500
63388
  /** Normalized 0–100. */
63501
- contrast: number().optional(),
63389
+ contrast: number().optional().meta(readingMeta("none")),
63502
63390
  /** Normalized 0–100. */
63503
- saturation: number().optional(),
63391
+ saturation: number().optional().meta(readingMeta("none")),
63504
63392
  /** Normalized 0–100. */
63505
- sharpness: number().optional(),
63393
+ sharpness: number().optional().meta(readingMeta("none")),
63506
63394
  mirror: boolean().optional(),
63507
63395
  flip: boolean().optional(),
63508
63396
  rotate: ImageRotateSchema.optional(),
63509
63397
  whiteBalance: WhiteBalanceModeSchema.optional(),
63510
63398
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
63511
- warmth: number().optional(),
63399
+ warmth: number().optional().meta(readingMeta("none")),
63512
63400
  exposureMode: ExposureModeSchema.optional(),
63513
63401
  backlightMode: BacklightModeSchema.optional(),
63514
63402
  lastFetchedAt: number()
@@ -63787,10 +63675,10 @@ var IntercomAbilitySchema = object({
63787
63675
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
63788
63676
  codecs: array(string()),
63789
63677
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
63790
- sampleRate: number(),
63678
+ sampleRate: number().meta(readingMeta("none")),
63791
63679
  duplex: _enum(["half", "full"]),
63792
63680
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
63793
- maxBacklogMs: number()
63681
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
63794
63682
  });
63795
63683
  var IntercomStatusSchema = object({
63796
63684
  /** True while a talk session is open. */
@@ -64017,13 +63905,13 @@ var LawnMowerControlStatusSchema = object({
64017
63905
  activity: LawnMowerActivitySchema,
64018
63906
  /** 0..100 battery percentage. Null when the device has no battery
64019
63907
  * reading. */
64020
- batteryLevel: number().min(0).max(100).nullable(),
63908
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64021
63909
  /** 0..100 mowing-completion percentage of the current task, or null when no
64022
63910
  * task is active / progress is unavailable. */
64023
- progressPercent: number().min(0).max(100).nullable(),
63911
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64024
63912
  /** Current device/event code (dynamic — mostly status, sometimes an error),
64025
63913
  * or null when unknown. */
64026
- currentCode: number().nullable(),
63914
+ currentCode: number().nullable().meta(readingMeta("none")),
64027
63915
  /** Human label for {@link currentCode}, or null when undecodable. */
64028
63916
  currentCodeLabel: string().nullable(),
64029
63917
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -64577,7 +64465,7 @@ var MediaPlayerStatusSchema = object({
64577
64465
  state: MediaPlayerStateSchema,
64578
64466
  /** Volume as 0..100 inclusive. Null when the device has no volume
64579
64467
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
64580
- volumeLevel: number().min(0).max(100).nullable(),
64468
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64581
64469
  /** Mute toggle distinct from volume=0. Null when no mute surface.
64582
64470
  * Pair with `DeviceFeature.MediaPlayerMute`. */
64583
64471
  isMuted: boolean().nullable(),
@@ -64590,10 +64478,10 @@ var MediaPlayerStatusSchema = object({
64590
64478
  currentMedia: MediaInfoSchema.nullable(),
64591
64479
  /** Current playback position in ms. Null when not seekable or
64592
64480
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
64593
- positionMs: number().int().nonnegative().nullable(),
64481
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64594
64482
  /** Total duration of the current media in ms. Null when unknown
64595
64483
  * (live stream) or nothing is playing. */
64596
- durationMs: number().int().nonnegative().nullable(),
64484
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64597
64485
  /** Shuffle toggle. Null when no shuffle surface.
64598
64486
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
64599
64487
  shuffle: boolean().nullable(),
@@ -64902,7 +64790,7 @@ var motionCustomize = {
64902
64790
  eventName: "onMotionChanged"
64903
64791
  },
64904
64792
  deviceTypes: [DeviceType.Camera],
64905
- apply: "replace",
64793
+ apply: "add",
64906
64794
  applyAuthority: {
64907
64795
  kind: "camera-sensors",
64908
64796
  timeoutField: "motionCooldownMs"
@@ -64999,7 +64887,7 @@ var MotionStatusSchema = object({
64999
64887
  * it, every provider seeded it at boot, and every session left it
65000
64888
  * `detected: true` after its detach.
65001
64889
  */
65002
- autoClearAfterMs: number().nullable()
64890
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
65003
64891
  });
65004
64892
  /**
65005
64893
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -65181,7 +65069,7 @@ var MotionZoneRegionSchema = object({
65181
65069
  * the grid region(s). */
65182
65070
  var MotionZoneStatusSchema = object({
65183
65071
  enabled: boolean(),
65184
- sensitivity: number(),
65072
+ sensitivity: number().meta(readingMeta("none")),
65185
65073
  /** Grid region(s). Today exactly one `grid` shape. */
65186
65074
  regions: array(MotionZoneRegionSchema),
65187
65075
  lastFetchedAt: number()
@@ -65551,7 +65439,7 @@ var NavigationStatusSchema = object({
65551
65439
  following: boolean(),
65552
65440
  flash: boolean(),
65553
65441
  lightMode: NavigationLightModeSchema,
65554
- lightLevel: number().min(40).max(100),
65442
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
65555
65443
  /** Ms epoch when the slice was last updated. */
65556
65444
  lastChangedAt: number()
65557
65445
  });
@@ -65685,13 +65573,13 @@ var NetworkLinkStatusSchema = object({
65685
65573
  * one whose reading has not landed must not be drawn at 0 %. Consumers
65686
65574
  * SKIP a null rather than coerce it.
65687
65575
  */
65688
- signalPercent: number().min(0).max(100).nullable(),
65576
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65689
65577
  /** Raw received signal strength in dBm, when the firmware reports one. */
65690
- rssiDbm: number().optional(),
65578
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
65691
65579
  /** Network name of a wireless link, when the firmware reports it. */
65692
65580
  ssid: string().optional(),
65693
65581
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
65694
- lastUpdated: number()
65582
+ lastUpdated: number().meta(readingMeta("none"))
65695
65583
  });
65696
65584
  var networkLinkCapability = {
65697
65585
  name: "network-link",
@@ -66076,7 +65964,7 @@ var NotifierStatusSchema = object({
66076
65964
  lastError: string().nullable(),
66077
65965
  /** Number of deliveries currently buffered server-side (rate-limited
66078
65966
  * notifiers may queue). 0 when the provider sends synchronously. */
66079
- queueDepth: number().int().nonnegative(),
65967
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
66080
65968
  /** Per-feature capability matrix — authoritative source for the
66081
65969
  * compose-form field gating. */
66082
65970
  supports: NotifierSupportsSchema
@@ -66168,11 +66056,9 @@ onSent: { data: object({
66168
66056
  * cap fits (HA `sensor` whose `device_class` we don't have a typed cap
66169
66057
  * for: water flow, distance, weight, frequency, signal strength, …).
66170
66058
  *
66171
- * The `unit` and `precision` fields carry the live display metadata that
66172
- * the upstream source provides with each state push (HA
66173
- * `attributes.unit_of_measurement` / `attributes.suggested_display_precision`).
66174
- * The UI reads them directly from the slice — they are the single source
66175
- * of truth for generic numeric rendering and do NOT live on `sourceInfo`.
66059
+ * The quantity is not on this slice. It is chosen when the device is
66060
+ * adopted and stored once on `SourceInfo.quantity` (D944). `value` is in
66061
+ * that quantity's base unit. `null` is a refused sample (D393).
66176
66062
  *
66177
66063
  * Prefer the typed alternatives (`temperature-sensor`, `humidity-sensor`,
66178
66064
  * `ambient-light-sensor`, `pressure-sensor`, `power-meter`,
@@ -66181,15 +66067,8 @@ onSent: { data: object({
66181
66067
  * numeric values land here.
66182
66068
  */
66183
66069
  var NumericSensorStatusSchema = object({
66184
- value: number(),
66185
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
66186
- * Populated live from the upstream source on each state push.
66187
- * Absent for unitless sensors. */
66188
- unit: string().optional(),
66189
- /** Suggested decimal places for numeric display.
66190
- * Populated live from the upstream source when provided.
66191
- * Falls back to auto-formatting when absent. */
66192
- precision: number().int().min(0).max(10).optional(),
66070
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
66071
+ value: number().nullable().meta(readingMeta("device")),
66193
66072
  /** Ms epoch when the slice was last updated. */
66194
66073
  lastFetchedAt: number()
66195
66074
  });
@@ -66213,11 +66092,11 @@ var numericSensorCapability = {
66213
66092
  */
66214
66093
  durability: "restored",
66215
66094
  /**
66216
- * History (D850): recorded series. The unit is per device, read from the sibling `unit` field.
66095
+ * History (D850): recorded series. The unit is the device quantity's base,
66096
+ * resolved when the series is read — the cap has no single unit.
66217
66097
  */
66218
66098
  history: { fields: { value: {
66219
66099
  kind: "gauge",
66220
- unitFrom: "unit",
66221
66100
  minIntervalMs: 3e4
66222
66101
  } } },
66223
66102
  /** Clock fields: written, but excluded from the compare that decides
@@ -66509,23 +66388,26 @@ var PetFeederStatusSchema = object({
66509
66388
  /** Food currently in the bowl (grams). Null when the device has not
66510
66389
  * reported a reading yet. On dual-hopper models this is the combined
66511
66390
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
66512
- foodLevel: number().nullable(),
66391
+ foodLevel: number().nullable().meta(readingMeta("none")),
66513
66392
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
66514
66393
  * single-hopper models. */
66515
- food1: number().nullable(),
66394
+ food1: number().nullable().meta(readingMeta("none")),
66516
66395
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
66517
66396
  * single-hopper models. */
66518
- food2: number().nullable(),
66397
+ food2: number().nullable().meta(readingMeta("none")),
66519
66398
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
66520
66399
  * (`device_class: problem`, on = low). True when the bowl is empty /
66521
66400
  * below the feeder's low threshold. */
66522
66401
  lowFood: boolean(),
66523
66402
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
66524
66403
  * device has no battery reading. */
66525
- batteryPower: number().min(0).max(100).nullable(),
66526
- /** Days of desiccant life remaining. Null when the model has no
66527
- * desiccant sensor. */
66528
- desiccantLeftDays: number().nullable(),
66404
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
66405
+ /**
66406
+ * Desiccant life remaining, stored in seconds (duration base, D944).
66407
+ * Providers convert from the days the feeder reports. Null when the
66408
+ * model has no desiccant sensor.
66409
+ */
66410
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
66529
66411
  /** True while a feed is in progress. */
66530
66412
  feeding: boolean(),
66531
66413
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -66536,7 +66418,7 @@ var PetFeederStatusSchema = object({
66536
66418
  * with `errorCode` for consumers that want the raw integer. */
66537
66419
  error: string().nullable(),
66538
66420
  /** Raw device error code (0 / null = no error). */
66539
- errorCode: number().nullable(),
66421
+ errorCode: number().nullable().meta(readingMeta("none")),
66540
66422
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
66541
66423
  isDualHopper: boolean(),
66542
66424
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -66546,7 +66428,7 @@ var PetFeederStatusSchema = object({
66546
66428
  /** Play a chime when dispensing. */
66547
66429
  feedSound: boolean(),
66548
66430
  /** Speaker / prompt volume level (device-scaled integer). */
66549
- volume: number(),
66431
+ volume: number().meta(readingMeta("none")),
66550
66432
  /** Ms epoch when the slice was last refreshed from the cloud. */
66551
66433
  lastFetchedAt: number()
66552
66434
  });
@@ -66665,7 +66547,7 @@ var petFeederCapability = {
66665
66547
  },
66666
66548
  desiccantLeftDays: {
66667
66549
  kind: "gauge",
66668
- unit: "days"
66550
+ unit: "s"
66669
66551
  }
66670
66552
  } }
66671
66553
  };
@@ -67101,36 +66983,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
67101
66983
  */
67102
66984
  var PowerMeterStatusSchema = object({
67103
66985
  /** Instantaneous power draw in watts. */
67104
- watts: number().optional().meta({
67105
- unit: "W",
67106
- label: "Power"
67107
- }),
66986
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
67108
66987
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
67109
- kwhTotal: number().optional().meta({
67110
- unit: "kWh",
67111
- label: "Energy"
67112
- }),
66988
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
67113
66989
  /** Voltage in volts. */
67114
- volts: number().optional(),
66990
+ volts: number().optional().meta(readingMeta("voltage")),
67115
66991
  /** Current in amperes. */
67116
- amps: number().optional(),
67117
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
67118
- va: number().optional(),
66992
+ amps: number().optional().meta(readingMeta("current")),
66993
+ /** Apparent power in volt-amperes. */
66994
+ va: number().optional().meta(readingMeta("apparent-power")),
67119
66995
  /** Power factor (0..1) if reported. */
67120
- powerFactor: number().min(0).max(1).optional(),
66996
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
67121
66997
  /** Ms epoch when the slice was last updated. */
67122
- lastFetchedAt: number(),
67123
- /** Live display unit of the single metric this slice carries (e.g. HA
67124
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
67125
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
67126
- * unit per slice is unambiguous. The UI prefers this over the role's
67127
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
67128
- unit: string().optional(),
67129
- /** Suggested decimal places for numeric display.
67130
- * Populated live from the upstream source when provided (e.g. HA
67131
- * `attributes.suggested_display_precision`). Falls back to
67132
- * auto-formatting when absent. */
67133
- precision: number().int().min(0).max(10).optional()
66998
+ lastFetchedAt: number()
67134
66999
  });
67135
67000
  var powerMeterCapability = {
67136
67001
  name: "power-meter",
@@ -67194,12 +67059,7 @@ var powerMeterCapability = {
67194
67059
  } },
67195
67060
  /** Clock fields: written, but excluded from the compare that decides
67196
67061
  * whether persisting is worth a SQLite commit. */
67197
- volatileStateFields: ["lastFetchedAt"],
67198
- customize: {
67199
- version: 1,
67200
- advanced: "derive",
67201
- targets: []
67202
- }
67062
+ volatileStateFields: ["lastFetchedAt"]
67203
67063
  };
67204
67064
  /**
67205
67065
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -67219,11 +67079,11 @@ var powerMeterCapability = {
67219
67079
  */
67220
67080
  var GpsLocationSchema = object({
67221
67081
  /** Latitude in decimal degrees, -90..90. */
67222
- latitude: number().min(-90).max(90),
67082
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
67223
67083
  /** Longitude in decimal degrees, -180..180. */
67224
- longitude: number().min(-180).max(180),
67084
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
67225
67085
  /** Reported accuracy in meters (lower = better). */
67226
- accuracyMeters: number().nonnegative()
67086
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
67227
67087
  });
67228
67088
  var PresenceStatusSchema = object({
67229
67089
  /** `home` / `not_home` / any user-defined zone name. */
@@ -67235,7 +67095,7 @@ var PresenceStatusSchema = object({
67235
67095
  gps: GpsLocationSchema.nullable(),
67236
67096
  /** Optional battery level of the tracking device (0..100). Null
67237
67097
  * when not reported. */
67238
- batteryPercent: number().min(0).max(100).nullable(),
67098
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
67239
67099
  /** Ms epoch when the slice was last updated. */
67240
67100
  lastChangedAt: number()
67241
67101
  });
@@ -67285,19 +67145,10 @@ var presenceCapability = {
67285
67145
  * `sourceInfo`.
67286
67146
  */
67287
67147
  var PressureSensorStatusSchema = object({
67288
- /** Current pressure in hPa. */
67289
- hpa: number(),
67148
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
67149
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
67290
67150
  /** Ms epoch when the slice was last updated. */
67291
- lastFetchedAt: number(),
67292
- /** Live display unit from the upstream source (e.g. HA
67293
- * `attributes.unit_of_measurement`). The UI prefers this over the
67294
- * role's canonical unit. Absent → fall back to the canonical unit. */
67295
- unit: string().optional(),
67296
- /** Suggested decimal places for numeric display.
67297
- * Populated live from the upstream source when provided (e.g. HA
67298
- * `attributes.suggested_display_precision`). Falls back to
67299
- * auto-formatting when absent. */
67300
- precision: number().int().min(0).max(10).optional()
67151
+ lastFetchedAt: number()
67301
67152
  });
67302
67153
  var pressureSensorCapability = {
67303
67154
  name: "pressure-sensor",
@@ -67711,8 +67562,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
67711
67562
  }), _void(), { kind: "mutation" });
67712
67563
  var PtzAutotrackSettingsSchema = object({
67713
67564
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
67714
- stopDelaySeconds: number().int().min(0).max(300),
67715
- disappearDelaySeconds: number().int().min(0).max(300)
67565
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
67566
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
67716
67567
  });
67717
67568
  /**
67718
67569
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -67866,7 +67717,7 @@ var RunningStateStatusSchema = object({
67866
67717
  /** null = unknown — never coerced to false (D393). */
67867
67718
  running: boolean().nullable(),
67868
67719
  /** Epoch ms the current boolean began; null while `running` is null. */
67869
- since: number().nullable(),
67720
+ since: number().nullable().meta(readingMeta("none")),
67870
67721
  source: RunningStateSourceSchema.meta({ label: "Source" }),
67871
67722
  phase: string().nullable(),
67872
67723
  /** null until a reading has been published (D393). */
@@ -67881,7 +67732,6 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
67881
67732
  /** Generic Customize target for the running boolean (D936). */
67882
67733
  var runningCustomize = {
67883
67734
  version: 1,
67884
- advanced: "derive",
67885
67735
  targets: [{
67886
67736
  id: "running",
67887
67737
  cardinality: "scalar",
@@ -67901,7 +67751,7 @@ var runningCustomize = {
67901
67751
  DeviceType.Thermostat,
67902
67752
  DeviceType.PetFeeder
67903
67753
  ],
67904
- apply: "add-or-replace",
67754
+ apply: "add",
67905
67755
  path: "running",
67906
67756
  sources: [
67907
67757
  {
@@ -67917,6 +67767,7 @@ var runningCustomize = {
67917
67767
  requiredUnit: "W",
67918
67768
  transforms: [{
67919
67769
  kind: "threshold",
67770
+ comparisons: ["gte", "lte"],
67920
67771
  comparison: "gte",
67921
67772
  defaultThreshold: 5,
67922
67773
  defaultStartDwellMs: RUNNING_POWER_START_DELAY_MS,
@@ -69196,9 +69047,9 @@ var RecordTriggerSchema = _enum([
69196
69047
  */
69197
69048
  var RecordWindowSchema = object({
69198
69049
  trigger: RecordTriggerSchema,
69199
- day: number().int().min(0).max(6),
69200
- startMinute: number().int().min(0).max(1439),
69201
- endMinute: number().int().min(1).max(1440)
69050
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
69051
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
69052
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
69202
69053
  });
69203
69054
  /** Status of one physical volume, as the camera itself describes it. */
69204
69055
  var OnboardStorageVolumeSchema = object({
@@ -69274,10 +69125,10 @@ var RecordingOnboardStatusSchema = object({
69274
69125
  /** Master "record to the card at all" switch. */
69275
69126
  enabled: boolean().nullable(),
69276
69127
  overwriteWhenFull: boolean().nullable(),
69277
- preRecordSec: number().nullable(),
69278
- postRecordSec: number().nullable(),
69128
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
69129
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
69279
69130
  /** Length of one recorded file, in minutes. */
69280
- segmentMinutes: number().nullable(),
69131
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
69281
69132
  /** The primary track's weekly windows, flattened. */
69282
69133
  windows: array(RecordWindowSchema),
69283
69134
  /**
@@ -69293,7 +69144,7 @@ var RecordingOnboardStatusSchema = object({
69293
69144
  * provider reporting a non-zero count also reports the schedule as not
69294
69145
  * writable.
69295
69146
  */
69296
- unreadableWindows: number(),
69147
+ unreadableWindows: number().meta(readingMeta("count")),
69297
69148
  /**
69298
69149
  * The camera is scheduled to record and has NO usable storage.
69299
69150
  *
@@ -70156,7 +70007,7 @@ var CamStreamDescriptorSchema = object({
70156
70007
  url: string().optional(),
70157
70008
  codec: string().optional(),
70158
70009
  resolution: CamStreamResolutionSchema.optional(),
70159
- fps: number().positive().optional(),
70010
+ fps: number().positive().optional().meta(readingMeta("rate")),
70160
70011
  label: string().optional(),
70161
70012
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
70162
70013
  deviceFeatures: array(string()).optional(),
@@ -70243,18 +70094,18 @@ var StreamProfileSchema = _enum([
70243
70094
  ]);
70244
70095
  /** The current encoding config of one stream profile. */
70245
70096
  var StreamProfileConfigSchema = object({
70246
- width: number(),
70247
- height: number(),
70097
+ width: number().meta(readingMeta("none")),
70098
+ height: number().meta(readingMeta("none")),
70248
70099
  codec: _enum(["h264", "h265"]),
70249
- framerate: number(),
70250
- bitrate: number(),
70100
+ framerate: number().meta(readingMeta("rate")),
70101
+ bitrate: number().meta(readingMeta("bitrate")),
70251
70102
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
70252
70103
  encoderProfile: _enum([
70253
70104
  "high",
70254
70105
  "main",
70255
70106
  "baseline"
70256
70107
  ]).optional(),
70257
- gop: number().optional(),
70108
+ gop: number().optional().meta(readingMeta("none")),
70258
70109
  /**
70259
70110
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
70260
70111
  *
@@ -70475,12 +70326,7 @@ var switchCapability = {
70475
70326
  label: "On",
70476
70327
  kind: "boolean"
70477
70328
  }
70478
- }] },
70479
- customize: {
70480
- version: 1,
70481
- advanced: "derive",
70482
- targets: []
70483
- }
70329
+ }] }
70484
70330
  };
70485
70331
  /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
70486
70332
  function hasControlCharacter(value) {
@@ -71063,35 +70909,21 @@ var tamperCapability = {
71063
70909
  * entries with `device_class: temperature` and any future native
71064
70910
  * thermometer.
71065
70911
  *
71066
- * Unit normalisation: providers convert to Celsius before storing.
71067
- * The slice value is always Celsius so cross-cap aggregators
71068
- * (climate-control's `currentTemp`, energy analytics) can compose
71069
- * without per-source unit fixups. The canonical display unit (`°C`) is
71070
- * a descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
71071
- * `sourceInfo`.
70912
+ * The number is Celsius (D944). Providers convert on ingest. A failed
70913
+ * conversion is `null`, never a guess. Display unit is a read-time preference.
71072
70914
  *
71073
70915
  * Status `lastFetchedAt` lets staleness-aware consumers detect a
71074
70916
  * frozen feed (provider hung) distinct from a "temperature hasn't
71075
70917
  * changed" steady state.
71076
70918
  */
71077
70919
  var TemperatureSensorStatusSchema = object({
71078
- /** Current temperature in Celsius. */
71079
- celsius: number().meta({
71080
- unit: "°C",
70920
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
70921
+ celsius: number().nullable().meta(readingMeta("temperature", {
70922
+ precision: 1,
71081
70923
  label: "Temperature"
71082
- }),
70924
+ })),
71083
70925
  /** Ms epoch when the slice was last updated (push or poll). */
71084
- lastFetchedAt: number(),
71085
- /** Live display unit from the upstream source (e.g. HA
71086
- * `attributes.unit_of_measurement`). The UI prefers this over the
71087
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
71088
- * Absent → the UI falls back to the role's canonical unit. */
71089
- unit: string().optional(),
71090
- /** Suggested decimal places for numeric display.
71091
- * Populated live from the upstream source when provided (e.g. HA
71092
- * `attributes.suggested_display_precision`). Falls back to
71093
- * auto-formatting when absent. */
71094
- precision: number().int().min(0).max(10).optional()
70926
+ lastFetchedAt: number()
71095
70927
  });
71096
70928
  var temperatureSensorCapability = {
71097
70929
  name: "temperature-sensor",
@@ -71122,12 +70954,7 @@ var temperatureSensorCapability = {
71122
70954
  } } },
71123
70955
  /** Clock fields: written, but excluded from the compare that decides
71124
70956
  * whether persisting is worth a SQLite commit. */
71125
- volatileStateFields: ["lastFetchedAt"],
71126
- customize: {
71127
- version: 1,
71128
- advanced: "derive",
71129
- targets: []
71130
- }
70957
+ volatileStateFields: ["lastFetchedAt"]
71131
70958
  };
71132
70959
  /**
71133
70960
  * toast — system-scoped singleton capability that streams toast
@@ -71410,16 +71237,16 @@ var VacuumControlStatusSchema = object({
71410
71237
  ]),
71411
71238
  /** 0..100 battery percentage. Null when the device has no battery
71412
71239
  * reading. */
71413
- batteryLevel: number().min(0).max(100).nullable(),
71240
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71414
71241
  /** Current fan-speed token (provider-verbatim). Null when unknown or
71415
71242
  * the vacuum has no speed control. */
71416
71243
  fanSpeed: string().nullable(),
71417
71244
  /** Speed tokens the hardware accepts — drives the UI selector. */
71418
71245
  availableFanSpeeds: array(string()),
71419
71246
  /** 0..100 cleaning-completion percentage of the current task, or null. */
71420
- progressPercent: number().min(0).max(100).nullable(),
71247
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71421
71248
  /** Current error code (0 / null = no error). */
71422
- errorCode: number().nullable(),
71249
+ errorCode: number().nullable().meta(readingMeta("none")),
71423
71250
  /** Human label for {@link errorCode}, or null when none / undecodable. */
71424
71251
  errorLabel: string().nullable(),
71425
71252
  /** Ms epoch when the slice was last updated. */
@@ -71497,7 +71324,7 @@ var ValveStatusSchema = object({
71497
71324
  ]),
71498
71325
  /** 0 = fully closed, 100 = fully open. Null when the device has no
71499
71326
  * intermediate position surface. */
71500
- position: number().min(0).max(100).nullable(),
71327
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71501
71328
  /** Ms epoch when the slice was last updated. */
71502
71329
  lastChangedAt: number()
71503
71330
  });
@@ -71605,9 +71432,9 @@ var vibrationCapability = {
71605
71432
  */
71606
71433
  var WaterHeaterStatusSchema = object({
71607
71434
  /** Current measured temperature. Null when not reported. */
71608
- currentTemp: number().nullable(),
71435
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
71609
71436
  /** Target temperature setpoint. Null when no setpoint surface. */
71610
- targetTemp: number().nullable(),
71437
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
71611
71438
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
71612
71439
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
71613
71440
  * device reports an unknown state. */
@@ -71618,9 +71445,9 @@ var WaterHeaterStatusSchema = object({
71618
71445
  * has no away surface. */
71619
71446
  away: boolean().nullable(),
71620
71447
  /** HA `min_temp` attribute. Null when not reported. */
71621
- minTemp: number().nullable(),
71448
+ minTemp: number().nullable().meta(readingMeta("temperature")),
71622
71449
  /** HA `max_temp` attribute. Null when not reported. */
71623
- maxTemp: number().nullable(),
71450
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
71624
71451
  /** Ms epoch when the slice was last updated. */
71625
71452
  lastChangedAt: number()
71626
71453
  });
@@ -71705,25 +71532,18 @@ var waterHeaterCapability = {
71705
71532
  * Forecast deferred — current conditions only for v1.
71706
71533
  */
71707
71534
  var WeatherStatusSchema = object({
71708
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
71709
- * when no condition has been reported yet. */
71535
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
71710
71536
  condition: string().nullable(),
71711
- /** Current temperature in the reported unit. Null when not provided. */
71712
- temperature: number().nullable(),
71713
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
71714
- temperatureUnit: string().nullable(),
71537
+ /** Current temperature in Celsius. Null when not provided or refused. */
71538
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
71715
71539
  /** Relative humidity (0..100). Null when not provided. */
71716
- humidity: number().min(0).max(100).nullable(),
71717
- /** Barometric pressure in the reported unit. Null when not provided. */
71718
- pressure: number().nullable(),
71719
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
71720
- pressureUnit: string().nullable(),
71721
- /** Wind speed in the reported unit. Null when not provided. */
71722
- windSpeed: number().nullable(),
71723
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
71724
- windSpeedUnit: string().nullable(),
71540
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
71541
+ /** Barometric pressure in hPa. Null when not provided or refused. */
71542
+ pressure: number().nullable().meta(readingMeta("pressure")),
71543
+ /** Wind speed in metres per second. Null when not provided or refused. */
71544
+ windSpeed: number().nullable().meta(readingMeta("speed")),
71725
71545
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
71726
- windBearing: number().nullable(),
71546
+ windBearing: number().nullable().meta(readingMeta("angle")),
71727
71547
  /** Ms epoch when the slice was last updated. */
71728
71548
  lastFetchedAt: number()
71729
71549
  });
@@ -71751,12 +71571,12 @@ var weatherCapability = {
71751
71571
  */
71752
71572
  durability: "session",
71753
71573
  /**
71754
- * History (D850): recorded series. Units follow the sibling `*Unit` fields.
71574
+ * History (D850): recorded series. Numbers are the quantity base (D944).
71755
71575
  */
71756
71576
  history: { fields: {
71757
71577
  temperature: {
71758
71578
  kind: "gauge",
71759
- unitFrom: "temperatureUnit",
71579
+ unit: "°C",
71760
71580
  deadband: .2
71761
71581
  },
71762
71582
  humidity: {
@@ -71766,12 +71586,12 @@ var weatherCapability = {
71766
71586
  },
71767
71587
  pressure: {
71768
71588
  kind: "gauge",
71769
- unitFrom: "pressureUnit",
71589
+ unit: "hPa",
71770
71590
  deadband: .5
71771
71591
  },
71772
71592
  windSpeed: {
71773
71593
  kind: "gauge",
71774
- unitFrom: "windSpeedUnit",
71594
+ unit: "m/s",
71775
71595
  deadband: .5
71776
71596
  }
71777
71597
  } }
@@ -86911,7 +86731,6 @@ var SENSOR_CAP_VOCABULARY = {
86911
86731
  field: "value",
86912
86732
  quantity: t("the reading", "il valore"),
86913
86733
  label: t("Reading", "Valore"),
86914
- unitField: "unit",
86915
86734
  icon: "gauge"
86916
86735
  }]
86917
86736
  },
@@ -87047,7 +86866,7 @@ var SENSOR_CAP_VOCABULARY = {
87047
86866
  field: "temperature",
87048
86867
  quantity: t("the outdoor temperature", "la temperatura esterna"),
87049
86868
  label: t("Temperature", "Temperatura"),
87050
- unitField: "temperatureUnit",
86869
+ unit: "°C",
87051
86870
  icon: "thermometer"
87052
86871
  },
87053
86872
  {
@@ -87063,7 +86882,7 @@ var SENSOR_CAP_VOCABULARY = {
87063
86882
  field: "windSpeed",
87064
86883
  quantity: t("the wind speed", "la velocità del vento"),
87065
86884
  label: t("Wind", "Vento"),
87066
- unitField: "windSpeedUnit",
86885
+ unit: "m/s",
87067
86886
  icon: "wind"
87068
86887
  },
87069
86888
  {
@@ -87071,7 +86890,7 @@ var SENSOR_CAP_VOCABULARY = {
87071
86890
  field: "pressure",
87072
86891
  quantity: t("the air pressure", "la pressione atmosferica"),
87073
86892
  label: t("Pressure", "Pressione"),
87074
- unitField: "pressureUnit",
86893
+ unit: "hPa",
87075
86894
  icon: "gauge"
87076
86895
  }
87077
86896
  ]
@@ -90305,6 +90124,11 @@ var PulseOverrideDraftSchema = object({
90305
90124
  targetId: string().min(1),
90306
90125
  enabled: boolean(),
90307
90126
  debounceMs: number().nonnegative(),
90127
+ edges: array(_enum([
90128
+ "rising",
90129
+ "button",
90130
+ "native-event"
90131
+ ])).min(1).optional(),
90308
90132
  sources: array(OverrideSourceSchema).min(1)
90309
90133
  });
90310
90134
  var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([