@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
@@ -5632,7 +5632,7 @@ var ZodIssueCode = {
5632
5632
  var ZodFirstPartyTypeKind;
5633
5633
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5634
5634
  //#endregion
5635
- //#region ../types/dist/sleep-BErAxhul.mjs
5635
+ //#region ../types/dist/sleep-CARd6wZF.mjs
5636
5636
  /**
5637
5637
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5638
5638
  * window to float samples (D455).
@@ -8332,6 +8332,52 @@ function defineRecordKinds(specs) {
8332
8332
  }
8333
8333
  return out;
8334
8334
  }
8335
+ var READING_QUANTITIES = [
8336
+ "temperature",
8337
+ "pressure",
8338
+ "speed",
8339
+ "precipitation",
8340
+ "precipitation-rate",
8341
+ "length",
8342
+ "area",
8343
+ "illuminance",
8344
+ "irradiance",
8345
+ "power",
8346
+ "energy",
8347
+ "voltage",
8348
+ "current",
8349
+ "apparent-power",
8350
+ "humidity",
8351
+ "percentage",
8352
+ "concentration-volume",
8353
+ "concentration-mass",
8354
+ "concentration-ppb",
8355
+ "volume",
8356
+ "duration",
8357
+ "duration-ms",
8358
+ "information",
8359
+ "sound-level",
8360
+ "signal-level",
8361
+ "colour-temperature",
8362
+ "angle",
8363
+ "bitrate",
8364
+ "rate",
8365
+ "mass",
8366
+ "index",
8367
+ "ratio",
8368
+ "count",
8369
+ "none",
8370
+ "device"
8371
+ ];
8372
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8373
+ new Set(READING_QUANTITIES);
8374
+ function readingMeta(quantity, extras) {
8375
+ return {
8376
+ quantity,
8377
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8378
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8379
+ };
8380
+ }
8335
8381
  /** Shared Zod schemas used across streaming capabilities. */
8336
8382
  var CamProfileSchema = _enum([
8337
8383
  "high",
@@ -8359,8 +8405,8 @@ var CamStreamKindSchema = _enum([
8359
8405
  "derived"
8360
8406
  ]);
8361
8407
  var CamStreamResolutionSchema = object({
8362
- width: number().int().positive(),
8363
- height: number().int().positive()
8408
+ width: number().int().positive().meta(readingMeta("none")),
8409
+ height: number().int().positive().meta(readingMeta("none"))
8364
8410
  });
8365
8411
  var CameraStreamSchema = object({
8366
8412
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -9089,14 +9135,6 @@ Object.freeze({
9089
9135
  {
9090
9136
  "path": "aqi",
9091
9137
  "valueType": "number"
9092
- },
9093
- {
9094
- "path": "unit",
9095
- "valueType": "string"
9096
- },
9097
- {
9098
- "path": "precision",
9099
- "valueType": "number"
9100
9138
  }
9101
9139
  ],
9102
9140
  "alarm-panel": [
@@ -9132,20 +9170,10 @@ Object.freeze({
9132
9170
  "valueType": "boolean"
9133
9171
  }
9134
9172
  ],
9135
- "ambient-light-sensor": [
9136
- {
9137
- "path": "lux",
9138
- "valueType": "number"
9139
- },
9140
- {
9141
- "path": "unit",
9142
- "valueType": "string"
9143
- },
9144
- {
9145
- "path": "precision",
9146
- "valueType": "number"
9147
- }
9148
- ],
9173
+ "ambient-light-sensor": [{
9174
+ "path": "lux",
9175
+ "valueType": "number"
9176
+ }],
9149
9177
  "audio-metrics": [
9150
9178
  {
9151
9179
  "path": "ts",
@@ -9225,6 +9253,10 @@ Object.freeze({
9225
9253
  "path": "lastUpdated",
9226
9254
  "valueType": "number"
9227
9255
  },
9256
+ {
9257
+ "path": "volts",
9258
+ "valueType": "number"
9259
+ },
9228
9260
  {
9229
9261
  "path": "lastContactAt",
9230
9262
  "valueType": "number"
@@ -9467,10 +9499,6 @@ Object.freeze({
9467
9499
  "datetime"
9468
9500
  ]
9469
9501
  },
9470
- {
9471
- "path": "unit",
9472
- "valueType": "string"
9473
- },
9474
9502
  {
9475
9503
  "path": "min",
9476
9504
  "valueType": "number"
@@ -9482,19 +9510,6 @@ Object.freeze({
9482
9510
  {
9483
9511
  "path": "step",
9484
9512
  "valueType": "number"
9485
- },
9486
- {
9487
- "path": "precision",
9488
- "valueType": "number"
9489
- },
9490
- {
9491
- "path": "format",
9492
- "valueType": "enum",
9493
- "enumValues": [
9494
- "date",
9495
- "time",
9496
- "datetime"
9497
- ]
9498
9513
  }
9499
9514
  ],
9500
9515
  "cover": [
@@ -9735,13 +9750,8 @@ Object.freeze({
9735
9750
  "path": "value",
9736
9751
  "valueType": "string"
9737
9752
  }, {
9738
- "path": "format",
9739
- "valueType": "enum",
9740
- "enumValues": [
9741
- "date",
9742
- "time",
9743
- "datetime"
9744
- ]
9753
+ "path": "timestamp",
9754
+ "valueType": "boolean"
9745
9755
  }],
9746
9756
  "event-emitter": [
9747
9757
  {
@@ -9848,20 +9858,10 @@ Object.freeze({
9848
9858
  "valueType": "number"
9849
9859
  }
9850
9860
  ],
9851
- "humidity-sensor": [
9852
- {
9853
- "path": "percent",
9854
- "valueType": "number"
9855
- },
9856
- {
9857
- "path": "unit",
9858
- "valueType": "string"
9859
- },
9860
- {
9861
- "path": "precision",
9862
- "valueType": "number"
9863
- }
9864
- ],
9861
+ "humidity-sensor": [{
9862
+ "path": "percent",
9863
+ "valueType": "number"
9864
+ }],
9865
9865
  "image": [{
9866
9866
  "path": "url",
9867
9867
  "valueType": "string"
@@ -10200,20 +10200,10 @@ Object.freeze({
10200
10200
  "valueType": "boolean"
10201
10201
  }
10202
10202
  ],
10203
- "numeric-sensor": [
10204
- {
10205
- "path": "value",
10206
- "valueType": "number"
10207
- },
10208
- {
10209
- "path": "unit",
10210
- "valueType": "string"
10211
- },
10212
- {
10213
- "path": "precision",
10214
- "valueType": "number"
10215
- }
10216
- ],
10203
+ "numeric-sensor": [{
10204
+ "path": "value",
10205
+ "valueType": "number"
10206
+ }],
10217
10207
  "pet-feeder": [
10218
10208
  {
10219
10209
  "path": "foodLevel",
@@ -10305,14 +10295,6 @@ Object.freeze({
10305
10295
  {
10306
10296
  "path": "powerFactor",
10307
10297
  "valueType": "number"
10308
- },
10309
- {
10310
- "path": "unit",
10311
- "valueType": "string"
10312
- },
10313
- {
10314
- "path": "precision",
10315
- "valueType": "number"
10316
10298
  }
10317
10299
  ],
10318
10300
  "presence": [
@@ -10341,20 +10323,10 @@ Object.freeze({
10341
10323
  "valueType": "number"
10342
10324
  }
10343
10325
  ],
10344
- "pressure-sensor": [
10345
- {
10346
- "path": "hpa",
10347
- "valueType": "number"
10348
- },
10349
- {
10350
- "path": "unit",
10351
- "valueType": "string"
10352
- },
10353
- {
10354
- "path": "precision",
10355
- "valueType": "number"
10356
- }
10357
- ],
10326
+ "pressure-sensor": [{
10327
+ "path": "hpa",
10328
+ "valueType": "number"
10329
+ }],
10358
10330
  "privacy-mask": [{
10359
10331
  "path": "enabled",
10360
10332
  "valueType": "boolean"
@@ -10665,20 +10637,10 @@ Object.freeze({
10665
10637
  "path": "tampered",
10666
10638
  "valueType": "boolean"
10667
10639
  }],
10668
- "temperature-sensor": [
10669
- {
10670
- "path": "celsius",
10671
- "valueType": "number"
10672
- },
10673
- {
10674
- "path": "unit",
10675
- "valueType": "string"
10676
- },
10677
- {
10678
- "path": "precision",
10679
- "valueType": "number"
10680
- }
10681
- ],
10640
+ "temperature-sensor": [{
10641
+ "path": "celsius",
10642
+ "valueType": "number"
10643
+ }],
10682
10644
  "update": [
10683
10645
  {
10684
10646
  "path": "currentVersion",
@@ -10789,10 +10751,6 @@ Object.freeze({
10789
10751
  "path": "temperature",
10790
10752
  "valueType": "number"
10791
10753
  },
10792
- {
10793
- "path": "temperatureUnit",
10794
- "valueType": "string"
10795
- },
10796
10754
  {
10797
10755
  "path": "humidity",
10798
10756
  "valueType": "number"
@@ -10801,18 +10759,10 @@ Object.freeze({
10801
10759
  "path": "pressure",
10802
10760
  "valueType": "number"
10803
10761
  },
10804
- {
10805
- "path": "pressureUnit",
10806
- "valueType": "string"
10807
- },
10808
10762
  {
10809
10763
  "path": "windSpeed",
10810
10764
  "valueType": "number"
10811
10765
  },
10812
- {
10813
- "path": "windSpeedUnit",
10814
- "valueType": "string"
10815
- },
10816
10766
  {
10817
10767
  "path": "windBearing",
10818
10768
  "valueType": "number"
@@ -19360,7 +19310,8 @@ var CompositionProblemSchema = object({
19360
19310
  "item-field-reserved",
19361
19311
  "target-missing",
19362
19312
  "target-unreadable",
19363
- "not-replaceable"
19313
+ "not-replaceable",
19314
+ "native-capability-replace"
19364
19315
  ]),
19365
19316
  path: string(),
19366
19317
  message: string()
@@ -19418,9 +19369,11 @@ var CompositionFieldPlanSchema = object({
19418
19369
  reads: array(CompositionFieldReadSchema)
19419
19370
  });
19420
19371
  /**
19421
- * What a feature does to its target (D663): `new` builds a capability on a new
19422
- * device; on an existing device it `add`s a capability the device lacks, or
19423
- * `replace`s named fields of one it has natively.
19372
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
19373
+ * new device; on an existing device it `add`s a capability the device lacks.
19374
+ * Replacing a capability the device already serves is refused
19375
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
19376
+ * wire for a claim the hub already holds.
19424
19377
  */
19425
19378
  var CompositionFeatureModeSchema = _enum([
19426
19379
  "new",
@@ -21313,12 +21266,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
21313
21266
  * providers detect renames by matching `uniqueId` and patch `id`
21314
21267
  * in-place so dispatch keeps working.
21315
21268
  *
21316
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
21317
- * `precision`) live in the cap STATUS SLICE so they flow live with every
21318
- * state push and are the single source of truth. The `device_class` tag is
21319
- * an HA-specific input that is consumed at the discovery boundary
21320
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
21321
- * stored after that point.
21269
+ * **Display unit and precision are NOT stored here.** They are a read-time
21270
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
21271
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
21272
+ * neither is repeated on each sample. The `device_class` tag is an
21273
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
21274
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
21322
21275
  *
21323
21276
  * The `raw` escape hatch carries provider-specific attributes that don't
21324
21277
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -21332,6 +21285,14 @@ var SourceInfoSchema = object({
21332
21285
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
21333
21286
  * Used to detect rename when `id` changes. */
21334
21287
  uniqueId: string().optional(),
21288
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
21289
+ quantity: ReadingQuantitySchema.optional(),
21290
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
21291
+ dateTimeShape: _enum([
21292
+ "date",
21293
+ "time",
21294
+ "datetime"
21295
+ ]).optional(),
21335
21296
  /** Provider-specific extras that don't fit the structured slots.
21336
21297
  * Whitelist what goes here per provider — do NOT dump entire HA
21337
21298
  * attribute blobs (would explode the meta JSON column). */
@@ -42359,7 +42320,8 @@ method(object({
42359
42320
  "enum"
42360
42321
  ]),
42361
42322
  enumValues: array(string()).optional(),
42362
- item: boolean().optional()
42323
+ item: boolean().optional(),
42324
+ quantity: ReadingQuantitySchema.optional()
42363
42325
  })).readonly(),
42364
42326
  itemArray: object({
42365
42327
  path: string(),
@@ -46426,8 +46388,8 @@ var MaskPolygonShapeSchema = object({
46426
46388
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
46427
46389
  var MaskGridShapeSchema = object({
46428
46390
  kind: literal("grid"),
46429
- gridWidth: number(),
46430
- gridHeight: number(),
46391
+ gridWidth: number().meta(readingMeta("none")),
46392
+ gridHeight: number().meta(readingMeta("none")),
46431
46393
  cells: array(boolean())
46432
46394
  });
46433
46395
  discriminatedUnion("kind", [
@@ -59753,27 +59715,17 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
59753
59715
  */
59754
59716
  var AirQualitySensorStatusSchema = object({
59755
59717
  /** Carbon dioxide concentration in ppm. */
59756
- co2Ppm: number().min(0).optional(),
59718
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
59757
59719
  /** Total volatile organic compounds in ppb. */
59758
- vocPpb: number().min(0).optional(),
59720
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
59759
59721
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
59760
- pm25: number().min(0).optional(),
59722
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
59761
59723
  /** Particulate matter ≤ 10 μm in µg/m³. */
59762
- pm10: number().min(0).optional(),
59724
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
59763
59725
  /** Composite AQI value (typically 0..500). */
59764
- aqi: number().optional(),
59726
+ aqi: number().optional().meta(readingMeta("index")),
59765
59727
  /** Ms epoch when the slice was last updated. */
59766
- lastFetchedAt: number(),
59767
- /** Live display unit of the single metric this slice carries (e.g. HA
59768
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
59769
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
59770
- * per slice is unambiguous. */
59771
- unit: string().optional(),
59772
- /** Suggested decimal places for numeric display.
59773
- * Populated live from the upstream source when provided (e.g. HA
59774
- * `attributes.suggested_display_precision`). Falls back to
59775
- * auto-formatting when absent. */
59776
- precision: number().int().min(0).max(10).optional()
59728
+ lastFetchedAt: number()
59777
59729
  });
59778
59730
  var airQualitySensorCapability = {
59779
59731
  name: "air-quality-sensor",
@@ -59810,12 +59762,12 @@ var airQualitySensorCapability = {
59810
59762
  },
59811
59763
  pm25: {
59812
59764
  kind: "gauge",
59813
- unit: "ug/m3",
59765
+ unit: "µg/m³",
59814
59766
  deadband: 1
59815
59767
  },
59816
59768
  pm10: {
59817
59769
  kind: "gauge",
59818
- unit: "ug/m3",
59770
+ unit: "µg/m³",
59819
59771
  deadband: 1
59820
59772
  },
59821
59773
  aqi: {
@@ -59833,22 +59785,13 @@ var airQualitySensorCapability = {
59833
59785
  * entries with `device_class: illuminance`.
59834
59786
  */
59835
59787
  var AmbientLightSensorStatusSchema = object({
59836
- /** Current illuminance in lux (lx). */
59837
- lux: number().min(0).meta({
59838
- unit: "lx",
59788
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
59789
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
59790
+ precision: 0,
59839
59791
  label: "Illuminance"
59840
- }),
59792
+ })),
59841
59793
  /** Ms epoch when the slice was last updated. */
59842
- lastFetchedAt: number(),
59843
- /** Live display unit from the upstream source (e.g. HA
59844
- * `attributes.unit_of_measurement`). The UI prefers this over the
59845
- * role's canonical unit. Absent → fall back to the canonical unit. */
59846
- unit: string().optional(),
59847
- /** Suggested decimal places for numeric display.
59848
- * Populated live from the upstream source when provided (e.g. HA
59849
- * `attributes.suggested_display_precision`). Falls back to
59850
- * auto-formatting when absent. */
59851
- precision: number().int().min(0).max(10).optional()
59794
+ lastFetchedAt: number()
59852
59795
  });
59853
59796
  var ambientLightSensorCapability = {
59854
59797
  name: "ambient-light-sensor",
@@ -59880,12 +59823,7 @@ var ambientLightSensorCapability = {
59880
59823
  } } },
59881
59824
  /** Clock fields: written, but excluded from the compare that decides
59882
59825
  * whether persisting is worth a SQLite commit. */
59883
- volatileStateFields: ["lastFetchedAt"],
59884
- customize: {
59885
- version: 1,
59886
- advanced: "derive",
59887
- targets: []
59888
- }
59826
+ volatileStateFields: ["lastFetchedAt"]
59889
59827
  };
59890
59828
  /**
59891
59829
  * Per-class audio metrics aggregated over a sliding window.
@@ -60112,10 +60050,7 @@ var BatteryStatusSchema = object({
60112
60050
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
60113
60051
  * declines to band a non-finite reading, which is the correct shape.
60114
60052
  */
60115
- percentage: number().min(0).max(100).nullable().meta({
60116
- unit: "%",
60117
- label: "Charge"
60118
- }),
60053
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
60119
60054
  /**
60120
60055
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
60121
60056
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -60142,7 +60077,14 @@ var BatteryStatusSchema = object({
60142
60077
  */
60143
60078
  sleeping: boolean().meta({ label: "Sleeping" }),
60144
60079
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60145
- lastUpdated: number(),
60080
+ lastUpdated: number().meta(readingMeta("none")),
60081
+ /**
60082
+ * Battery voltage in volts, for a cell the firmware reports as voltage
60083
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
60084
+ * that only report charge. A second reading on this cap, not a second
60085
+ * `numeric-sensor` on the same device.
60086
+ */
60087
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
60146
60088
  /**
60147
60089
  * Ms epoch of the last time the device PROVED it was reachable — a
60148
60090
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -60194,7 +60136,6 @@ var BATTERY_DEVICE_TYPES = [
60194
60136
  /** Generic Customize target for the percentage reading (D936). */
60195
60137
  var batteryCustomize = {
60196
60138
  version: 1,
60197
- advanced: "derive",
60198
60139
  targets: [{
60199
60140
  id: "percentage",
60200
60141
  cardinality: "scalar",
@@ -60203,7 +60144,7 @@ var batteryCustomize = {
60203
60144
  stateField: "percentage"
60204
60145
  },
60205
60146
  deviceTypes: BATTERY_DEVICE_TYPES,
60206
- apply: "add-or-replace",
60147
+ apply: "add",
60207
60148
  path: "percentage",
60208
60149
  sources: [{
60209
60150
  kind: "field",
@@ -60324,10 +60265,16 @@ onStatusChanged: { data: object({
60324
60265
  /**
60325
60266
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
60326
60267
  */
60327
- history: { fields: { percentage: {
60328
- kind: "gauge",
60329
- unit: "%"
60330
- } } },
60268
+ history: { fields: {
60269
+ percentage: {
60270
+ kind: "gauge",
60271
+ unit: "%"
60272
+ },
60273
+ volts: {
60274
+ kind: "gauge",
60275
+ unit: "V"
60276
+ }
60277
+ } },
60331
60278
  customize: batteryCustomize,
60332
60279
  /** Clock fields: written, but excluded from the compare that decides
60333
60280
  * whether persisting is worth a SQLite commit. */
@@ -60374,12 +60321,7 @@ var binaryCapability = {
60374
60321
  history: { fields: { on: { kind: "boolean" } } },
60375
60322
  /** Clock fields: written, but excluded from the compare that decides
60376
60323
  * whether persisting is worth a SQLite commit. */
60377
- volatileStateFields: ["lastChangedAt"],
60378
- customize: {
60379
- version: 1,
60380
- advanced: "derive",
60381
- targets: []
60382
- }
60324
+ volatileStateFields: ["lastChangedAt"]
60383
60325
  };
60384
60326
  /**
60385
60327
  * Dimmable-light brightness control. Co-exists with `switch` on the
@@ -60395,7 +60337,7 @@ var binaryCapability = {
60395
60337
  */
60396
60338
  var BrightnessStatusSchema = object({
60397
60339
  /** Current level as 0..100 inclusive. Firmware-reported. */
60398
- percentage: number().min(0).max(100),
60340
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
60399
60341
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
60400
60342
  lastChangedAt: number()
60401
60343
  });
@@ -60876,21 +60818,21 @@ var ClimateControlStatusSchema = object({
60876
60818
  /** Single setpoint in Celsius. Used by single-target modes
60877
60819
  * (heat / cool / dry). Null when the device is in a range mode
60878
60820
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
60879
- target: number().nullable(),
60821
+ target: number().nullable().meta(readingMeta("temperature")),
60880
60822
  /** Upper bound of the dual setpoint in Celsius. Populated only
60881
60823
  * in `heat_cool` mode. */
60882
- targetHigh: number().nullable(),
60824
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
60883
60825
  /** Lower bound of the dual setpoint in Celsius. Populated only
60884
60826
  * in `heat_cool` mode. */
60885
- targetLow: number().nullable(),
60827
+ targetLow: number().nullable().meta(readingMeta("temperature")),
60886
60828
  /** Target relative humidity (0..100). Null when the device has no
60887
60829
  * humidity control. */
60888
- targetHumidity: number().min(0).max(100).nullable(),
60830
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
60889
60831
  /** Current measured temperature in Celsius. Read-only — pushed by
60890
60832
  * the device's internal sensor. */
60891
- currentTemp: number().nullable(),
60833
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
60892
60834
  /** Current measured relative humidity (0..100). Read-only. */
60893
- currentHumidity: number().min(0).max(100).nullable(),
60835
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
60894
60836
  /** Vertical louver swing on/off. Null when the device has no vertical
60895
60837
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
60896
60838
  swingVertical: boolean().nullable(),
@@ -60898,11 +60840,11 @@ var ClimateControlStatusSchema = object({
60898
60840
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
60899
60841
  swingHorizontal: boolean().nullable(),
60900
60842
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
60901
- minTemp: number().nullable().optional(),
60843
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
60902
60844
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
60903
- maxTemp: number().nullable().optional(),
60845
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
60904
60846
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
60905
- tempStep: number().positive().nullable().optional(),
60847
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
60906
60848
  /** Ms epoch when the slice was last updated (push or command). */
60907
60849
  lastFetchedAt: number()
60908
60850
  });
@@ -61044,17 +60986,17 @@ var climateControlCapability = {
61044
60986
  * modes to expose in the picker without a slice fetch.
61045
60987
  */
61046
60988
  var RgbTripletSchema = object({
61047
- r: number().int().min(0).max(255),
61048
- g: number().int().min(0).max(255),
61049
- b: number().int().min(0).max(255)
60989
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
60990
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
60991
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
61050
60992
  });
61051
60993
  var HsvTripletSchema = object({
61052
60994
  /** Hue in degrees, 0..360. */
61053
- h: number().min(0).max(360),
60995
+ h: number().min(0).max(360).meta(readingMeta("none")),
61054
60996
  /** Saturation as 0..100 inclusive. */
61055
- s: number().min(0).max(100),
60997
+ s: number().min(0).max(100).meta(readingMeta("none")),
61056
60998
  /** Value/brightness as 0..100 inclusive. */
61057
- v: number().min(0).max(100)
60999
+ v: number().min(0).max(100).meta(readingMeta("none"))
61058
61000
  });
61059
61001
  var ColorInputSchema = discriminatedUnion("mode", [
61060
61002
  object({
@@ -61083,7 +61025,7 @@ var ColorStatusSchema = object({
61083
61025
  /** Populated when `mode === 'hsv'`. */
61084
61026
  hsv: HsvTripletSchema.optional(),
61085
61027
  /** Populated when `mode === 'mired'`. */
61086
- mireds: number().int().optional(),
61028
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
61087
61029
  /** Ms epoch of the last operator-driven change. */
61088
61030
  lastChangedAt: number()
61089
61031
  });
@@ -61292,18 +61234,12 @@ var FORM_OF_KIND = {
61292
61234
  function consumableFormOf(item) {
61293
61235
  return item.form ?? FORM_OF_KIND[consumableKindOf(item)];
61294
61236
  }
61295
- /** Units a remaining-life counter is reported in. Closed: the panel renders
61296
- * each. */
61297
- var ConsumableRemainingUnitSchema = _enum([
61298
- "days",
61299
- "hours",
61300
- "cycles"
61301
- ]);
61302
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
61303
- * days). `value` null = unknown (D393). */
61237
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
61238
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
61239
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
61304
61240
  var ConsumableRemainingSchema = object({
61305
- value: number().min(0).nullable(),
61306
- unit: ConsumableRemainingUnitSchema
61241
+ value: number().min(0).nullable().meta(readingMeta("device")),
61242
+ kind: ConsumableRemainingKindSchema
61307
61243
  });
61308
61244
  /** A single consumable item. Either a continuous `level` (remaining
61309
61245
  * life %, or fill % for a `supply` / `waste` item) or a discrete `status`
@@ -61316,7 +61252,7 @@ var ConsumableItemSchema = object({
61316
61252
  /** Display name. */
61317
61253
  label: string().min(1),
61318
61254
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
61319
- level: number().min(0).max(100).nullable(),
61255
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61320
61256
  /** The provider's discrete verdict when known (binary mode). */
61321
61257
  status: ConsumableStatusSchema.nullable(),
61322
61258
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -61398,8 +61334,8 @@ var consumablesCustomize = {
61398
61334
  transforms: [{ kind: "identity" }]
61399
61335
  }],
61400
61336
  literals: [{
61401
- path: "remaining.unit",
61402
- values: [...ConsumableRemainingUnitSchema.options]
61337
+ path: "remaining.kind",
61338
+ values: [...ConsumableRemainingKindSchema.options]
61403
61339
  }]
61404
61340
  },
61405
61341
  {
@@ -61528,7 +61464,6 @@ reset: method(object({
61528
61464
  },
61529
61465
  "remaining.value": {
61530
61466
  kind: "gauge",
61531
- unitFrom: "remaining.unit",
61532
61467
  deadband: 1,
61533
61468
  displayName: "Remaining"
61534
61469
  }
@@ -61598,10 +61533,9 @@ var contactCapability = {
61598
61533
  * (empty array for the other kinds). It lives in the slice because
61599
61534
  * HA `select.*` entities may update their options at runtime.
61600
61535
  *
61601
- * The `unit`/`min`/`max`/`step` fields are meaningful for
61602
- * `kind === 'numeric'` and are absent (undefined) for all other
61603
- * kinds. They are populated live from HA attributes on each state
61604
- * push — consistent with `options` and the sensor-unit approach.
61536
+ * Numeric `min` / `max` / `step` live in the slice and share the device
61537
+ * quantity (`SourceInfo.quantity`, D944). Display precision is a read-time
61538
+ * preference. A datetime control's shape is `SourceInfo.dateTimeShape`.
61605
61539
  */
61606
61540
  var ControlStatusSchema = object({
61607
61541
  kind: _enum([
@@ -61613,39 +61547,17 @@ var ControlStatusSchema = object({
61613
61547
  /** Current value. Numeric kind → number; select/text/datetime → string.
61614
61548
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
61615
61549
  * or time-only — HA accepts all three forms). */
61616
- value: union([number(), string()]),
61550
+ value: union([number().meta(readingMeta("device")), string()]),
61617
61551
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
61618
61552
  options: array(string()),
61619
61553
  /** Ms epoch when the slice was last updated. */
61620
61554
  lastChangedAt: number(),
61621
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
61622
- * Populated live from HA `attributes.unit_of_measurement` on each push.
61623
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61624
- unit: string().optional(),
61625
- /** Minimum allowed value. Populated live from HA `attributes.min`.
61626
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61627
- min: number().optional(),
61628
- /** Maximum allowed value. Populated live from HA `attributes.max`.
61629
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61630
- max: number().optional(),
61631
- /** Step increment. Populated live from HA `attributes.step`.
61632
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61633
- step: number().optional(),
61634
- /** Suggested decimal places for numeric display. Populated live from HA
61635
- * `attributes.suggested_display_precision`, or derived from `step` when the
61636
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
61637
- * other kinds. Falls back to auto-formatting when absent. */
61638
- precision: number().int().min(0).max(10).optional(),
61639
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
61640
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
61641
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
61642
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
61643
- * `has_date`/`has_time` attributes on each push. */
61644
- format: _enum([
61645
- "date",
61646
- "time",
61647
- "datetime"
61648
- ]).optional()
61555
+ /** Minimum allowed value, in the device quantity's base unit. */
61556
+ min: number().optional().meta(readingMeta("device")),
61557
+ /** Maximum allowed value, in the device quantity's base unit. */
61558
+ max: number().optional().meta(readingMeta("device")),
61559
+ /** Step increment, in the device quantity's base unit. */
61560
+ step: number().optional().meta(readingMeta("device"))
61649
61561
  });
61650
61562
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
61651
61563
  object({
@@ -61733,9 +61645,9 @@ var CoverStatusSchema = object({
61733
61645
  state: CoverStateSchema,
61734
61646
  /** 0 = fully closed, 100 = fully open. Null when the device has
61735
61647
  * no intermediate position surface. */
61736
- position: number().min(0).max(100).nullable(),
61648
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61737
61649
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
61738
- tiltPosition: number().min(0).max(100).nullable(),
61650
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61739
61651
  /** Ms epoch when the slice was last updated. */
61740
61652
  lastChangedAt: number()
61741
61653
  });
@@ -61838,9 +61750,9 @@ var NormalizedRangeSchema$1 = object({
61838
61750
  var DayNightStatusSchema = object({
61839
61751
  mode: DayNightModeSchema,
61840
61752
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
61841
- sensitivity: number().optional(),
61753
+ sensitivity: number().optional().meta(readingMeta("none")),
61842
61754
  /** Delay before the IR-cut filter flips, in seconds. */
61843
- switchDelaySec: number().optional(),
61755
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
61844
61756
  lastFetchedAt: number()
61845
61757
  });
61846
61758
  /**
@@ -62058,11 +61970,11 @@ var DishwasherStatusSchema = object({
62058
61970
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
62059
61971
  availablePrograms: array(string()),
62060
61972
  /** Seconds remaining on the active cycle, or null when not running / not known. */
62061
- remainingSeconds: number().int().nonnegative().nullable(),
61973
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62062
61974
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
62063
- delayedStartSeconds: number().int().nonnegative().nullable(),
61975
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
62064
61976
  /** Cycle completion 0..100, or null when not applicable. */
62065
- progressPercent: number().min(0).max(100).nullable(),
61977
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62066
61978
  /** True when the door is reported open; null when the door state is not known. */
62067
61979
  doorOpen: boolean().nullable(),
62068
61980
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -62086,7 +61998,7 @@ var DishwasherStatusSchema = object({
62086
61998
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
62087
61999
  machineCareDue: boolean().nullable(),
62088
62000
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
62089
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
62001
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
62090
62002
  /** Option states: null when the appliance does not expose the option or has not said. */
62091
62003
  halfLoad: boolean().nullable(),
62092
62004
  hygienePlus: boolean().nullable(),
@@ -62101,7 +62013,7 @@ var DishwasherStatusSchema = object({
62101
62013
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
62102
62014
  activeEvents: array(string()),
62103
62015
  /** Numeric error code when the appliance reports one; null otherwise. */
62104
- errorCode: number().nullable(),
62016
+ errorCode: number().nullable().meta(readingMeta("none")),
62105
62017
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
62106
62018
  errorLabel: string().nullable(),
62107
62019
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -62313,6 +62225,7 @@ var doorbellCustomize = {
62313
62225
  applyAuthority: { kind: "composition-claim" },
62314
62226
  eventName: "onPressed",
62315
62227
  edges: ["rising"],
62228
+ defaultEdge: "rising",
62316
62229
  timestampPath: "lastPressedAt",
62317
62230
  counterPath: "pressCountSinceStart",
62318
62231
  highWater: "composer-advance",
@@ -62331,7 +62244,7 @@ var DoorbellStatusSchema = object({
62331
62244
  /** Ms epoch of the last press. null = never observed since this provider started. */
62332
62245
  lastPressedAt: number().nullable(),
62333
62246
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
62334
- pressCountSinceStart: number()
62247
+ pressCountSinceStart: number().meta(readingMeta("count"))
62335
62248
  });
62336
62249
  var DoorbellPressEventSchema = object({
62337
62250
  deviceId: number(),
@@ -62396,17 +62309,7 @@ onPressed: {
62396
62309
  history: "none",
62397
62310
  customize: doorbellCustomize
62398
62311
  };
62399
- /**
62400
- * Enum-state sensor — a string value picked from a finite option set.
62401
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
62402
- * states, weather conditions, contact-source channels, mode strings).
62403
- * The option set is stable across observations; it is persisted in the
62404
- * device config blob (via the control slice at adoption) and is NOT
62405
- * stored in `sourceInfo`.
62406
- */
62407
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
62408
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
62409
- var EnumSensorDateTimeFormatSchema = _enum([
62312
+ _enum([
62410
62313
  "date",
62411
62314
  "time",
62412
62315
  "datetime"
@@ -62414,12 +62317,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
62414
62317
  var EnumSensorStatusSchema = object({
62415
62318
  value: string(),
62416
62319
  /**
62417
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
62418
- * as a locale date/time/datetime; absent for plain enum sensors. HA
62419
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
62420
- * a time-only sensor → `'time'`.
62320
+ * Set when `value` is a timestamp rather than an enum state, so history
62321
+ * does not learn ISO strings as states. The display shape (date / time /
62322
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
62421
62323
  */
62422
- format: EnumSensorDateTimeFormatSchema.optional(),
62324
+ timestamp: boolean().optional(),
62423
62325
  /** Ms epoch when the slice was last updated. */
62424
62326
  lastFetchedAt: number()
62425
62327
  });
@@ -62446,11 +62348,11 @@ var enumSensorCapability = {
62446
62348
  * History (D850): the value as an `enum` series (an appliance's program or
62447
62349
  * operation state, an HVAC action). The option set is per device, so the
62448
62350
  * value list is LEARNED as values are first seen (bounded). A sensor with a
62449
- * `format` carries a timestamp, not a state, and is not recorded.
62351
+ * `timestamp` carries a clock, not a state, and is not recorded.
62450
62352
  */
62451
62353
  history: { fields: { value: {
62452
62354
  kind: "enum",
62453
- skipWhenSet: "format"
62355
+ skipWhenSet: "timestamp"
62454
62356
  } } },
62455
62357
  /** Clock fields: written, but excluded from the compare that decides
62456
62358
  * whether persisting is worth a SQLite commit. */
@@ -62470,13 +62372,13 @@ var EventFireSchema = object({
62470
62372
  deviceId: number(),
62471
62373
  eventType: string(),
62472
62374
  data: record(string(), unknown()).nullable(),
62473
- timestamp: number(),
62474
- seq: number()
62375
+ timestamp: number().meta(readingMeta("none")),
62376
+ seq: number().meta(readingMeta("count"))
62475
62377
  });
62476
62378
  var EventEmitterStatusSchema = object({
62477
62379
  eventTypes: array(string()),
62478
62380
  lastEvent: EventFireSchema.nullable(),
62479
- eventCountSinceStart: number()
62381
+ eventCountSinceStart: number().meta(readingMeta("count"))
62480
62382
  });
62481
62383
  var eventEmitterCapability = {
62482
62384
  name: "event-emitter",
@@ -62929,10 +62831,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
62929
62831
  var FanControlStatusSchema = object({
62930
62832
  /** Active speed as 0..100 inclusive. Null when the device has no
62931
62833
  * speed surface (single-speed fan). */
62932
- percentage: number().min(0).max(100).nullable(),
62834
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62933
62835
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
62934
62836
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
62935
- percentageStep: number().positive().optional(),
62837
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
62936
62838
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
62937
62839
  * — empty string when no preset is active or supported. */
62938
62840
  preset: string(),
@@ -63053,7 +62955,7 @@ var FeatureProbeStatusSchema = object({
63053
62955
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
63054
62956
  model: string().nullable(),
63055
62957
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
63056
- channelCount: number().nullable(),
62958
+ channelCount: number().nullable().meta(readingMeta("count")),
63057
62959
  /**
63058
62960
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
63059
62961
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -63178,7 +63080,7 @@ var gasCapability = {
63178
63080
  };
63179
63081
  var HeatingValveStatusSchema = object({
63180
63082
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
63181
- position: number().min(0).max(100).nullable(),
63083
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63182
63084
  /** Ms epoch when the slice was last updated (push or poll). */
63183
63085
  lastFetchedAt: number()
63184
63086
  });
@@ -63221,9 +63123,9 @@ var HumidifierStatusSchema = object({
63221
63123
  /** Whether the humidifier is currently on. */
63222
63124
  on: boolean(),
63223
63125
  /** Current measured relative humidity (0..100). Null when not reported. */
63224
- currentHumidity: number().min(0).max(100).nullable(),
63126
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
63225
63127
  /** Target relative humidity (0..100). Null when no setpoint surface. */
63226
- targetHumidity: number().min(0).max(100).nullable(),
63128
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
63227
63129
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
63228
63130
  * device has no mode surface. */
63229
63131
  mode: string().nullable(),
@@ -63233,9 +63135,9 @@ var HumidifierStatusSchema = object({
63233
63135
  * `idle` / `off`). Null when the device doesn't report it. */
63234
63136
  action: string().nullable(),
63235
63137
  /** HA `min_humidity` attribute. Null → UI uses 0. */
63236
- minHumidity: number().nullable(),
63138
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
63237
63139
  /** HA `max_humidity` attribute. Null → UI uses 100. */
63238
- maxHumidity: number().nullable(),
63140
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
63239
63141
  /** Ms epoch when the slice was last updated. */
63240
63142
  lastChangedAt: number()
63241
63143
  });
@@ -63305,22 +63207,13 @@ var humidifierCapability = {
63305
63207
  * `sourceInfo`.
63306
63208
  */
63307
63209
  var HumiditySensorStatusSchema = object({
63308
- /** Current relative humidity, 0..100. */
63309
- percent: number().min(0).max(100).meta({
63310
- unit: "%",
63210
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
63211
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63212
+ precision: 0,
63311
63213
  label: "Humidity"
63312
- }),
63214
+ })),
63313
63215
  /** Ms epoch when the slice was last updated. */
63314
- lastFetchedAt: number(),
63315
- /** Live display unit from the upstream source (e.g. HA
63316
- * `attributes.unit_of_measurement`). The UI prefers this over the
63317
- * role's canonical unit. Absent → fall back to the canonical unit. */
63318
- unit: string().optional(),
63319
- /** Suggested decimal places for numeric display.
63320
- * Populated live from the upstream source when provided (e.g. HA
63321
- * `attributes.suggested_display_precision`). Falls back to
63322
- * auto-formatting when absent. */
63323
- precision: number().int().min(0).max(10).optional()
63216
+ lastFetchedAt: number()
63324
63217
  });
63325
63218
  var humiditySensorCapability = {
63326
63219
  name: "humidity-sensor",
@@ -63351,12 +63244,7 @@ var humiditySensorCapability = {
63351
63244
  } } },
63352
63245
  /** Clock fields: written, but excluded from the compare that decides
63353
63246
  * whether persisting is worth a SQLite commit. */
63354
- volatileStateFields: ["lastFetchedAt"],
63355
- customize: {
63356
- version: 1,
63357
- advanced: "derive",
63358
- targets: []
63359
- }
63247
+ volatileStateFields: ["lastFetchedAt"]
63360
63248
  };
63361
63249
  /**
63362
63250
  * Image display cap. Models a single still image exposed by an integration —
@@ -63377,7 +63265,7 @@ var ImageStatusSchema = object({
63377
63265
  * entity exposes no `entity_picture` (yet). */
63378
63266
  url: string().nullable(),
63379
63267
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
63380
- lastUpdated: number().nullable()
63268
+ lastUpdated: number().nullable().meta(readingMeta("none"))
63381
63269
  });
63382
63270
  var imageCapability = {
63383
63271
  name: "image",
@@ -63465,19 +63353,19 @@ var NormalizedRangeSchema = object({
63465
63353
  */
63466
63354
  var ImageSettingsStatusSchema = object({
63467
63355
  /** Normalized 0–100. */
63468
- brightness: number().optional(),
63356
+ brightness: number().optional().meta(readingMeta("none")),
63469
63357
  /** Normalized 0–100. */
63470
- contrast: number().optional(),
63358
+ contrast: number().optional().meta(readingMeta("none")),
63471
63359
  /** Normalized 0–100. */
63472
- saturation: number().optional(),
63360
+ saturation: number().optional().meta(readingMeta("none")),
63473
63361
  /** Normalized 0–100. */
63474
- sharpness: number().optional(),
63362
+ sharpness: number().optional().meta(readingMeta("none")),
63475
63363
  mirror: boolean().optional(),
63476
63364
  flip: boolean().optional(),
63477
63365
  rotate: ImageRotateSchema.optional(),
63478
63366
  whiteBalance: WhiteBalanceModeSchema.optional(),
63479
63367
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
63480
- warmth: number().optional(),
63368
+ warmth: number().optional().meta(readingMeta("none")),
63481
63369
  exposureMode: ExposureModeSchema.optional(),
63482
63370
  backlightMode: BacklightModeSchema.optional(),
63483
63371
  lastFetchedAt: number()
@@ -63756,10 +63644,10 @@ var IntercomAbilitySchema = object({
63756
63644
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
63757
63645
  codecs: array(string()),
63758
63646
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
63759
- sampleRate: number(),
63647
+ sampleRate: number().meta(readingMeta("none")),
63760
63648
  duplex: _enum(["half", "full"]),
63761
63649
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
63762
- maxBacklogMs: number()
63650
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
63763
63651
  });
63764
63652
  var IntercomStatusSchema = object({
63765
63653
  /** True while a talk session is open. */
@@ -63986,13 +63874,13 @@ var LawnMowerControlStatusSchema = object({
63986
63874
  activity: LawnMowerActivitySchema,
63987
63875
  /** 0..100 battery percentage. Null when the device has no battery
63988
63876
  * reading. */
63989
- batteryLevel: number().min(0).max(100).nullable(),
63877
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63990
63878
  /** 0..100 mowing-completion percentage of the current task, or null when no
63991
63879
  * task is active / progress is unavailable. */
63992
- progressPercent: number().min(0).max(100).nullable(),
63880
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63993
63881
  /** Current device/event code (dynamic — mostly status, sometimes an error),
63994
63882
  * or null when unknown. */
63995
- currentCode: number().nullable(),
63883
+ currentCode: number().nullable().meta(readingMeta("none")),
63996
63884
  /** Human label for {@link currentCode}, or null when undecodable. */
63997
63885
  currentCodeLabel: string().nullable(),
63998
63886
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -64546,7 +64434,7 @@ var MediaPlayerStatusSchema = object({
64546
64434
  state: MediaPlayerStateSchema,
64547
64435
  /** Volume as 0..100 inclusive. Null when the device has no volume
64548
64436
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
64549
- volumeLevel: number().min(0).max(100).nullable(),
64437
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64550
64438
  /** Mute toggle distinct from volume=0. Null when no mute surface.
64551
64439
  * Pair with `DeviceFeature.MediaPlayerMute`. */
64552
64440
  isMuted: boolean().nullable(),
@@ -64559,10 +64447,10 @@ var MediaPlayerStatusSchema = object({
64559
64447
  currentMedia: MediaInfoSchema.nullable(),
64560
64448
  /** Current playback position in ms. Null when not seekable or
64561
64449
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
64562
- positionMs: number().int().nonnegative().nullable(),
64450
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64563
64451
  /** Total duration of the current media in ms. Null when unknown
64564
64452
  * (live stream) or nothing is playing. */
64565
- durationMs: number().int().nonnegative().nullable(),
64453
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
64566
64454
  /** Shuffle toggle. Null when no shuffle surface.
64567
64455
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
64568
64456
  shuffle: boolean().nullable(),
@@ -64871,7 +64759,7 @@ var motionCustomize = {
64871
64759
  eventName: "onMotionChanged"
64872
64760
  },
64873
64761
  deviceTypes: [DeviceType.Camera],
64874
- apply: "replace",
64762
+ apply: "add",
64875
64763
  applyAuthority: {
64876
64764
  kind: "camera-sensors",
64877
64765
  timeoutField: "motionCooldownMs"
@@ -64968,7 +64856,7 @@ var MotionStatusSchema = object({
64968
64856
  * it, every provider seeded it at boot, and every session left it
64969
64857
  * `detected: true` after its detach.
64970
64858
  */
64971
- autoClearAfterMs: number().nullable()
64859
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
64972
64860
  });
64973
64861
  /**
64974
64862
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -65150,7 +65038,7 @@ var MotionZoneRegionSchema = object({
65150
65038
  * the grid region(s). */
65151
65039
  var MotionZoneStatusSchema = object({
65152
65040
  enabled: boolean(),
65153
- sensitivity: number(),
65041
+ sensitivity: number().meta(readingMeta("none")),
65154
65042
  /** Grid region(s). Today exactly one `grid` shape. */
65155
65043
  regions: array(MotionZoneRegionSchema),
65156
65044
  lastFetchedAt: number()
@@ -65520,7 +65408,7 @@ var NavigationStatusSchema = object({
65520
65408
  following: boolean(),
65521
65409
  flash: boolean(),
65522
65410
  lightMode: NavigationLightModeSchema,
65523
- lightLevel: number().min(40).max(100),
65411
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
65524
65412
  /** Ms epoch when the slice was last updated. */
65525
65413
  lastChangedAt: number()
65526
65414
  });
@@ -65654,13 +65542,13 @@ var NetworkLinkStatusSchema = object({
65654
65542
  * one whose reading has not landed must not be drawn at 0 %. Consumers
65655
65543
  * SKIP a null rather than coerce it.
65656
65544
  */
65657
- signalPercent: number().min(0).max(100).nullable(),
65545
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65658
65546
  /** Raw received signal strength in dBm, when the firmware reports one. */
65659
- rssiDbm: number().optional(),
65547
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
65660
65548
  /** Network name of a wireless link, when the firmware reports it. */
65661
65549
  ssid: string().optional(),
65662
65550
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
65663
- lastUpdated: number()
65551
+ lastUpdated: number().meta(readingMeta("none"))
65664
65552
  });
65665
65553
  var networkLinkCapability = {
65666
65554
  name: "network-link",
@@ -66045,7 +65933,7 @@ var NotifierStatusSchema = object({
66045
65933
  lastError: string().nullable(),
66046
65934
  /** Number of deliveries currently buffered server-side (rate-limited
66047
65935
  * notifiers may queue). 0 when the provider sends synchronously. */
66048
- queueDepth: number().int().nonnegative(),
65936
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
66049
65937
  /** Per-feature capability matrix — authoritative source for the
66050
65938
  * compose-form field gating. */
66051
65939
  supports: NotifierSupportsSchema
@@ -66137,11 +66025,9 @@ onSent: { data: object({
66137
66025
  * cap fits (HA `sensor` whose `device_class` we don't have a typed cap
66138
66026
  * for: water flow, distance, weight, frequency, signal strength, …).
66139
66027
  *
66140
- * The `unit` and `precision` fields carry the live display metadata that
66141
- * the upstream source provides with each state push (HA
66142
- * `attributes.unit_of_measurement` / `attributes.suggested_display_precision`).
66143
- * The UI reads them directly from the slice — they are the single source
66144
- * of truth for generic numeric rendering and do NOT live on `sourceInfo`.
66028
+ * The quantity is not on this slice. It is chosen when the device is
66029
+ * adopted and stored once on `SourceInfo.quantity` (D944). `value` is in
66030
+ * that quantity's base unit. `null` is a refused sample (D393).
66145
66031
  *
66146
66032
  * Prefer the typed alternatives (`temperature-sensor`, `humidity-sensor`,
66147
66033
  * `ambient-light-sensor`, `pressure-sensor`, `power-meter`,
@@ -66150,15 +66036,8 @@ onSent: { data: object({
66150
66036
  * numeric values land here.
66151
66037
  */
66152
66038
  var NumericSensorStatusSchema = object({
66153
- value: number(),
66154
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
66155
- * Populated live from the upstream source on each state push.
66156
- * Absent for unitless sensors. */
66157
- unit: string().optional(),
66158
- /** Suggested decimal places for numeric display.
66159
- * Populated live from the upstream source when provided.
66160
- * Falls back to auto-formatting when absent. */
66161
- precision: number().int().min(0).max(10).optional(),
66039
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
66040
+ value: number().nullable().meta(readingMeta("device")),
66162
66041
  /** Ms epoch when the slice was last updated. */
66163
66042
  lastFetchedAt: number()
66164
66043
  });
@@ -66182,11 +66061,11 @@ var numericSensorCapability = {
66182
66061
  */
66183
66062
  durability: "restored",
66184
66063
  /**
66185
- * History (D850): recorded series. The unit is per device, read from the sibling `unit` field.
66064
+ * History (D850): recorded series. The unit is the device quantity's base,
66065
+ * resolved when the series is read — the cap has no single unit.
66186
66066
  */
66187
66067
  history: { fields: { value: {
66188
66068
  kind: "gauge",
66189
- unitFrom: "unit",
66190
66069
  minIntervalMs: 3e4
66191
66070
  } } },
66192
66071
  /** Clock fields: written, but excluded from the compare that decides
@@ -66478,23 +66357,26 @@ var PetFeederStatusSchema = object({
66478
66357
  /** Food currently in the bowl (grams). Null when the device has not
66479
66358
  * reported a reading yet. On dual-hopper models this is the combined
66480
66359
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
66481
- foodLevel: number().nullable(),
66360
+ foodLevel: number().nullable().meta(readingMeta("none")),
66482
66361
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
66483
66362
  * single-hopper models. */
66484
- food1: number().nullable(),
66363
+ food1: number().nullable().meta(readingMeta("none")),
66485
66364
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
66486
66365
  * single-hopper models. */
66487
- food2: number().nullable(),
66366
+ food2: number().nullable().meta(readingMeta("none")),
66488
66367
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
66489
66368
  * (`device_class: problem`, on = low). True when the bowl is empty /
66490
66369
  * below the feeder's low threshold. */
66491
66370
  lowFood: boolean(),
66492
66371
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
66493
66372
  * device has no battery reading. */
66494
- batteryPower: number().min(0).max(100).nullable(),
66495
- /** Days of desiccant life remaining. Null when the model has no
66496
- * desiccant sensor. */
66497
- desiccantLeftDays: number().nullable(),
66373
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
66374
+ /**
66375
+ * Desiccant life remaining, stored in seconds (duration base, D944).
66376
+ * Providers convert from the days the feeder reports. Null when the
66377
+ * model has no desiccant sensor.
66378
+ */
66379
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
66498
66380
  /** True while a feed is in progress. */
66499
66381
  feeding: boolean(),
66500
66382
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -66505,7 +66387,7 @@ var PetFeederStatusSchema = object({
66505
66387
  * with `errorCode` for consumers that want the raw integer. */
66506
66388
  error: string().nullable(),
66507
66389
  /** Raw device error code (0 / null = no error). */
66508
- errorCode: number().nullable(),
66390
+ errorCode: number().nullable().meta(readingMeta("none")),
66509
66391
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
66510
66392
  isDualHopper: boolean(),
66511
66393
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -66515,7 +66397,7 @@ var PetFeederStatusSchema = object({
66515
66397
  /** Play a chime when dispensing. */
66516
66398
  feedSound: boolean(),
66517
66399
  /** Speaker / prompt volume level (device-scaled integer). */
66518
- volume: number(),
66400
+ volume: number().meta(readingMeta("none")),
66519
66401
  /** Ms epoch when the slice was last refreshed from the cloud. */
66520
66402
  lastFetchedAt: number()
66521
66403
  });
@@ -66634,7 +66516,7 @@ var petFeederCapability = {
66634
66516
  },
66635
66517
  desiccantLeftDays: {
66636
66518
  kind: "gauge",
66637
- unit: "days"
66519
+ unit: "s"
66638
66520
  }
66639
66521
  } }
66640
66522
  };
@@ -67070,36 +66952,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
67070
66952
  */
67071
66953
  var PowerMeterStatusSchema = object({
67072
66954
  /** Instantaneous power draw in watts. */
67073
- watts: number().optional().meta({
67074
- unit: "W",
67075
- label: "Power"
67076
- }),
66955
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
67077
66956
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
67078
- kwhTotal: number().optional().meta({
67079
- unit: "kWh",
67080
- label: "Energy"
67081
- }),
66957
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
67082
66958
  /** Voltage in volts. */
67083
- volts: number().optional(),
66959
+ volts: number().optional().meta(readingMeta("voltage")),
67084
66960
  /** Current in amperes. */
67085
- amps: number().optional(),
67086
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
67087
- va: number().optional(),
66961
+ amps: number().optional().meta(readingMeta("current")),
66962
+ /** Apparent power in volt-amperes. */
66963
+ va: number().optional().meta(readingMeta("apparent-power")),
67088
66964
  /** Power factor (0..1) if reported. */
67089
- powerFactor: number().min(0).max(1).optional(),
66965
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
67090
66966
  /** Ms epoch when the slice was last updated. */
67091
- lastFetchedAt: number(),
67092
- /** Live display unit of the single metric this slice carries (e.g. HA
67093
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
67094
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
67095
- * unit per slice is unambiguous. The UI prefers this over the role's
67096
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
67097
- unit: string().optional(),
67098
- /** Suggested decimal places for numeric display.
67099
- * Populated live from the upstream source when provided (e.g. HA
67100
- * `attributes.suggested_display_precision`). Falls back to
67101
- * auto-formatting when absent. */
67102
- precision: number().int().min(0).max(10).optional()
66967
+ lastFetchedAt: number()
67103
66968
  });
67104
66969
  var powerMeterCapability = {
67105
66970
  name: "power-meter",
@@ -67163,12 +67028,7 @@ var powerMeterCapability = {
67163
67028
  } },
67164
67029
  /** Clock fields: written, but excluded from the compare that decides
67165
67030
  * whether persisting is worth a SQLite commit. */
67166
- volatileStateFields: ["lastFetchedAt"],
67167
- customize: {
67168
- version: 1,
67169
- advanced: "derive",
67170
- targets: []
67171
- }
67031
+ volatileStateFields: ["lastFetchedAt"]
67172
67032
  };
67173
67033
  /**
67174
67034
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -67188,11 +67048,11 @@ var powerMeterCapability = {
67188
67048
  */
67189
67049
  var GpsLocationSchema = object({
67190
67050
  /** Latitude in decimal degrees, -90..90. */
67191
- latitude: number().min(-90).max(90),
67051
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
67192
67052
  /** Longitude in decimal degrees, -180..180. */
67193
- longitude: number().min(-180).max(180),
67053
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
67194
67054
  /** Reported accuracy in meters (lower = better). */
67195
- accuracyMeters: number().nonnegative()
67055
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
67196
67056
  });
67197
67057
  var PresenceStatusSchema = object({
67198
67058
  /** `home` / `not_home` / any user-defined zone name. */
@@ -67204,7 +67064,7 @@ var PresenceStatusSchema = object({
67204
67064
  gps: GpsLocationSchema.nullable(),
67205
67065
  /** Optional battery level of the tracking device (0..100). Null
67206
67066
  * when not reported. */
67207
- batteryPercent: number().min(0).max(100).nullable(),
67067
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
67208
67068
  /** Ms epoch when the slice was last updated. */
67209
67069
  lastChangedAt: number()
67210
67070
  });
@@ -67254,19 +67114,10 @@ var presenceCapability = {
67254
67114
  * `sourceInfo`.
67255
67115
  */
67256
67116
  var PressureSensorStatusSchema = object({
67257
- /** Current pressure in hPa. */
67258
- hpa: number(),
67117
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
67118
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
67259
67119
  /** Ms epoch when the slice was last updated. */
67260
- lastFetchedAt: number(),
67261
- /** Live display unit from the upstream source (e.g. HA
67262
- * `attributes.unit_of_measurement`). The UI prefers this over the
67263
- * role's canonical unit. Absent → fall back to the canonical unit. */
67264
- unit: string().optional(),
67265
- /** Suggested decimal places for numeric display.
67266
- * Populated live from the upstream source when provided (e.g. HA
67267
- * `attributes.suggested_display_precision`). Falls back to
67268
- * auto-formatting when absent. */
67269
- precision: number().int().min(0).max(10).optional()
67120
+ lastFetchedAt: number()
67270
67121
  });
67271
67122
  var pressureSensorCapability = {
67272
67123
  name: "pressure-sensor",
@@ -67680,8 +67531,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
67680
67531
  }), _void(), { kind: "mutation" });
67681
67532
  var PtzAutotrackSettingsSchema = object({
67682
67533
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
67683
- stopDelaySeconds: number().int().min(0).max(300),
67684
- disappearDelaySeconds: number().int().min(0).max(300)
67534
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
67535
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
67685
67536
  });
67686
67537
  /**
67687
67538
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -67835,7 +67686,7 @@ var RunningStateStatusSchema = object({
67835
67686
  /** null = unknown — never coerced to false (D393). */
67836
67687
  running: boolean().nullable(),
67837
67688
  /** Epoch ms the current boolean began; null while `running` is null. */
67838
- since: number().nullable(),
67689
+ since: number().nullable().meta(readingMeta("none")),
67839
67690
  source: RunningStateSourceSchema.meta({ label: "Source" }),
67840
67691
  phase: string().nullable(),
67841
67692
  /** null until a reading has been published (D393). */
@@ -67850,7 +67701,6 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
67850
67701
  /** Generic Customize target for the running boolean (D936). */
67851
67702
  var runningCustomize = {
67852
67703
  version: 1,
67853
- advanced: "derive",
67854
67704
  targets: [{
67855
67705
  id: "running",
67856
67706
  cardinality: "scalar",
@@ -67870,7 +67720,7 @@ var runningCustomize = {
67870
67720
  DeviceType.Thermostat,
67871
67721
  DeviceType.PetFeeder
67872
67722
  ],
67873
- apply: "add-or-replace",
67723
+ apply: "add",
67874
67724
  path: "running",
67875
67725
  sources: [
67876
67726
  {
@@ -67886,6 +67736,7 @@ var runningCustomize = {
67886
67736
  requiredUnit: "W",
67887
67737
  transforms: [{
67888
67738
  kind: "threshold",
67739
+ comparisons: ["gte", "lte"],
67889
67740
  comparison: "gte",
67890
67741
  defaultThreshold: 5,
67891
67742
  defaultStartDwellMs: RUNNING_POWER_START_DELAY_MS,
@@ -69165,9 +69016,9 @@ var RecordTriggerSchema = _enum([
69165
69016
  */
69166
69017
  var RecordWindowSchema = object({
69167
69018
  trigger: RecordTriggerSchema,
69168
- day: number().int().min(0).max(6),
69169
- startMinute: number().int().min(0).max(1439),
69170
- endMinute: number().int().min(1).max(1440)
69019
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
69020
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
69021
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
69171
69022
  });
69172
69023
  /** Status of one physical volume, as the camera itself describes it. */
69173
69024
  var OnboardStorageVolumeSchema = object({
@@ -69243,10 +69094,10 @@ var RecordingOnboardStatusSchema = object({
69243
69094
  /** Master "record to the card at all" switch. */
69244
69095
  enabled: boolean().nullable(),
69245
69096
  overwriteWhenFull: boolean().nullable(),
69246
- preRecordSec: number().nullable(),
69247
- postRecordSec: number().nullable(),
69097
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
69098
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
69248
69099
  /** Length of one recorded file, in minutes. */
69249
- segmentMinutes: number().nullable(),
69100
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
69250
69101
  /** The primary track's weekly windows, flattened. */
69251
69102
  windows: array(RecordWindowSchema),
69252
69103
  /**
@@ -69262,7 +69113,7 @@ var RecordingOnboardStatusSchema = object({
69262
69113
  * provider reporting a non-zero count also reports the schedule as not
69263
69114
  * writable.
69264
69115
  */
69265
- unreadableWindows: number(),
69116
+ unreadableWindows: number().meta(readingMeta("count")),
69266
69117
  /**
69267
69118
  * The camera is scheduled to record and has NO usable storage.
69268
69119
  *
@@ -70125,7 +69976,7 @@ var CamStreamDescriptorSchema = object({
70125
69976
  url: string().optional(),
70126
69977
  codec: string().optional(),
70127
69978
  resolution: CamStreamResolutionSchema.optional(),
70128
- fps: number().positive().optional(),
69979
+ fps: number().positive().optional().meta(readingMeta("rate")),
70129
69980
  label: string().optional(),
70130
69981
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
70131
69982
  deviceFeatures: array(string()).optional(),
@@ -70212,18 +70063,18 @@ var StreamProfileSchema = _enum([
70212
70063
  ]);
70213
70064
  /** The current encoding config of one stream profile. */
70214
70065
  var StreamProfileConfigSchema = object({
70215
- width: number(),
70216
- height: number(),
70066
+ width: number().meta(readingMeta("none")),
70067
+ height: number().meta(readingMeta("none")),
70217
70068
  codec: _enum(["h264", "h265"]),
70218
- framerate: number(),
70219
- bitrate: number(),
70069
+ framerate: number().meta(readingMeta("rate")),
70070
+ bitrate: number().meta(readingMeta("bitrate")),
70220
70071
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
70221
70072
  encoderProfile: _enum([
70222
70073
  "high",
70223
70074
  "main",
70224
70075
  "baseline"
70225
70076
  ]).optional(),
70226
- gop: number().optional(),
70077
+ gop: number().optional().meta(readingMeta("none")),
70227
70078
  /**
70228
70079
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
70229
70080
  *
@@ -70444,12 +70295,7 @@ var switchCapability = {
70444
70295
  label: "On",
70445
70296
  kind: "boolean"
70446
70297
  }
70447
- }] },
70448
- customize: {
70449
- version: 1,
70450
- advanced: "derive",
70451
- targets: []
70452
- }
70298
+ }] }
70453
70299
  };
70454
70300
  /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
70455
70301
  function hasControlCharacter(value) {
@@ -71032,35 +70878,21 @@ var tamperCapability = {
71032
70878
  * entries with `device_class: temperature` and any future native
71033
70879
  * thermometer.
71034
70880
  *
71035
- * Unit normalisation: providers convert to Celsius before storing.
71036
- * The slice value is always Celsius so cross-cap aggregators
71037
- * (climate-control's `currentTemp`, energy analytics) can compose
71038
- * without per-source unit fixups. The canonical display unit (`°C`) is
71039
- * a descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
71040
- * `sourceInfo`.
70881
+ * The number is Celsius (D944). Providers convert on ingest. A failed
70882
+ * conversion is `null`, never a guess. Display unit is a read-time preference.
71041
70883
  *
71042
70884
  * Status `lastFetchedAt` lets staleness-aware consumers detect a
71043
70885
  * frozen feed (provider hung) distinct from a "temperature hasn't
71044
70886
  * changed" steady state.
71045
70887
  */
71046
70888
  var TemperatureSensorStatusSchema = object({
71047
- /** Current temperature in Celsius. */
71048
- celsius: number().meta({
71049
- unit: "°C",
70889
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
70890
+ celsius: number().nullable().meta(readingMeta("temperature", {
70891
+ precision: 1,
71050
70892
  label: "Temperature"
71051
- }),
70893
+ })),
71052
70894
  /** Ms epoch when the slice was last updated (push or poll). */
71053
- lastFetchedAt: number(),
71054
- /** Live display unit from the upstream source (e.g. HA
71055
- * `attributes.unit_of_measurement`). The UI prefers this over the
71056
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
71057
- * Absent → the UI falls back to the role's canonical unit. */
71058
- unit: string().optional(),
71059
- /** Suggested decimal places for numeric display.
71060
- * Populated live from the upstream source when provided (e.g. HA
71061
- * `attributes.suggested_display_precision`). Falls back to
71062
- * auto-formatting when absent. */
71063
- precision: number().int().min(0).max(10).optional()
70895
+ lastFetchedAt: number()
71064
70896
  });
71065
70897
  var temperatureSensorCapability = {
71066
70898
  name: "temperature-sensor",
@@ -71091,12 +70923,7 @@ var temperatureSensorCapability = {
71091
70923
  } } },
71092
70924
  /** Clock fields: written, but excluded from the compare that decides
71093
70925
  * whether persisting is worth a SQLite commit. */
71094
- volatileStateFields: ["lastFetchedAt"],
71095
- customize: {
71096
- version: 1,
71097
- advanced: "derive",
71098
- targets: []
71099
- }
70926
+ volatileStateFields: ["lastFetchedAt"]
71100
70927
  };
71101
70928
  /**
71102
70929
  * toast — system-scoped singleton capability that streams toast
@@ -71379,16 +71206,16 @@ var VacuumControlStatusSchema = object({
71379
71206
  ]),
71380
71207
  /** 0..100 battery percentage. Null when the device has no battery
71381
71208
  * reading. */
71382
- batteryLevel: number().min(0).max(100).nullable(),
71209
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71383
71210
  /** Current fan-speed token (provider-verbatim). Null when unknown or
71384
71211
  * the vacuum has no speed control. */
71385
71212
  fanSpeed: string().nullable(),
71386
71213
  /** Speed tokens the hardware accepts — drives the UI selector. */
71387
71214
  availableFanSpeeds: array(string()),
71388
71215
  /** 0..100 cleaning-completion percentage of the current task, or null. */
71389
- progressPercent: number().min(0).max(100).nullable(),
71216
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71390
71217
  /** Current error code (0 / null = no error). */
71391
- errorCode: number().nullable(),
71218
+ errorCode: number().nullable().meta(readingMeta("none")),
71392
71219
  /** Human label for {@link errorCode}, or null when none / undecodable. */
71393
71220
  errorLabel: string().nullable(),
71394
71221
  /** Ms epoch when the slice was last updated. */
@@ -71466,7 +71293,7 @@ var ValveStatusSchema = object({
71466
71293
  ]),
71467
71294
  /** 0 = fully closed, 100 = fully open. Null when the device has no
71468
71295
  * intermediate position surface. */
71469
- position: number().min(0).max(100).nullable(),
71296
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
71470
71297
  /** Ms epoch when the slice was last updated. */
71471
71298
  lastChangedAt: number()
71472
71299
  });
@@ -71574,9 +71401,9 @@ var vibrationCapability = {
71574
71401
  */
71575
71402
  var WaterHeaterStatusSchema = object({
71576
71403
  /** Current measured temperature. Null when not reported. */
71577
- currentTemp: number().nullable(),
71404
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
71578
71405
  /** Target temperature setpoint. Null when no setpoint surface. */
71579
- targetTemp: number().nullable(),
71406
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
71580
71407
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
71581
71408
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
71582
71409
  * device reports an unknown state. */
@@ -71587,9 +71414,9 @@ var WaterHeaterStatusSchema = object({
71587
71414
  * has no away surface. */
71588
71415
  away: boolean().nullable(),
71589
71416
  /** HA `min_temp` attribute. Null when not reported. */
71590
- minTemp: number().nullable(),
71417
+ minTemp: number().nullable().meta(readingMeta("temperature")),
71591
71418
  /** HA `max_temp` attribute. Null when not reported. */
71592
- maxTemp: number().nullable(),
71419
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
71593
71420
  /** Ms epoch when the slice was last updated. */
71594
71421
  lastChangedAt: number()
71595
71422
  });
@@ -71674,25 +71501,18 @@ var waterHeaterCapability = {
71674
71501
  * Forecast deferred — current conditions only for v1.
71675
71502
  */
71676
71503
  var WeatherStatusSchema = object({
71677
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
71678
- * when no condition has been reported yet. */
71504
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
71679
71505
  condition: string().nullable(),
71680
- /** Current temperature in the reported unit. Null when not provided. */
71681
- temperature: number().nullable(),
71682
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
71683
- temperatureUnit: string().nullable(),
71506
+ /** Current temperature in Celsius. Null when not provided or refused. */
71507
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
71684
71508
  /** Relative humidity (0..100). Null when not provided. */
71685
- humidity: number().min(0).max(100).nullable(),
71686
- /** Barometric pressure in the reported unit. Null when not provided. */
71687
- pressure: number().nullable(),
71688
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
71689
- pressureUnit: string().nullable(),
71690
- /** Wind speed in the reported unit. Null when not provided. */
71691
- windSpeed: number().nullable(),
71692
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
71693
- windSpeedUnit: string().nullable(),
71509
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
71510
+ /** Barometric pressure in hPa. Null when not provided or refused. */
71511
+ pressure: number().nullable().meta(readingMeta("pressure")),
71512
+ /** Wind speed in metres per second. Null when not provided or refused. */
71513
+ windSpeed: number().nullable().meta(readingMeta("speed")),
71694
71514
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
71695
- windBearing: number().nullable(),
71515
+ windBearing: number().nullable().meta(readingMeta("angle")),
71696
71516
  /** Ms epoch when the slice was last updated. */
71697
71517
  lastFetchedAt: number()
71698
71518
  });
@@ -71720,12 +71540,12 @@ var weatherCapability = {
71720
71540
  */
71721
71541
  durability: "session",
71722
71542
  /**
71723
- * History (D850): recorded series. Units follow the sibling `*Unit` fields.
71543
+ * History (D850): recorded series. Numbers are the quantity base (D944).
71724
71544
  */
71725
71545
  history: { fields: {
71726
71546
  temperature: {
71727
71547
  kind: "gauge",
71728
- unitFrom: "temperatureUnit",
71548
+ unit: "°C",
71729
71549
  deadband: .2
71730
71550
  },
71731
71551
  humidity: {
@@ -71735,12 +71555,12 @@ var weatherCapability = {
71735
71555
  },
71736
71556
  pressure: {
71737
71557
  kind: "gauge",
71738
- unitFrom: "pressureUnit",
71558
+ unit: "hPa",
71739
71559
  deadband: .5
71740
71560
  },
71741
71561
  windSpeed: {
71742
71562
  kind: "gauge",
71743
- unitFrom: "windSpeedUnit",
71563
+ unit: "m/s",
71744
71564
  deadband: .5
71745
71565
  }
71746
71566
  } }
@@ -86880,7 +86700,6 @@ var SENSOR_CAP_VOCABULARY = {
86880
86700
  field: "value",
86881
86701
  quantity: t("the reading", "il valore"),
86882
86702
  label: t("Reading", "Valore"),
86883
- unitField: "unit",
86884
86703
  icon: "gauge"
86885
86704
  }]
86886
86705
  },
@@ -87016,7 +86835,7 @@ var SENSOR_CAP_VOCABULARY = {
87016
86835
  field: "temperature",
87017
86836
  quantity: t("the outdoor temperature", "la temperatura esterna"),
87018
86837
  label: t("Temperature", "Temperatura"),
87019
- unitField: "temperatureUnit",
86838
+ unit: "°C",
87020
86839
  icon: "thermometer"
87021
86840
  },
87022
86841
  {
@@ -87032,7 +86851,7 @@ var SENSOR_CAP_VOCABULARY = {
87032
86851
  field: "windSpeed",
87033
86852
  quantity: t("the wind speed", "la velocità del vento"),
87034
86853
  label: t("Wind", "Vento"),
87035
- unitField: "windSpeedUnit",
86854
+ unit: "m/s",
87036
86855
  icon: "wind"
87037
86856
  },
87038
86857
  {
@@ -87040,7 +86859,7 @@ var SENSOR_CAP_VOCABULARY = {
87040
86859
  field: "pressure",
87041
86860
  quantity: t("the air pressure", "la pressione atmosferica"),
87042
86861
  label: t("Pressure", "Pressione"),
87043
- unitField: "pressureUnit",
86862
+ unit: "hPa",
87044
86863
  icon: "gauge"
87045
86864
  }
87046
86865
  ]
@@ -90274,6 +90093,11 @@ var PulseOverrideDraftSchema = object({
90274
90093
  targetId: string().min(1),
90275
90094
  enabled: boolean(),
90276
90095
  debounceMs: number().nonnegative(),
90096
+ edges: array(_enum([
90097
+ "rising",
90098
+ "button",
90099
+ "native-event"
90100
+ ])).min(1).optional(),
90277
90101
  sources: array(OverrideSourceSchema).min(1)
90278
90102
  });
90279
90103
  var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([