@camstack/addon-matter-broker 0.2.147 → 0.2.148

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 (3) hide show
  1. package/dist/addon.js +312 -496
  2. package/dist/addon.mjs +312 -496
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -5581,7 +5581,7 @@ var ZodIssueCode = {
5581
5581
  var ZodFirstPartyTypeKind;
5582
5582
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5583
5583
  //#endregion
5584
- //#region ../types/dist/sleep-BErAxhul.mjs
5584
+ //#region ../types/dist/sleep-CARd6wZF.mjs
5585
5585
  /**
5586
5586
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5587
5587
  * window to float samples (D455).
@@ -8323,6 +8323,52 @@ function defineEntryKind(name, spec) {
8323
8323
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8324
8324
  return defined;
8325
8325
  }
8326
+ var READING_QUANTITIES = [
8327
+ "temperature",
8328
+ "pressure",
8329
+ "speed",
8330
+ "precipitation",
8331
+ "precipitation-rate",
8332
+ "length",
8333
+ "area",
8334
+ "illuminance",
8335
+ "irradiance",
8336
+ "power",
8337
+ "energy",
8338
+ "voltage",
8339
+ "current",
8340
+ "apparent-power",
8341
+ "humidity",
8342
+ "percentage",
8343
+ "concentration-volume",
8344
+ "concentration-mass",
8345
+ "concentration-ppb",
8346
+ "volume",
8347
+ "duration",
8348
+ "duration-ms",
8349
+ "information",
8350
+ "sound-level",
8351
+ "signal-level",
8352
+ "colour-temperature",
8353
+ "angle",
8354
+ "bitrate",
8355
+ "rate",
8356
+ "mass",
8357
+ "index",
8358
+ "ratio",
8359
+ "count",
8360
+ "none",
8361
+ "device"
8362
+ ];
8363
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8364
+ new Set(READING_QUANTITIES);
8365
+ function readingMeta(quantity, extras) {
8366
+ return {
8367
+ quantity,
8368
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8369
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8370
+ };
8371
+ }
8326
8372
  /** Shared Zod schemas used across streaming capabilities. */
8327
8373
  var CamProfileSchema = _enum([
8328
8374
  "high",
@@ -8350,8 +8396,8 @@ var CamStreamKindSchema = _enum([
8350
8396
  "derived"
8351
8397
  ]);
8352
8398
  var CamStreamResolutionSchema = object({
8353
- width: number().int().positive(),
8354
- height: number().int().positive()
8399
+ width: number().int().positive().meta(readingMeta("none")),
8400
+ height: number().int().positive().meta(readingMeta("none"))
8355
8401
  });
8356
8402
  var CameraStreamSchema = object({
8357
8403
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8998,14 +9044,6 @@ Object.freeze({
8998
9044
  {
8999
9045
  "path": "aqi",
9000
9046
  "valueType": "number"
9001
- },
9002
- {
9003
- "path": "unit",
9004
- "valueType": "string"
9005
- },
9006
- {
9007
- "path": "precision",
9008
- "valueType": "number"
9009
9047
  }
9010
9048
  ],
9011
9049
  "alarm-panel": [
@@ -9041,20 +9079,10 @@ Object.freeze({
9041
9079
  "valueType": "boolean"
9042
9080
  }
9043
9081
  ],
9044
- "ambient-light-sensor": [
9045
- {
9046
- "path": "lux",
9047
- "valueType": "number"
9048
- },
9049
- {
9050
- "path": "unit",
9051
- "valueType": "string"
9052
- },
9053
- {
9054
- "path": "precision",
9055
- "valueType": "number"
9056
- }
9057
- ],
9082
+ "ambient-light-sensor": [{
9083
+ "path": "lux",
9084
+ "valueType": "number"
9085
+ }],
9058
9086
  "audio-metrics": [
9059
9087
  {
9060
9088
  "path": "ts",
@@ -9134,6 +9162,10 @@ Object.freeze({
9134
9162
  "path": "lastUpdated",
9135
9163
  "valueType": "number"
9136
9164
  },
9165
+ {
9166
+ "path": "volts",
9167
+ "valueType": "number"
9168
+ },
9137
9169
  {
9138
9170
  "path": "lastContactAt",
9139
9171
  "valueType": "number"
@@ -9376,10 +9408,6 @@ Object.freeze({
9376
9408
  "datetime"
9377
9409
  ]
9378
9410
  },
9379
- {
9380
- "path": "unit",
9381
- "valueType": "string"
9382
- },
9383
9411
  {
9384
9412
  "path": "min",
9385
9413
  "valueType": "number"
@@ -9391,19 +9419,6 @@ Object.freeze({
9391
9419
  {
9392
9420
  "path": "step",
9393
9421
  "valueType": "number"
9394
- },
9395
- {
9396
- "path": "precision",
9397
- "valueType": "number"
9398
- },
9399
- {
9400
- "path": "format",
9401
- "valueType": "enum",
9402
- "enumValues": [
9403
- "date",
9404
- "time",
9405
- "datetime"
9406
- ]
9407
9422
  }
9408
9423
  ],
9409
9424
  "cover": [
@@ -9644,13 +9659,8 @@ Object.freeze({
9644
9659
  "path": "value",
9645
9660
  "valueType": "string"
9646
9661
  }, {
9647
- "path": "format",
9648
- "valueType": "enum",
9649
- "enumValues": [
9650
- "date",
9651
- "time",
9652
- "datetime"
9653
- ]
9662
+ "path": "timestamp",
9663
+ "valueType": "boolean"
9654
9664
  }],
9655
9665
  "event-emitter": [
9656
9666
  {
@@ -9757,20 +9767,10 @@ Object.freeze({
9757
9767
  "valueType": "number"
9758
9768
  }
9759
9769
  ],
9760
- "humidity-sensor": [
9761
- {
9762
- "path": "percent",
9763
- "valueType": "number"
9764
- },
9765
- {
9766
- "path": "unit",
9767
- "valueType": "string"
9768
- },
9769
- {
9770
- "path": "precision",
9771
- "valueType": "number"
9772
- }
9773
- ],
9770
+ "humidity-sensor": [{
9771
+ "path": "percent",
9772
+ "valueType": "number"
9773
+ }],
9774
9774
  "image": [{
9775
9775
  "path": "url",
9776
9776
  "valueType": "string"
@@ -10109,20 +10109,10 @@ Object.freeze({
10109
10109
  "valueType": "boolean"
10110
10110
  }
10111
10111
  ],
10112
- "numeric-sensor": [
10113
- {
10114
- "path": "value",
10115
- "valueType": "number"
10116
- },
10117
- {
10118
- "path": "unit",
10119
- "valueType": "string"
10120
- },
10121
- {
10122
- "path": "precision",
10123
- "valueType": "number"
10124
- }
10125
- ],
10112
+ "numeric-sensor": [{
10113
+ "path": "value",
10114
+ "valueType": "number"
10115
+ }],
10126
10116
  "pet-feeder": [
10127
10117
  {
10128
10118
  "path": "foodLevel",
@@ -10214,14 +10204,6 @@ Object.freeze({
10214
10204
  {
10215
10205
  "path": "powerFactor",
10216
10206
  "valueType": "number"
10217
- },
10218
- {
10219
- "path": "unit",
10220
- "valueType": "string"
10221
- },
10222
- {
10223
- "path": "precision",
10224
- "valueType": "number"
10225
10207
  }
10226
10208
  ],
10227
10209
  "presence": [
@@ -10250,20 +10232,10 @@ Object.freeze({
10250
10232
  "valueType": "number"
10251
10233
  }
10252
10234
  ],
10253
- "pressure-sensor": [
10254
- {
10255
- "path": "hpa",
10256
- "valueType": "number"
10257
- },
10258
- {
10259
- "path": "unit",
10260
- "valueType": "string"
10261
- },
10262
- {
10263
- "path": "precision",
10264
- "valueType": "number"
10265
- }
10266
- ],
10235
+ "pressure-sensor": [{
10236
+ "path": "hpa",
10237
+ "valueType": "number"
10238
+ }],
10267
10239
  "privacy-mask": [{
10268
10240
  "path": "enabled",
10269
10241
  "valueType": "boolean"
@@ -10574,20 +10546,10 @@ Object.freeze({
10574
10546
  "path": "tampered",
10575
10547
  "valueType": "boolean"
10576
10548
  }],
10577
- "temperature-sensor": [
10578
- {
10579
- "path": "celsius",
10580
- "valueType": "number"
10581
- },
10582
- {
10583
- "path": "unit",
10584
- "valueType": "string"
10585
- },
10586
- {
10587
- "path": "precision",
10588
- "valueType": "number"
10589
- }
10590
- ],
10549
+ "temperature-sensor": [{
10550
+ "path": "celsius",
10551
+ "valueType": "number"
10552
+ }],
10591
10553
  "update": [
10592
10554
  {
10593
10555
  "path": "currentVersion",
@@ -10698,10 +10660,6 @@ Object.freeze({
10698
10660
  "path": "temperature",
10699
10661
  "valueType": "number"
10700
10662
  },
10701
- {
10702
- "path": "temperatureUnit",
10703
- "valueType": "string"
10704
- },
10705
10663
  {
10706
10664
  "path": "humidity",
10707
10665
  "valueType": "number"
@@ -10710,18 +10668,10 @@ Object.freeze({
10710
10668
  "path": "pressure",
10711
10669
  "valueType": "number"
10712
10670
  },
10713
- {
10714
- "path": "pressureUnit",
10715
- "valueType": "string"
10716
- },
10717
10671
  {
10718
10672
  "path": "windSpeed",
10719
10673
  "valueType": "number"
10720
10674
  },
10721
- {
10722
- "path": "windSpeedUnit",
10723
- "valueType": "string"
10724
- },
10725
10675
  {
10726
10676
  "path": "windBearing",
10727
10677
  "valueType": "number"
@@ -18234,7 +18184,8 @@ var CompositionProblemSchema = object({
18234
18184
  "item-field-reserved",
18235
18185
  "target-missing",
18236
18186
  "target-unreadable",
18237
- "not-replaceable"
18187
+ "not-replaceable",
18188
+ "native-capability-replace"
18238
18189
  ]),
18239
18190
  path: string$2(),
18240
18191
  message: string$2()
@@ -18292,9 +18243,11 @@ var CompositionFieldPlanSchema = object({
18292
18243
  reads: array(CompositionFieldReadSchema)
18293
18244
  });
18294
18245
  /**
18295
- * What a feature does to its target (D663): `new` builds a capability on a new
18296
- * device; on an existing device it `add`s a capability the device lacks, or
18297
- * `replace`s named fields of one it has natively.
18246
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
18247
+ * new device; on an existing device it `add`s a capability the device lacks.
18248
+ * Replacing a capability the device already serves is refused
18249
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
18250
+ * wire for a claim the hub already holds.
18298
18251
  */
18299
18252
  var CompositionFeatureModeSchema = _enum([
18300
18253
  "new",
@@ -20180,12 +20133,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
20180
20133
  * providers detect renames by matching `uniqueId` and patch `id`
20181
20134
  * in-place so dispatch keeps working.
20182
20135
  *
20183
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
20184
- * `precision`) live in the cap STATUS SLICE so they flow live with every
20185
- * state push and are the single source of truth. The `device_class` tag is
20186
- * an HA-specific input that is consumed at the discovery boundary
20187
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
20188
- * stored after that point.
20136
+ * **Display unit and precision are NOT stored here.** They are a read-time
20137
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20138
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20139
+ * neither is repeated on each sample. The `device_class` tag is an
20140
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20141
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
20189
20142
  *
20190
20143
  * The `raw` escape hatch carries provider-specific attributes that don't
20191
20144
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -20199,6 +20152,14 @@ var SourceInfoSchema = object({
20199
20152
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
20200
20153
  * Used to detect rename when `id` changes. */
20201
20154
  uniqueId: string$2().optional(),
20155
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20156
+ quantity: ReadingQuantitySchema.optional(),
20157
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20158
+ dateTimeShape: _enum([
20159
+ "date",
20160
+ "time",
20161
+ "datetime"
20162
+ ]).optional(),
20202
20163
  /** Provider-specific extras that don't fit the structured slots.
20203
20164
  * Whitelist what goes here per provider — do NOT dump entire HA
20204
20165
  * attribute blobs (would explode the meta JSON column). */
@@ -41320,7 +41281,8 @@ method(object({
41320
41281
  "enum"
41321
41282
  ]),
41322
41283
  enumValues: array(string$2()).optional(),
41323
- item: boolean().optional()
41284
+ item: boolean().optional(),
41285
+ quantity: ReadingQuantitySchema.optional()
41324
41286
  })).readonly(),
41325
41287
  itemArray: object({
41326
41288
  path: string$2(),
@@ -45164,8 +45126,8 @@ var MaskPolygonShapeSchema = object({
45164
45126
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
45165
45127
  var MaskGridShapeSchema = object({
45166
45128
  kind: literal("grid"),
45167
- gridWidth: number(),
45168
- gridHeight: number(),
45129
+ gridWidth: number().meta(readingMeta("none")),
45130
+ gridHeight: number().meta(readingMeta("none")),
45169
45131
  cells: array(boolean())
45170
45132
  });
45171
45133
  discriminatedUnion("kind", [
@@ -56645,27 +56607,17 @@ method(object({ nodeId: string$2().optional() }).optional(), array(AddonListItem
56645
56607
  */
56646
56608
  var AirQualitySensorStatusSchema = object({
56647
56609
  /** Carbon dioxide concentration in ppm. */
56648
- co2Ppm: number().min(0).optional(),
56610
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
56649
56611
  /** Total volatile organic compounds in ppb. */
56650
- vocPpb: number().min(0).optional(),
56612
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56651
56613
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56652
- pm25: number().min(0).optional(),
56614
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56653
56615
  /** Particulate matter ≤ 10 μm in µg/m³. */
56654
- pm10: number().min(0).optional(),
56616
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
56655
56617
  /** Composite AQI value (typically 0..500). */
56656
- aqi: number().optional(),
56618
+ aqi: number().optional().meta(readingMeta("index")),
56657
56619
  /** Ms epoch when the slice was last updated. */
56658
- lastFetchedAt: number(),
56659
- /** Live display unit of the single metric this slice carries (e.g. HA
56660
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
56661
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
56662
- * per slice is unambiguous. */
56663
- unit: string$2().optional(),
56664
- /** Suggested decimal places for numeric display.
56665
- * Populated live from the upstream source when provided (e.g. HA
56666
- * `attributes.suggested_display_precision`). Falls back to
56667
- * auto-formatting when absent. */
56668
- precision: number().int().min(0).max(10).optional()
56620
+ lastFetchedAt: number()
56669
56621
  });
56670
56622
  var airQualitySensorCapability = {
56671
56623
  name: "air-quality-sensor",
@@ -56702,12 +56654,12 @@ var airQualitySensorCapability = {
56702
56654
  },
56703
56655
  pm25: {
56704
56656
  kind: "gauge",
56705
- unit: "ug/m3",
56657
+ unit: "µg/m³",
56706
56658
  deadband: 1
56707
56659
  },
56708
56660
  pm10: {
56709
56661
  kind: "gauge",
56710
- unit: "ug/m3",
56662
+ unit: "µg/m³",
56711
56663
  deadband: 1
56712
56664
  },
56713
56665
  aqi: {
@@ -56725,22 +56677,13 @@ var airQualitySensorCapability = {
56725
56677
  * entries with `device_class: illuminance`.
56726
56678
  */
56727
56679
  var AmbientLightSensorStatusSchema = object({
56728
- /** Current illuminance in lux (lx). */
56729
- lux: number().min(0).meta({
56730
- unit: "lx",
56680
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56681
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
56682
+ precision: 0,
56731
56683
  label: "Illuminance"
56732
- }),
56684
+ })),
56733
56685
  /** Ms epoch when the slice was last updated. */
56734
- lastFetchedAt: number(),
56735
- /** Live display unit from the upstream source (e.g. HA
56736
- * `attributes.unit_of_measurement`). The UI prefers this over the
56737
- * role's canonical unit. Absent → fall back to the canonical unit. */
56738
- unit: string$2().optional(),
56739
- /** Suggested decimal places for numeric display.
56740
- * Populated live from the upstream source when provided (e.g. HA
56741
- * `attributes.suggested_display_precision`). Falls back to
56742
- * auto-formatting when absent. */
56743
- precision: number().int().min(0).max(10).optional()
56686
+ lastFetchedAt: number()
56744
56687
  });
56745
56688
  var ambientLightSensorCapability = {
56746
56689
  name: "ambient-light-sensor",
@@ -56772,12 +56715,7 @@ var ambientLightSensorCapability = {
56772
56715
  } } },
56773
56716
  /** Clock fields: written, but excluded from the compare that decides
56774
56717
  * whether persisting is worth a SQLite commit. */
56775
- volatileStateFields: ["lastFetchedAt"],
56776
- customize: {
56777
- version: 1,
56778
- advanced: "derive",
56779
- targets: []
56780
- }
56718
+ volatileStateFields: ["lastFetchedAt"]
56781
56719
  };
56782
56720
  /**
56783
56721
  * Per-class audio metrics aggregated over a sliding window.
@@ -57004,10 +56942,7 @@ var BatteryStatusSchema = object({
57004
56942
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
57005
56943
  * declines to band a non-finite reading, which is the correct shape.
57006
56944
  */
57007
- percentage: number().min(0).max(100).nullable().meta({
57008
- unit: "%",
57009
- label: "Charge"
57010
- }),
56945
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
57011
56946
  /**
57012
56947
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
57013
56948
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -57034,7 +56969,14 @@ var BatteryStatusSchema = object({
57034
56969
  */
57035
56970
  sleeping: boolean().meta({ label: "Sleeping" }),
57036
56971
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
57037
- lastUpdated: number(),
56972
+ lastUpdated: number().meta(readingMeta("none")),
56973
+ /**
56974
+ * Battery voltage in volts, for a cell the firmware reports as voltage
56975
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
56976
+ * that only report charge. A second reading on this cap, not a second
56977
+ * `numeric-sensor` on the same device.
56978
+ */
56979
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
57038
56980
  /**
57039
56981
  * Ms epoch of the last time the device PROVED it was reachable — a
57040
56982
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -57086,7 +57028,6 @@ var BATTERY_DEVICE_TYPES = [
57086
57028
  /** Generic Customize target for the percentage reading (D936). */
57087
57029
  var batteryCustomize = {
57088
57030
  version: 1,
57089
- advanced: "derive",
57090
57031
  targets: [{
57091
57032
  id: "percentage",
57092
57033
  cardinality: "scalar",
@@ -57095,7 +57036,7 @@ var batteryCustomize = {
57095
57036
  stateField: "percentage"
57096
57037
  },
57097
57038
  deviceTypes: BATTERY_DEVICE_TYPES,
57098
- apply: "add-or-replace",
57039
+ apply: "add",
57099
57040
  path: "percentage",
57100
57041
  sources: [{
57101
57042
  kind: "field",
@@ -57216,10 +57157,16 @@ onStatusChanged: { data: object({
57216
57157
  /**
57217
57158
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
57218
57159
  */
57219
- history: { fields: { percentage: {
57220
- kind: "gauge",
57221
- unit: "%"
57222
- } } },
57160
+ history: { fields: {
57161
+ percentage: {
57162
+ kind: "gauge",
57163
+ unit: "%"
57164
+ },
57165
+ volts: {
57166
+ kind: "gauge",
57167
+ unit: "V"
57168
+ }
57169
+ } },
57223
57170
  customize: batteryCustomize,
57224
57171
  /** Clock fields: written, but excluded from the compare that decides
57225
57172
  * whether persisting is worth a SQLite commit. */
@@ -57266,12 +57213,7 @@ var binaryCapability = {
57266
57213
  history: { fields: { on: { kind: "boolean" } } },
57267
57214
  /** Clock fields: written, but excluded from the compare that decides
57268
57215
  * whether persisting is worth a SQLite commit. */
57269
- volatileStateFields: ["lastChangedAt"],
57270
- customize: {
57271
- version: 1,
57272
- advanced: "derive",
57273
- targets: []
57274
- }
57216
+ volatileStateFields: ["lastChangedAt"]
57275
57217
  };
57276
57218
  /**
57277
57219
  * Dimmable-light brightness control. Co-exists with `switch` on the
@@ -57287,7 +57229,7 @@ var binaryCapability = {
57287
57229
  */
57288
57230
  var BrightnessStatusSchema = object({
57289
57231
  /** Current level as 0..100 inclusive. Firmware-reported. */
57290
- percentage: number().min(0).max(100),
57232
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
57291
57233
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
57292
57234
  lastChangedAt: number()
57293
57235
  });
@@ -57758,21 +57700,21 @@ var ClimateControlStatusSchema = object({
57758
57700
  /** Single setpoint in Celsius. Used by single-target modes
57759
57701
  * (heat / cool / dry). Null when the device is in a range mode
57760
57702
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
57761
- target: number().nullable(),
57703
+ target: number().nullable().meta(readingMeta("temperature")),
57762
57704
  /** Upper bound of the dual setpoint in Celsius. Populated only
57763
57705
  * in `heat_cool` mode. */
57764
- targetHigh: number().nullable(),
57706
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
57765
57707
  /** Lower bound of the dual setpoint in Celsius. Populated only
57766
57708
  * in `heat_cool` mode. */
57767
- targetLow: number().nullable(),
57709
+ targetLow: number().nullable().meta(readingMeta("temperature")),
57768
57710
  /** Target relative humidity (0..100). Null when the device has no
57769
57711
  * humidity control. */
57770
- targetHumidity: number().min(0).max(100).nullable(),
57712
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
57771
57713
  /** Current measured temperature in Celsius. Read-only — pushed by
57772
57714
  * the device's internal sensor. */
57773
- currentTemp: number().nullable(),
57715
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
57774
57716
  /** Current measured relative humidity (0..100). Read-only. */
57775
- currentHumidity: number().min(0).max(100).nullable(),
57717
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
57776
57718
  /** Vertical louver swing on/off. Null when the device has no vertical
57777
57719
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
57778
57720
  swingVertical: boolean().nullable(),
@@ -57780,11 +57722,11 @@ var ClimateControlStatusSchema = object({
57780
57722
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
57781
57723
  swingHorizontal: boolean().nullable(),
57782
57724
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
57783
- minTemp: number().nullable().optional(),
57725
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
57784
57726
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
57785
- maxTemp: number().nullable().optional(),
57727
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
57786
57728
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
57787
- tempStep: number().positive().nullable().optional(),
57729
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
57788
57730
  /** Ms epoch when the slice was last updated (push or command). */
57789
57731
  lastFetchedAt: number()
57790
57732
  });
@@ -57926,17 +57868,17 @@ var climateControlCapability = {
57926
57868
  * modes to expose in the picker without a slice fetch.
57927
57869
  */
57928
57870
  var RgbTripletSchema = object({
57929
- r: number().int().min(0).max(255),
57930
- g: number().int().min(0).max(255),
57931
- b: number().int().min(0).max(255)
57871
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
57872
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
57873
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
57932
57874
  });
57933
57875
  var HsvTripletSchema = object({
57934
57876
  /** Hue in degrees, 0..360. */
57935
- h: number().min(0).max(360),
57877
+ h: number().min(0).max(360).meta(readingMeta("none")),
57936
57878
  /** Saturation as 0..100 inclusive. */
57937
- s: number().min(0).max(100),
57879
+ s: number().min(0).max(100).meta(readingMeta("none")),
57938
57880
  /** Value/brightness as 0..100 inclusive. */
57939
- v: number().min(0).max(100)
57881
+ v: number().min(0).max(100).meta(readingMeta("none"))
57940
57882
  });
57941
57883
  var ColorInputSchema = discriminatedUnion("mode", [
57942
57884
  object({
@@ -57965,7 +57907,7 @@ var ColorStatusSchema = object({
57965
57907
  /** Populated when `mode === 'hsv'`. */
57966
57908
  hsv: HsvTripletSchema.optional(),
57967
57909
  /** Populated when `mode === 'mired'`. */
57968
- mireds: number().int().optional(),
57910
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
57969
57911
  /** Ms epoch of the last operator-driven change. */
57970
57912
  lastChangedAt: number()
57971
57913
  });
@@ -58160,18 +58102,12 @@ _enum([
58160
58102
  "empty",
58161
58103
  "full"
58162
58104
  ]);
58163
- /** Units a remaining-life counter is reported in. Closed: the panel renders
58164
- * each. */
58165
- var ConsumableRemainingUnitSchema = _enum([
58166
- "days",
58167
- "hours",
58168
- "cycles"
58169
- ]);
58170
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
58171
- * days). `value` null = unknown (D393). */
58105
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
58106
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
58107
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
58172
58108
  var ConsumableRemainingSchema = object({
58173
- value: number().min(0).nullable(),
58174
- unit: ConsumableRemainingUnitSchema
58109
+ value: number().min(0).nullable().meta(readingMeta("device")),
58110
+ kind: ConsumableRemainingKindSchema
58175
58111
  });
58176
58112
  /** A single consumable item. Either a continuous `level` (remaining
58177
58113
  * life %, or fill % for a `supply` / `waste` item) or a discrete `status`
@@ -58184,7 +58120,7 @@ var ConsumableItemSchema = object({
58184
58120
  /** Display name. */
58185
58121
  label: string$2().min(1),
58186
58122
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
58187
- level: number().min(0).max(100).nullable(),
58123
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58188
58124
  /** The provider's discrete verdict when known (binary mode). */
58189
58125
  status: ConsumableStatusSchema.nullable(),
58190
58126
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -58266,8 +58202,8 @@ var consumablesCustomize = {
58266
58202
  transforms: [{ kind: "identity" }]
58267
58203
  }],
58268
58204
  literals: [{
58269
- path: "remaining.unit",
58270
- values: [...ConsumableRemainingUnitSchema.options]
58205
+ path: "remaining.kind",
58206
+ values: [...ConsumableRemainingKindSchema.options]
58271
58207
  }]
58272
58208
  },
58273
58209
  {
@@ -58396,7 +58332,6 @@ reset: method(object({
58396
58332
  },
58397
58333
  "remaining.value": {
58398
58334
  kind: "gauge",
58399
- unitFrom: "remaining.unit",
58400
58335
  deadband: 1,
58401
58336
  displayName: "Remaining"
58402
58337
  }
@@ -58466,10 +58401,9 @@ var contactCapability = {
58466
58401
  * (empty array for the other kinds). It lives in the slice because
58467
58402
  * HA `select.*` entities may update their options at runtime.
58468
58403
  *
58469
- * The `unit`/`min`/`max`/`step` fields are meaningful for
58470
- * `kind === 'numeric'` and are absent (undefined) for all other
58471
- * kinds. They are populated live from HA attributes on each state
58472
- * push — consistent with `options` and the sensor-unit approach.
58404
+ * Numeric `min` / `max` / `step` live in the slice and share the device
58405
+ * quantity (`SourceInfo.quantity`, D944). Display precision is a read-time
58406
+ * preference. A datetime control's shape is `SourceInfo.dateTimeShape`.
58473
58407
  */
58474
58408
  var ControlStatusSchema = object({
58475
58409
  kind: _enum([
@@ -58481,39 +58415,17 @@ var ControlStatusSchema = object({
58481
58415
  /** Current value. Numeric kind → number; select/text/datetime → string.
58482
58416
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
58483
58417
  * or time-only — HA accepts all three forms). */
58484
- value: union([number(), string$2()]),
58418
+ value: union([number().meta(readingMeta("device")), string$2()]),
58485
58419
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
58486
58420
  options: array(string$2()),
58487
58421
  /** Ms epoch when the slice was last updated. */
58488
58422
  lastChangedAt: number(),
58489
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
58490
- * Populated live from HA `attributes.unit_of_measurement` on each push.
58491
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
58492
- unit: string$2().optional(),
58493
- /** Minimum allowed value. Populated live from HA `attributes.min`.
58494
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
58495
- min: number().optional(),
58496
- /** Maximum allowed value. Populated live from HA `attributes.max`.
58497
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
58498
- max: number().optional(),
58499
- /** Step increment. Populated live from HA `attributes.step`.
58500
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
58501
- step: number().optional(),
58502
- /** Suggested decimal places for numeric display. Populated live from HA
58503
- * `attributes.suggested_display_precision`, or derived from `step` when the
58504
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
58505
- * other kinds. Falls back to auto-formatting when absent. */
58506
- precision: number().int().min(0).max(10).optional(),
58507
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
58508
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
58509
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
58510
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
58511
- * `has_date`/`has_time` attributes on each push. */
58512
- format: _enum([
58513
- "date",
58514
- "time",
58515
- "datetime"
58516
- ]).optional()
58423
+ /** Minimum allowed value, in the device quantity's base unit. */
58424
+ min: number().optional().meta(readingMeta("device")),
58425
+ /** Maximum allowed value, in the device quantity's base unit. */
58426
+ max: number().optional().meta(readingMeta("device")),
58427
+ /** Step increment, in the device quantity's base unit. */
58428
+ step: number().optional().meta(readingMeta("device"))
58517
58429
  });
58518
58430
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
58519
58431
  object({
@@ -58579,9 +58491,9 @@ var CoverStatusSchema = object({
58579
58491
  ]),
58580
58492
  /** 0 = fully closed, 100 = fully open. Null when the device has
58581
58493
  * no intermediate position surface. */
58582
- position: number().min(0).max(100).nullable(),
58494
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58583
58495
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
58584
- tiltPosition: number().min(0).max(100).nullable(),
58496
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58585
58497
  /** Ms epoch when the slice was last updated. */
58586
58498
  lastChangedAt: number()
58587
58499
  });
@@ -58684,9 +58596,9 @@ var NormalizedRangeSchema$1 = object({
58684
58596
  var DayNightStatusSchema = object({
58685
58597
  mode: DayNightModeSchema,
58686
58598
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
58687
- sensitivity: number().optional(),
58599
+ sensitivity: number().optional().meta(readingMeta("none")),
58688
58600
  /** Delay before the IR-cut filter flips, in seconds. */
58689
- switchDelaySec: number().optional(),
58601
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
58690
58602
  lastFetchedAt: number()
58691
58603
  });
58692
58604
  /**
@@ -58904,11 +58816,11 @@ var DishwasherStatusSchema = object({
58904
58816
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
58905
58817
  availablePrograms: array(string$2()),
58906
58818
  /** Seconds remaining on the active cycle, or null when not running / not known. */
58907
- remainingSeconds: number().int().nonnegative().nullable(),
58819
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
58908
58820
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
58909
- delayedStartSeconds: number().int().nonnegative().nullable(),
58821
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
58910
58822
  /** Cycle completion 0..100, or null when not applicable. */
58911
- progressPercent: number().min(0).max(100).nullable(),
58823
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58912
58824
  /** True when the door is reported open; null when the door state is not known. */
58913
58825
  doorOpen: boolean().nullable(),
58914
58826
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -58932,7 +58844,7 @@ var DishwasherStatusSchema = object({
58932
58844
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
58933
58845
  machineCareDue: boolean().nullable(),
58934
58846
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
58935
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
58847
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
58936
58848
  /** Option states: null when the appliance does not expose the option or has not said. */
58937
58849
  halfLoad: boolean().nullable(),
58938
58850
  hygienePlus: boolean().nullable(),
@@ -58947,7 +58859,7 @@ var DishwasherStatusSchema = object({
58947
58859
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
58948
58860
  activeEvents: array(string$2()),
58949
58861
  /** Numeric error code when the appliance reports one; null otherwise. */
58950
- errorCode: number().nullable(),
58862
+ errorCode: number().nullable().meta(readingMeta("none")),
58951
58863
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
58952
58864
  errorLabel: string$2().nullable(),
58953
58865
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -59178,7 +59090,7 @@ var DoorbellStatusSchema = object({
59178
59090
  /** Ms epoch of the last press. null = never observed since this provider started. */
59179
59091
  lastPressedAt: number().nullable(),
59180
59092
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
59181
- pressCountSinceStart: number()
59093
+ pressCountSinceStart: number().meta(readingMeta("count"))
59182
59094
  });
59183
59095
  var DoorbellPressEventSchema = object({
59184
59096
  deviceId: number(),
@@ -59243,17 +59155,7 @@ onPressed: {
59243
59155
  history: "none",
59244
59156
  customize: doorbellCustomize
59245
59157
  };
59246
- /**
59247
- * Enum-state sensor — a string value picked from a finite option set.
59248
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
59249
- * states, weather conditions, contact-source channels, mode strings).
59250
- * The option set is stable across observations; it is persisted in the
59251
- * device config blob (via the control slice at adoption) and is NOT
59252
- * stored in `sourceInfo`.
59253
- */
59254
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
59255
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
59256
- var EnumSensorDateTimeFormatSchema = _enum([
59158
+ _enum([
59257
59159
  "date",
59258
59160
  "time",
59259
59161
  "datetime"
@@ -59261,12 +59163,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
59261
59163
  var EnumSensorStatusSchema = object({
59262
59164
  value: string$2(),
59263
59165
  /**
59264
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
59265
- * as a locale date/time/datetime; absent for plain enum sensors. HA
59266
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
59267
- * a time-only sensor → `'time'`.
59166
+ * Set when `value` is a timestamp rather than an enum state, so history
59167
+ * does not learn ISO strings as states. The display shape (date / time /
59168
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
59268
59169
  */
59269
- format: EnumSensorDateTimeFormatSchema.optional(),
59170
+ timestamp: boolean().optional(),
59270
59171
  /** Ms epoch when the slice was last updated. */
59271
59172
  lastFetchedAt: number()
59272
59173
  });
@@ -59293,11 +59194,11 @@ var enumSensorCapability = {
59293
59194
  * History (D850): the value as an `enum` series (an appliance's program or
59294
59195
  * operation state, an HVAC action). The option set is per device, so the
59295
59196
  * value list is LEARNED as values are first seen (bounded). A sensor with a
59296
- * `format` carries a timestamp, not a state, and is not recorded.
59197
+ * `timestamp` carries a clock, not a state, and is not recorded.
59297
59198
  */
59298
59199
  history: { fields: { value: {
59299
59200
  kind: "enum",
59300
- skipWhenSet: "format"
59201
+ skipWhenSet: "timestamp"
59301
59202
  } } },
59302
59203
  /** Clock fields: written, but excluded from the compare that decides
59303
59204
  * whether persisting is worth a SQLite commit. */
@@ -59317,13 +59218,13 @@ var EventFireSchema = object({
59317
59218
  deviceId: number(),
59318
59219
  eventType: string$2(),
59319
59220
  data: record(string$2(), unknown()).nullable(),
59320
- timestamp: number(),
59321
- seq: number()
59221
+ timestamp: number().meta(readingMeta("none")),
59222
+ seq: number().meta(readingMeta("count"))
59322
59223
  });
59323
59224
  var EventEmitterStatusSchema = object({
59324
59225
  eventTypes: array(string$2()),
59325
59226
  lastEvent: EventFireSchema.nullable(),
59326
- eventCountSinceStart: number()
59227
+ eventCountSinceStart: number().meta(readingMeta("count"))
59327
59228
  });
59328
59229
  var eventEmitterCapability = {
59329
59230
  name: "event-emitter",
@@ -59736,10 +59637,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
59736
59637
  var FanControlStatusSchema = object({
59737
59638
  /** Active speed as 0..100 inclusive. Null when the device has no
59738
59639
  * speed surface (single-speed fan). */
59739
- percentage: number().min(0).max(100).nullable(),
59640
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59740
59641
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
59741
59642
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
59742
- percentageStep: number().positive().optional(),
59643
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
59743
59644
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
59744
59645
  * — empty string when no preset is active or supported. */
59745
59646
  preset: string$2(),
@@ -59860,7 +59761,7 @@ var FeatureProbeStatusSchema = object({
59860
59761
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
59861
59762
  model: string$2().nullable(),
59862
59763
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
59863
- channelCount: number().nullable(),
59764
+ channelCount: number().nullable().meta(readingMeta("count")),
59864
59765
  /**
59865
59766
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
59866
59767
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -59985,7 +59886,7 @@ var gasCapability = {
59985
59886
  };
59986
59887
  var HeatingValveStatusSchema = object({
59987
59888
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
59988
- position: number().min(0).max(100).nullable(),
59889
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59989
59890
  /** Ms epoch when the slice was last updated (push or poll). */
59990
59891
  lastFetchedAt: number()
59991
59892
  });
@@ -60028,9 +59929,9 @@ var HumidifierStatusSchema = object({
60028
59929
  /** Whether the humidifier is currently on. */
60029
59930
  on: boolean(),
60030
59931
  /** Current measured relative humidity (0..100). Null when not reported. */
60031
- currentHumidity: number().min(0).max(100).nullable(),
59932
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
60032
59933
  /** Target relative humidity (0..100). Null when no setpoint surface. */
60033
- targetHumidity: number().min(0).max(100).nullable(),
59934
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
60034
59935
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
60035
59936
  * device has no mode surface. */
60036
59937
  mode: string$2().nullable(),
@@ -60040,9 +59941,9 @@ var HumidifierStatusSchema = object({
60040
59941
  * `idle` / `off`). Null when the device doesn't report it. */
60041
59942
  action: string$2().nullable(),
60042
59943
  /** HA `min_humidity` attribute. Null → UI uses 0. */
60043
- minHumidity: number().nullable(),
59944
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
60044
59945
  /** HA `max_humidity` attribute. Null → UI uses 100. */
60045
- maxHumidity: number().nullable(),
59946
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
60046
59947
  /** Ms epoch when the slice was last updated. */
60047
59948
  lastChangedAt: number()
60048
59949
  });
@@ -60112,22 +60013,13 @@ var humidifierCapability = {
60112
60013
  * `sourceInfo`.
60113
60014
  */
60114
60015
  var HumiditySensorStatusSchema = object({
60115
- /** Current relative humidity, 0..100. */
60116
- percent: number().min(0).max(100).meta({
60117
- unit: "%",
60016
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
60017
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
60018
+ precision: 0,
60118
60019
  label: "Humidity"
60119
- }),
60020
+ })),
60120
60021
  /** Ms epoch when the slice was last updated. */
60121
- lastFetchedAt: number(),
60122
- /** Live display unit from the upstream source (e.g. HA
60123
- * `attributes.unit_of_measurement`). The UI prefers this over the
60124
- * role's canonical unit. Absent → fall back to the canonical unit. */
60125
- unit: string$2().optional(),
60126
- /** Suggested decimal places for numeric display.
60127
- * Populated live from the upstream source when provided (e.g. HA
60128
- * `attributes.suggested_display_precision`). Falls back to
60129
- * auto-formatting when absent. */
60130
- precision: number().int().min(0).max(10).optional()
60022
+ lastFetchedAt: number()
60131
60023
  });
60132
60024
  var humiditySensorCapability = {
60133
60025
  name: "humidity-sensor",
@@ -60158,12 +60050,7 @@ var humiditySensorCapability = {
60158
60050
  } } },
60159
60051
  /** Clock fields: written, but excluded from the compare that decides
60160
60052
  * whether persisting is worth a SQLite commit. */
60161
- volatileStateFields: ["lastFetchedAt"],
60162
- customize: {
60163
- version: 1,
60164
- advanced: "derive",
60165
- targets: []
60166
- }
60053
+ volatileStateFields: ["lastFetchedAt"]
60167
60054
  };
60168
60055
  /**
60169
60056
  * Image display cap. Models a single still image exposed by an integration —
@@ -60184,7 +60071,7 @@ var ImageStatusSchema = object({
60184
60071
  * entity exposes no `entity_picture` (yet). */
60185
60072
  url: string$2().nullable(),
60186
60073
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
60187
- lastUpdated: number().nullable()
60074
+ lastUpdated: number().nullable().meta(readingMeta("none"))
60188
60075
  });
60189
60076
  var imageCapability = {
60190
60077
  name: "image",
@@ -60272,19 +60159,19 @@ var NormalizedRangeSchema = object({
60272
60159
  */
60273
60160
  var ImageSettingsStatusSchema = object({
60274
60161
  /** Normalized 0–100. */
60275
- brightness: number().optional(),
60162
+ brightness: number().optional().meta(readingMeta("none")),
60276
60163
  /** Normalized 0–100. */
60277
- contrast: number().optional(),
60164
+ contrast: number().optional().meta(readingMeta("none")),
60278
60165
  /** Normalized 0–100. */
60279
- saturation: number().optional(),
60166
+ saturation: number().optional().meta(readingMeta("none")),
60280
60167
  /** Normalized 0–100. */
60281
- sharpness: number().optional(),
60168
+ sharpness: number().optional().meta(readingMeta("none")),
60282
60169
  mirror: boolean().optional(),
60283
60170
  flip: boolean().optional(),
60284
60171
  rotate: ImageRotateSchema.optional(),
60285
60172
  whiteBalance: WhiteBalanceModeSchema.optional(),
60286
60173
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
60287
- warmth: number().optional(),
60174
+ warmth: number().optional().meta(readingMeta("none")),
60288
60175
  exposureMode: ExposureModeSchema.optional(),
60289
60176
  backlightMode: BacklightModeSchema.optional(),
60290
60177
  lastFetchedAt: number()
@@ -60563,10 +60450,10 @@ var IntercomAbilitySchema = object({
60563
60450
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
60564
60451
  codecs: array(string$2()),
60565
60452
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
60566
- sampleRate: number(),
60453
+ sampleRate: number().meta(readingMeta("none")),
60567
60454
  duplex: _enum(["half", "full"]),
60568
60455
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
60569
- maxBacklogMs: number()
60456
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
60570
60457
  });
60571
60458
  var IntercomStatusSchema = object({
60572
60459
  /** True while a talk session is open. */
@@ -60793,13 +60680,13 @@ var LawnMowerControlStatusSchema = object({
60793
60680
  activity: LawnMowerActivitySchema,
60794
60681
  /** 0..100 battery percentage. Null when the device has no battery
60795
60682
  * reading. */
60796
- batteryLevel: number().min(0).max(100).nullable(),
60683
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60797
60684
  /** 0..100 mowing-completion percentage of the current task, or null when no
60798
60685
  * task is active / progress is unavailable. */
60799
- progressPercent: number().min(0).max(100).nullable(),
60686
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60800
60687
  /** Current device/event code (dynamic — mostly status, sometimes an error),
60801
60688
  * or null when unknown. */
60802
- currentCode: number().nullable(),
60689
+ currentCode: number().nullable().meta(readingMeta("none")),
60803
60690
  /** Human label for {@link currentCode}, or null when undecodable. */
60804
60691
  currentCodeLabel: string$2().nullable(),
60805
60692
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -61332,7 +61219,7 @@ var MediaPlayerStatusSchema = object({
61332
61219
  state: MediaPlayerStateSchema,
61333
61220
  /** Volume as 0..100 inclusive. Null when the device has no volume
61334
61221
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
61335
- volumeLevel: number().min(0).max(100).nullable(),
61222
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61336
61223
  /** Mute toggle distinct from volume=0. Null when no mute surface.
61337
61224
  * Pair with `DeviceFeature.MediaPlayerMute`. */
61338
61225
  isMuted: boolean().nullable(),
@@ -61345,10 +61232,10 @@ var MediaPlayerStatusSchema = object({
61345
61232
  currentMedia: MediaInfoSchema.nullable(),
61346
61233
  /** Current playback position in ms. Null when not seekable or
61347
61234
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
61348
- positionMs: number().int().nonnegative().nullable(),
61235
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
61349
61236
  /** Total duration of the current media in ms. Null when unknown
61350
61237
  * (live stream) or nothing is playing. */
61351
- durationMs: number().int().nonnegative().nullable(),
61238
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
61352
61239
  /** Shuffle toggle. Null when no shuffle surface.
61353
61240
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
61354
61241
  shuffle: boolean().nullable(),
@@ -61657,7 +61544,7 @@ var motionCustomize = {
61657
61544
  eventName: "onMotionChanged"
61658
61545
  },
61659
61546
  deviceTypes: [DeviceType.Camera],
61660
- apply: "replace",
61547
+ apply: "add",
61661
61548
  applyAuthority: {
61662
61549
  kind: "camera-sensors",
61663
61550
  timeoutField: "motionCooldownMs"
@@ -61729,7 +61616,7 @@ var MotionStatusSchema = object({
61729
61616
  * it, every provider seeded it at boot, and every session left it
61730
61617
  * `detected: true` after its detach.
61731
61618
  */
61732
- autoClearAfterMs: number().nullable()
61619
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
61733
61620
  });
61734
61621
  /**
61735
61622
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -61911,7 +61798,7 @@ var MotionZoneRegionSchema = object({
61911
61798
  * the grid region(s). */
61912
61799
  var MotionZoneStatusSchema = object({
61913
61800
  enabled: boolean(),
61914
- sensitivity: number(),
61801
+ sensitivity: number().meta(readingMeta("none")),
61915
61802
  /** Grid region(s). Today exactly one `grid` shape. */
61916
61803
  regions: array(MotionZoneRegionSchema),
61917
61804
  lastFetchedAt: number()
@@ -62281,7 +62168,7 @@ var NavigationStatusSchema = object({
62281
62168
  following: boolean(),
62282
62169
  flash: boolean(),
62283
62170
  lightMode: NavigationLightModeSchema,
62284
- lightLevel: number().min(40).max(100),
62171
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
62285
62172
  /** Ms epoch when the slice was last updated. */
62286
62173
  lastChangedAt: number()
62287
62174
  });
@@ -62415,13 +62302,13 @@ var NetworkLinkStatusSchema = object({
62415
62302
  * one whose reading has not landed must not be drawn at 0 %. Consumers
62416
62303
  * SKIP a null rather than coerce it.
62417
62304
  */
62418
- signalPercent: number().min(0).max(100).nullable(),
62305
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62419
62306
  /** Raw received signal strength in dBm, when the firmware reports one. */
62420
- rssiDbm: number().optional(),
62307
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
62421
62308
  /** Network name of a wireless link, when the firmware reports it. */
62422
62309
  ssid: string$2().optional(),
62423
62310
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
62424
- lastUpdated: number()
62311
+ lastUpdated: number().meta(readingMeta("none"))
62425
62312
  });
62426
62313
  var networkLinkCapability = {
62427
62314
  name: "network-link",
@@ -62806,7 +62693,7 @@ var NotifierStatusSchema = object({
62806
62693
  lastError: string$2().nullable(),
62807
62694
  /** Number of deliveries currently buffered server-side (rate-limited
62808
62695
  * notifiers may queue). 0 when the provider sends synchronously. */
62809
- queueDepth: number().int().nonnegative(),
62696
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
62810
62697
  /** Per-feature capability matrix — authoritative source for the
62811
62698
  * compose-form field gating. */
62812
62699
  supports: NotifierSupportsSchema
@@ -62898,11 +62785,9 @@ onSent: { data: object({
62898
62785
  * cap fits (HA `sensor` whose `device_class` we don't have a typed cap
62899
62786
  * for: water flow, distance, weight, frequency, signal strength, …).
62900
62787
  *
62901
- * The `unit` and `precision` fields carry the live display metadata that
62902
- * the upstream source provides with each state push (HA
62903
- * `attributes.unit_of_measurement` / `attributes.suggested_display_precision`).
62904
- * The UI reads them directly from the slice — they are the single source
62905
- * of truth for generic numeric rendering and do NOT live on `sourceInfo`.
62788
+ * The quantity is not on this slice. It is chosen when the device is
62789
+ * adopted and stored once on `SourceInfo.quantity` (D944). `value` is in
62790
+ * that quantity's base unit. `null` is a refused sample (D393).
62906
62791
  *
62907
62792
  * Prefer the typed alternatives (`temperature-sensor`, `humidity-sensor`,
62908
62793
  * `ambient-light-sensor`, `pressure-sensor`, `power-meter`,
@@ -62911,15 +62796,8 @@ onSent: { data: object({
62911
62796
  * numeric values land here.
62912
62797
  */
62913
62798
  var NumericSensorStatusSchema = object({
62914
- value: number(),
62915
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
62916
- * Populated live from the upstream source on each state push.
62917
- * Absent for unitless sensors. */
62918
- unit: string$2().optional(),
62919
- /** Suggested decimal places for numeric display.
62920
- * Populated live from the upstream source when provided.
62921
- * Falls back to auto-formatting when absent. */
62922
- precision: number().int().min(0).max(10).optional(),
62799
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
62800
+ value: number().nullable().meta(readingMeta("device")),
62923
62801
  /** Ms epoch when the slice was last updated. */
62924
62802
  lastFetchedAt: number()
62925
62803
  });
@@ -62943,11 +62821,11 @@ var numericSensorCapability = {
62943
62821
  */
62944
62822
  durability: "restored",
62945
62823
  /**
62946
- * History (D850): recorded series. The unit is per device, read from the sibling `unit` field.
62824
+ * History (D850): recorded series. The unit is the device quantity's base,
62825
+ * resolved when the series is read — the cap has no single unit.
62947
62826
  */
62948
62827
  history: { fields: { value: {
62949
62828
  kind: "gauge",
62950
- unitFrom: "unit",
62951
62829
  minIntervalMs: 3e4
62952
62830
  } } },
62953
62831
  /** Clock fields: written, but excluded from the compare that decides
@@ -63239,23 +63117,26 @@ var PetFeederStatusSchema = object({
63239
63117
  /** Food currently in the bowl (grams). Null when the device has not
63240
63118
  * reported a reading yet. On dual-hopper models this is the combined
63241
63119
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
63242
- foodLevel: number().nullable(),
63120
+ foodLevel: number().nullable().meta(readingMeta("none")),
63243
63121
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
63244
63122
  * single-hopper models. */
63245
- food1: number().nullable(),
63123
+ food1: number().nullable().meta(readingMeta("none")),
63246
63124
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
63247
63125
  * single-hopper models. */
63248
- food2: number().nullable(),
63126
+ food2: number().nullable().meta(readingMeta("none")),
63249
63127
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
63250
63128
  * (`device_class: problem`, on = low). True when the bowl is empty /
63251
63129
  * below the feeder's low threshold. */
63252
63130
  lowFood: boolean(),
63253
63131
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
63254
63132
  * device has no battery reading. */
63255
- batteryPower: number().min(0).max(100).nullable(),
63256
- /** Days of desiccant life remaining. Null when the model has no
63257
- * desiccant sensor. */
63258
- desiccantLeftDays: number().nullable(),
63133
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63134
+ /**
63135
+ * Desiccant life remaining, stored in seconds (duration base, D944).
63136
+ * Providers convert from the days the feeder reports. Null when the
63137
+ * model has no desiccant sensor.
63138
+ */
63139
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
63259
63140
  /** True while a feed is in progress. */
63260
63141
  feeding: boolean(),
63261
63142
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -63266,7 +63147,7 @@ var PetFeederStatusSchema = object({
63266
63147
  * with `errorCode` for consumers that want the raw integer. */
63267
63148
  error: string$2().nullable(),
63268
63149
  /** Raw device error code (0 / null = no error). */
63269
- errorCode: number().nullable(),
63150
+ errorCode: number().nullable().meta(readingMeta("none")),
63270
63151
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
63271
63152
  isDualHopper: boolean(),
63272
63153
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -63276,7 +63157,7 @@ var PetFeederStatusSchema = object({
63276
63157
  /** Play a chime when dispensing. */
63277
63158
  feedSound: boolean(),
63278
63159
  /** Speaker / prompt volume level (device-scaled integer). */
63279
- volume: number(),
63160
+ volume: number().meta(readingMeta("none")),
63280
63161
  /** Ms epoch when the slice was last refreshed from the cloud. */
63281
63162
  lastFetchedAt: number()
63282
63163
  });
@@ -63395,7 +63276,7 @@ var petFeederCapability = {
63395
63276
  },
63396
63277
  desiccantLeftDays: {
63397
63278
  kind: "gauge",
63398
- unit: "days"
63279
+ unit: "s"
63399
63280
  }
63400
63281
  } }
63401
63282
  };
@@ -63796,36 +63677,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
63796
63677
  */
63797
63678
  var PowerMeterStatusSchema = object({
63798
63679
  /** Instantaneous power draw in watts. */
63799
- watts: number().optional().meta({
63800
- unit: "W",
63801
- label: "Power"
63802
- }),
63680
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
63803
63681
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
63804
- kwhTotal: number().optional().meta({
63805
- unit: "kWh",
63806
- label: "Energy"
63807
- }),
63682
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
63808
63683
  /** Voltage in volts. */
63809
- volts: number().optional(),
63684
+ volts: number().optional().meta(readingMeta("voltage")),
63810
63685
  /** Current in amperes. */
63811
- amps: number().optional(),
63812
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
63813
- va: number().optional(),
63686
+ amps: number().optional().meta(readingMeta("current")),
63687
+ /** Apparent power in volt-amperes. */
63688
+ va: number().optional().meta(readingMeta("apparent-power")),
63814
63689
  /** Power factor (0..1) if reported. */
63815
- powerFactor: number().min(0).max(1).optional(),
63690
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
63816
63691
  /** Ms epoch when the slice was last updated. */
63817
- lastFetchedAt: number(),
63818
- /** Live display unit of the single metric this slice carries (e.g. HA
63819
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
63820
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
63821
- * unit per slice is unambiguous. The UI prefers this over the role's
63822
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
63823
- unit: string$2().optional(),
63824
- /** Suggested decimal places for numeric display.
63825
- * Populated live from the upstream source when provided (e.g. HA
63826
- * `attributes.suggested_display_precision`). Falls back to
63827
- * auto-formatting when absent. */
63828
- precision: number().int().min(0).max(10).optional()
63692
+ lastFetchedAt: number()
63829
63693
  });
63830
63694
  var powerMeterCapability = {
63831
63695
  name: "power-meter",
@@ -63889,12 +63753,7 @@ var powerMeterCapability = {
63889
63753
  } },
63890
63754
  /** Clock fields: written, but excluded from the compare that decides
63891
63755
  * whether persisting is worth a SQLite commit. */
63892
- volatileStateFields: ["lastFetchedAt"],
63893
- customize: {
63894
- version: 1,
63895
- advanced: "derive",
63896
- targets: []
63897
- }
63756
+ volatileStateFields: ["lastFetchedAt"]
63898
63757
  };
63899
63758
  /**
63900
63759
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -63914,11 +63773,11 @@ var powerMeterCapability = {
63914
63773
  */
63915
63774
  var GpsLocationSchema = object({
63916
63775
  /** Latitude in decimal degrees, -90..90. */
63917
- latitude: number().min(-90).max(90),
63776
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
63918
63777
  /** Longitude in decimal degrees, -180..180. */
63919
- longitude: number().min(-180).max(180),
63778
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
63920
63779
  /** Reported accuracy in meters (lower = better). */
63921
- accuracyMeters: number().nonnegative()
63780
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
63922
63781
  });
63923
63782
  var PresenceStatusSchema = object({
63924
63783
  /** `home` / `not_home` / any user-defined zone name. */
@@ -63930,7 +63789,7 @@ var PresenceStatusSchema = object({
63930
63789
  gps: GpsLocationSchema.nullable(),
63931
63790
  /** Optional battery level of the tracking device (0..100). Null
63932
63791
  * when not reported. */
63933
- batteryPercent: number().min(0).max(100).nullable(),
63792
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63934
63793
  /** Ms epoch when the slice was last updated. */
63935
63794
  lastChangedAt: number()
63936
63795
  });
@@ -63980,19 +63839,10 @@ var presenceCapability = {
63980
63839
  * `sourceInfo`.
63981
63840
  */
63982
63841
  var PressureSensorStatusSchema = object({
63983
- /** Current pressure in hPa. */
63984
- hpa: number(),
63842
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
63843
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
63985
63844
  /** Ms epoch when the slice was last updated. */
63986
- lastFetchedAt: number(),
63987
- /** Live display unit from the upstream source (e.g. HA
63988
- * `attributes.unit_of_measurement`). The UI prefers this over the
63989
- * role's canonical unit. Absent → fall back to the canonical unit. */
63990
- unit: string$2().optional(),
63991
- /** Suggested decimal places for numeric display.
63992
- * Populated live from the upstream source when provided (e.g. HA
63993
- * `attributes.suggested_display_precision`). Falls back to
63994
- * auto-formatting when absent. */
63995
- precision: number().int().min(0).max(10).optional()
63845
+ lastFetchedAt: number()
63996
63846
  });
63997
63847
  var pressureSensorCapability = {
63998
63848
  name: "pressure-sensor",
@@ -64406,8 +64256,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
64406
64256
  }), _void(), { kind: "mutation" });
64407
64257
  var PtzAutotrackSettingsSchema = object({
64408
64258
  targetType: string$2().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
64409
- stopDelaySeconds: number().int().min(0).max(300),
64410
- disappearDelaySeconds: number().int().min(0).max(300)
64259
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
64260
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
64411
64261
  });
64412
64262
  /**
64413
64263
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -64561,7 +64411,7 @@ var RunningStateStatusSchema = object({
64561
64411
  /** null = unknown — never coerced to false (D393). */
64562
64412
  running: boolean().nullable(),
64563
64413
  /** Epoch ms the current boolean began; null while `running` is null. */
64564
- since: number().nullable(),
64414
+ since: number().nullable().meta(readingMeta("none")),
64565
64415
  source: RunningStateSourceSchema.meta({ label: "Source" }),
64566
64416
  phase: string$2().nullable(),
64567
64417
  /** null until a reading has been published (D393). */
@@ -64576,7 +64426,6 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
64576
64426
  /** Generic Customize target for the running boolean (D936). */
64577
64427
  var runningCustomize = {
64578
64428
  version: 1,
64579
- advanced: "derive",
64580
64429
  targets: [{
64581
64430
  id: "running",
64582
64431
  cardinality: "scalar",
@@ -64596,7 +64445,7 @@ var runningCustomize = {
64596
64445
  DeviceType.Thermostat,
64597
64446
  DeviceType.PetFeeder
64598
64447
  ],
64599
- apply: "add-or-replace",
64448
+ apply: "add",
64600
64449
  path: "running",
64601
64450
  sources: [
64602
64451
  {
@@ -65860,9 +65709,9 @@ var RecordTriggerSchema = _enum([
65860
65709
  */
65861
65710
  var RecordWindowSchema = object({
65862
65711
  trigger: RecordTriggerSchema,
65863
- day: number().int().min(0).max(6),
65864
- startMinute: number().int().min(0).max(1439),
65865
- endMinute: number().int().min(1).max(1440)
65712
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
65713
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
65714
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
65866
65715
  });
65867
65716
  /** Status of one physical volume, as the camera itself describes it. */
65868
65717
  var OnboardStorageVolumeSchema = object({
@@ -65938,10 +65787,10 @@ var RecordingOnboardStatusSchema = object({
65938
65787
  /** Master "record to the card at all" switch. */
65939
65788
  enabled: boolean().nullable(),
65940
65789
  overwriteWhenFull: boolean().nullable(),
65941
- preRecordSec: number().nullable(),
65942
- postRecordSec: number().nullable(),
65790
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
65791
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
65943
65792
  /** Length of one recorded file, in minutes. */
65944
- segmentMinutes: number().nullable(),
65793
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
65945
65794
  /** The primary track's weekly windows, flattened. */
65946
65795
  windows: array(RecordWindowSchema),
65947
65796
  /**
@@ -65957,7 +65806,7 @@ var RecordingOnboardStatusSchema = object({
65957
65806
  * provider reporting a non-zero count also reports the schedule as not
65958
65807
  * writable.
65959
65808
  */
65960
- unreadableWindows: number(),
65809
+ unreadableWindows: number().meta(readingMeta("count")),
65961
65810
  /**
65962
65811
  * The camera is scheduled to record and has NO usable storage.
65963
65812
  *
@@ -66802,7 +66651,7 @@ var CamStreamDescriptorSchema = object({
66802
66651
  url: string$2().optional(),
66803
66652
  codec: string$2().optional(),
66804
66653
  resolution: CamStreamResolutionSchema.optional(),
66805
- fps: number().positive().optional(),
66654
+ fps: number().positive().optional().meta(readingMeta("rate")),
66806
66655
  label: string$2().optional(),
66807
66656
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
66808
66657
  deviceFeatures: array(string$2()).optional(),
@@ -66889,18 +66738,18 @@ var StreamProfileSchema = _enum([
66889
66738
  ]);
66890
66739
  /** The current encoding config of one stream profile. */
66891
66740
  var StreamProfileConfigSchema = object({
66892
- width: number(),
66893
- height: number(),
66741
+ width: number().meta(readingMeta("none")),
66742
+ height: number().meta(readingMeta("none")),
66894
66743
  codec: _enum(["h264", "h265"]),
66895
- framerate: number(),
66896
- bitrate: number(),
66744
+ framerate: number().meta(readingMeta("rate")),
66745
+ bitrate: number().meta(readingMeta("bitrate")),
66897
66746
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
66898
66747
  encoderProfile: _enum([
66899
66748
  "high",
66900
66749
  "main",
66901
66750
  "baseline"
66902
66751
  ]).optional(),
66903
- gop: number().optional(),
66752
+ gop: number().optional().meta(readingMeta("none")),
66904
66753
  /**
66905
66754
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
66906
66755
  *
@@ -67121,12 +66970,7 @@ var switchCapability = {
67121
66970
  label: "On",
67122
66971
  kind: "boolean"
67123
66972
  }
67124
- }] },
67125
- customize: {
67126
- version: 1,
67127
- advanced: "derive",
67128
- targets: []
67129
- }
66973
+ }] }
67130
66974
  };
67131
66975
  /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
67132
66976
  function hasControlCharacter(value) {
@@ -67709,35 +67553,21 @@ var tamperCapability = {
67709
67553
  * entries with `device_class: temperature` and any future native
67710
67554
  * thermometer.
67711
67555
  *
67712
- * Unit normalisation: providers convert to Celsius before storing.
67713
- * The slice value is always Celsius so cross-cap aggregators
67714
- * (climate-control's `currentTemp`, energy analytics) can compose
67715
- * without per-source unit fixups. The canonical display unit (`°C`) is
67716
- * a descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
67717
- * `sourceInfo`.
67556
+ * The number is Celsius (D944). Providers convert on ingest. A failed
67557
+ * conversion is `null`, never a guess. Display unit is a read-time preference.
67718
67558
  *
67719
67559
  * Status `lastFetchedAt` lets staleness-aware consumers detect a
67720
67560
  * frozen feed (provider hung) distinct from a "temperature hasn't
67721
67561
  * changed" steady state.
67722
67562
  */
67723
67563
  var TemperatureSensorStatusSchema = object({
67724
- /** Current temperature in Celsius. */
67725
- celsius: number().meta({
67726
- unit: "°C",
67564
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
67565
+ celsius: number().nullable().meta(readingMeta("temperature", {
67566
+ precision: 1,
67727
67567
  label: "Temperature"
67728
- }),
67568
+ })),
67729
67569
  /** Ms epoch when the slice was last updated (push or poll). */
67730
- lastFetchedAt: number(),
67731
- /** Live display unit from the upstream source (e.g. HA
67732
- * `attributes.unit_of_measurement`). The UI prefers this over the
67733
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
67734
- * Absent → the UI falls back to the role's canonical unit. */
67735
- unit: string$2().optional(),
67736
- /** Suggested decimal places for numeric display.
67737
- * Populated live from the upstream source when provided (e.g. HA
67738
- * `attributes.suggested_display_precision`). Falls back to
67739
- * auto-formatting when absent. */
67740
- precision: number().int().min(0).max(10).optional()
67570
+ lastFetchedAt: number()
67741
67571
  });
67742
67572
  var temperatureSensorCapability = {
67743
67573
  name: "temperature-sensor",
@@ -67768,12 +67598,7 @@ var temperatureSensorCapability = {
67768
67598
  } } },
67769
67599
  /** Clock fields: written, but excluded from the compare that decides
67770
67600
  * whether persisting is worth a SQLite commit. */
67771
- volatileStateFields: ["lastFetchedAt"],
67772
- customize: {
67773
- version: 1,
67774
- advanced: "derive",
67775
- targets: []
67776
- }
67601
+ volatileStateFields: ["lastFetchedAt"]
67777
67602
  };
67778
67603
  /**
67779
67604
  * toast — system-scoped singleton capability that streams toast
@@ -68056,16 +67881,16 @@ var VacuumControlStatusSchema = object({
68056
67881
  ]),
68057
67882
  /** 0..100 battery percentage. Null when the device has no battery
68058
67883
  * reading. */
68059
- batteryLevel: number().min(0).max(100).nullable(),
67884
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
68060
67885
  /** Current fan-speed token (provider-verbatim). Null when unknown or
68061
67886
  * the vacuum has no speed control. */
68062
67887
  fanSpeed: string$2().nullable(),
68063
67888
  /** Speed tokens the hardware accepts — drives the UI selector. */
68064
67889
  availableFanSpeeds: array(string$2()),
68065
67890
  /** 0..100 cleaning-completion percentage of the current task, or null. */
68066
- progressPercent: number().min(0).max(100).nullable(),
67891
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
68067
67892
  /** Current error code (0 / null = no error). */
68068
- errorCode: number().nullable(),
67893
+ errorCode: number().nullable().meta(readingMeta("none")),
68069
67894
  /** Human label for {@link errorCode}, or null when none / undecodable. */
68070
67895
  errorLabel: string$2().nullable(),
68071
67896
  /** Ms epoch when the slice was last updated. */
@@ -68143,7 +67968,7 @@ var ValveStatusSchema = object({
68143
67968
  ]),
68144
67969
  /** 0 = fully closed, 100 = fully open. Null when the device has no
68145
67970
  * intermediate position surface. */
68146
- position: number().min(0).max(100).nullable(),
67971
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
68147
67972
  /** Ms epoch when the slice was last updated. */
68148
67973
  lastChangedAt: number()
68149
67974
  });
@@ -68251,9 +68076,9 @@ var vibrationCapability = {
68251
68076
  */
68252
68077
  var WaterHeaterStatusSchema = object({
68253
68078
  /** Current measured temperature. Null when not reported. */
68254
- currentTemp: number().nullable(),
68079
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
68255
68080
  /** Target temperature setpoint. Null when no setpoint surface. */
68256
- targetTemp: number().nullable(),
68081
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
68257
68082
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
68258
68083
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
68259
68084
  * device reports an unknown state. */
@@ -68264,9 +68089,9 @@ var WaterHeaterStatusSchema = object({
68264
68089
  * has no away surface. */
68265
68090
  away: boolean().nullable(),
68266
68091
  /** HA `min_temp` attribute. Null when not reported. */
68267
- minTemp: number().nullable(),
68092
+ minTemp: number().nullable().meta(readingMeta("temperature")),
68268
68093
  /** HA `max_temp` attribute. Null when not reported. */
68269
- maxTemp: number().nullable(),
68094
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
68270
68095
  /** Ms epoch when the slice was last updated. */
68271
68096
  lastChangedAt: number()
68272
68097
  });
@@ -68351,25 +68176,18 @@ var waterHeaterCapability = {
68351
68176
  * Forecast deferred — current conditions only for v1.
68352
68177
  */
68353
68178
  var WeatherStatusSchema = object({
68354
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
68355
- * when no condition has been reported yet. */
68179
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
68356
68180
  condition: string$2().nullable(),
68357
- /** Current temperature in the reported unit. Null when not provided. */
68358
- temperature: number().nullable(),
68359
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
68360
- temperatureUnit: string$2().nullable(),
68181
+ /** Current temperature in Celsius. Null when not provided or refused. */
68182
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
68361
68183
  /** Relative humidity (0..100). Null when not provided. */
68362
- humidity: number().min(0).max(100).nullable(),
68363
- /** Barometric pressure in the reported unit. Null when not provided. */
68364
- pressure: number().nullable(),
68365
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
68366
- pressureUnit: string$2().nullable(),
68367
- /** Wind speed in the reported unit. Null when not provided. */
68368
- windSpeed: number().nullable(),
68369
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
68370
- windSpeedUnit: string$2().nullable(),
68184
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
68185
+ /** Barometric pressure in hPa. Null when not provided or refused. */
68186
+ pressure: number().nullable().meta(readingMeta("pressure")),
68187
+ /** Wind speed in metres per second. Null when not provided or refused. */
68188
+ windSpeed: number().nullable().meta(readingMeta("speed")),
68371
68189
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
68372
- windBearing: number().nullable(),
68190
+ windBearing: number().nullable().meta(readingMeta("angle")),
68373
68191
  /** Ms epoch when the slice was last updated. */
68374
68192
  lastFetchedAt: number()
68375
68193
  });
@@ -68397,12 +68215,12 @@ var weatherCapability = {
68397
68215
  */
68398
68216
  durability: "session",
68399
68217
  /**
68400
- * History (D850): recorded series. Units follow the sibling `*Unit` fields.
68218
+ * History (D850): recorded series. Numbers are the quantity base (D944).
68401
68219
  */
68402
68220
  history: { fields: {
68403
68221
  temperature: {
68404
68222
  kind: "gauge",
68405
- unitFrom: "temperatureUnit",
68223
+ unit: "°C",
68406
68224
  deadband: .2
68407
68225
  },
68408
68226
  humidity: {
@@ -68412,12 +68230,12 @@ var weatherCapability = {
68412
68230
  },
68413
68231
  pressure: {
68414
68232
  kind: "gauge",
68415
- unitFrom: "pressureUnit",
68233
+ unit: "hPa",
68416
68234
  deadband: .5
68417
68235
  },
68418
68236
  windSpeed: {
68419
68237
  kind: "gauge",
68420
- unitFrom: "windSpeedUnit",
68238
+ unit: "m/s",
68421
68239
  deadband: .5
68422
68240
  }
68423
68241
  } }
@@ -83987,7 +83805,6 @@ var SENSOR_CAP_VOCABULARY = {
83987
83805
  field: "value",
83988
83806
  quantity: t("the reading", "il valore"),
83989
83807
  label: t("Reading", "Valore"),
83990
- unitField: "unit",
83991
83808
  icon: "gauge"
83992
83809
  }]
83993
83810
  },
@@ -84123,7 +83940,7 @@ var SENSOR_CAP_VOCABULARY = {
84123
83940
  field: "temperature",
84124
83941
  quantity: t("the outdoor temperature", "la temperatura esterna"),
84125
83942
  label: t("Temperature", "Temperatura"),
84126
- unitField: "temperatureUnit",
83943
+ unit: "°C",
84127
83944
  icon: "thermometer"
84128
83945
  },
84129
83946
  {
@@ -84139,7 +83956,7 @@ var SENSOR_CAP_VOCABULARY = {
84139
83956
  field: "windSpeed",
84140
83957
  quantity: t("the wind speed", "la velocità del vento"),
84141
83958
  label: t("Wind", "Vento"),
84142
- unitField: "windSpeedUnit",
83959
+ unit: "m/s",
84143
83960
  icon: "wind"
84144
83961
  },
84145
83962
  {
@@ -84147,7 +83964,7 @@ var SENSOR_CAP_VOCABULARY = {
84147
83964
  field: "pressure",
84148
83965
  quantity: t("the air pressure", "la pressione atmosferica"),
84149
83966
  label: t("Pressure", "Pressione"),
84150
- unitField: "pressureUnit",
83967
+ unit: "hPa",
84151
83968
  icon: "gauge"
84152
83969
  }
84153
83970
  ]
@@ -197439,7 +197256,6 @@ function decodeElectricalPower(attributeId, value) {
197439
197256
  cap: "power-meter",
197440
197257
  patch: {
197441
197258
  watts: mw / 1e3,
197442
- unit: "W",
197443
197259
  lastFetchedAt: Date.now()
197444
197260
  }
197445
197261
  };