@camstack/addon-pipeline 1.2.627 → 1.2.629

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 (37) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +35 -0
  3. package/dist/audio-analyzer/index.js +3 -3
  4. package/dist/audio-analyzer/index.mjs +3 -3
  5. package/dist/{custom-model-catalog-DtxDgB4p.mjs → custom-model-catalog--z5qDAbz.mjs} +2 -2
  6. package/dist/{custom-model-catalog-rWY1H5Ei.js → custom-model-catalog-DyFqiWQw.js} +2 -2
  7. package/dist/detection-pipeline/index.js +4 -4
  8. package/dist/detection-pipeline/index.mjs +4 -4
  9. package/dist/{dist-CQe330cU.mjs → dist-DkFeDO1Z.mjs} +867 -438
  10. package/dist/{dist-CcZsLU96.js → dist-vvIUo4Fg.js} +867 -438
  11. package/dist/{model-catalogs-B43s5kgs.mjs → model-catalogs-DcAtdzQX.mjs} +1 -1
  12. package/dist/{model-catalogs-BJjn54AQ.js → model-catalogs-dHtp4VIV.js} +1 -1
  13. package/dist/motion-wasm/index.js +1 -1
  14. package/dist/motion-wasm/index.mjs +1 -1
  15. package/dist/{node-XOjWaowO.mjs → node-CT-ETMWK.mjs} +1 -1
  16. package/dist/{node-Ba3e855X.js → node-j-hM0LYt.js} +1 -1
  17. package/dist/pipeline-runner/index.js +3 -3
  18. package/dist/pipeline-runner/index.mjs +3 -3
  19. package/dist/{process-memory-Dw_mzdZ5.js → process-memory-B9lUUQZw.js} +1 -1
  20. package/dist/{process-memory-DyYlcpfM.mjs → process-memory-BwZIeAKw.mjs} +1 -1
  21. package/dist/recorder/index.js +2 -2
  22. package/dist/recorder/index.mjs +2 -2
  23. package/dist/{segment-demux-js-qzmsqDt7.js → segment-demux-js-BCBpOp5j.js} +1 -1
  24. package/dist/{segment-demux-js-BVwtkI0F.mjs → segment-demux-js-CW7aiIml.mjs} +1 -1
  25. package/dist/stream-broker/_stub.js +2 -2
  26. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CJJ0Dj8s.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DUUHEAPV.mjs} +2 -2
  27. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BQMKjVwR.mjs +26 -0
  28. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Ca0Qmh7u.mjs +26 -0
  29. package/dist/stream-broker/demux-worker-child.js +1 -1
  30. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  31. package/dist/stream-broker/{hostInit-BIx670St.mjs → hostInit-BiqKaq23.mjs} +2 -2
  32. package/dist/stream-broker/index.js +3 -3
  33. package/dist/stream-broker/index.mjs +3 -3
  34. package/dist/stream-broker/remoteEntry.js +1 -1
  35. package/package.json +4 -2
  36. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D-GlPMcf.mjs +0 -26
  37. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CEiM-boT.mjs +0 -26
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- //#region ../types/dist/event-category-DHXxB8t9.mjs
2
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -325,6 +325,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
325
325
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
326
326
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
327
327
  /**
328
+ * A camera's package LLM confirm has failed closed three times in a row
329
+ * (D931, 2026-10-26). One edge per camera until
330
+ * {@link PipelinePackageLlmAnswered}.
331
+ */
332
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
333
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
334
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
335
+ /**
328
336
  * Per-camera pipeline config was mutated by the orchestrator
329
337
  * (a settings change via `setAgentInferenceDevices` /
330
338
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5567,7 +5575,7 @@ var ZodIssueCode = {
5567
5575
  var ZodFirstPartyTypeKind;
5568
5576
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5569
5577
  //#endregion
5570
- //#region ../types/dist/sleep-BErAxhul.mjs
5578
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5571
5579
  /**
5572
5580
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5573
5581
  * window to float samples (D455).
@@ -8315,6 +8323,99 @@ function defineRecordKinds(specs) {
8315
8323
  }
8316
8324
  return out;
8317
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
+ /**
8366
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8367
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8368
+ */
8369
+ var QUANTITY_BASE = {
8370
+ temperature: "°C",
8371
+ pressure: "hPa",
8372
+ speed: "m/s",
8373
+ precipitation: "mm",
8374
+ "precipitation-rate": "mm/h",
8375
+ length: "m",
8376
+ area: "m²",
8377
+ illuminance: "lx",
8378
+ irradiance: "W/m²",
8379
+ power: "W",
8380
+ energy: "kWh",
8381
+ voltage: "V",
8382
+ current: "A",
8383
+ "apparent-power": "VA",
8384
+ humidity: "%",
8385
+ percentage: "%",
8386
+ "concentration-volume": "ppm",
8387
+ "concentration-mass": "µg/m³",
8388
+ "concentration-ppb": "ppb",
8389
+ volume: "L",
8390
+ duration: "s",
8391
+ "duration-ms": "ms",
8392
+ information: "B",
8393
+ "sound-level": "dBFS",
8394
+ "signal-level": "dBm",
8395
+ "colour-temperature": "mired",
8396
+ angle: "°",
8397
+ bitrate: "kbps",
8398
+ rate: "fps",
8399
+ mass: "kg",
8400
+ index: "none",
8401
+ ratio: "none",
8402
+ count: "none",
8403
+ none: "none",
8404
+ device: "device"
8405
+ };
8406
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8407
+ function baseUnitOf(quantity) {
8408
+ const symbol = QUANTITY_BASE[quantity];
8409
+ if (symbol === "none" || symbol === "device") return void 0;
8410
+ return symbol;
8411
+ }
8412
+ function readingMeta(quantity, extras) {
8413
+ return {
8414
+ quantity,
8415
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8416
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8417
+ };
8418
+ }
8318
8419
  /** Shared Zod schemas used across streaming capabilities. */
8319
8420
  var CamProfileSchema = _enum([
8320
8421
  "high",
@@ -8348,8 +8449,8 @@ var CamStreamKindSchema = _enum([
8348
8449
  "derived"
8349
8450
  ]);
8350
8451
  var CamStreamResolutionSchema = object({
8351
- width: number().int().positive(),
8352
- height: number().int().positive()
8452
+ width: number().int().positive().meta(readingMeta("none")),
8453
+ height: number().int().positive().meta(readingMeta("none"))
8353
8454
  });
8354
8455
  var CameraStreamSchema = object({
8355
8456
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -9006,6 +9107,8 @@ var CAP_NAMES = Object.freeze([
9006
9107
  "filesystem-browse",
9007
9108
  "flood",
9008
9109
  "gas",
9110
+ "group-commands",
9111
+ "group-status",
9009
9112
  "heating-valve",
9010
9113
  "humidifier",
9011
9114
  "humidity-sensor",
@@ -9135,14 +9238,6 @@ Object.freeze({
9135
9238
  {
9136
9239
  "path": "aqi",
9137
9240
  "valueType": "number"
9138
- },
9139
- {
9140
- "path": "unit",
9141
- "valueType": "string"
9142
- },
9143
- {
9144
- "path": "precision",
9145
- "valueType": "number"
9146
9241
  }
9147
9242
  ],
9148
9243
  "alarm-panel": [
@@ -9178,20 +9273,10 @@ Object.freeze({
9178
9273
  "valueType": "boolean"
9179
9274
  }
9180
9275
  ],
9181
- "ambient-light-sensor": [
9182
- {
9183
- "path": "lux",
9184
- "valueType": "number"
9185
- },
9186
- {
9187
- "path": "unit",
9188
- "valueType": "string"
9189
- },
9190
- {
9191
- "path": "precision",
9192
- "valueType": "number"
9193
- }
9194
- ],
9276
+ "ambient-light-sensor": [{
9277
+ "path": "lux",
9278
+ "valueType": "number"
9279
+ }],
9195
9280
  "audio-metrics": [
9196
9281
  {
9197
9282
  "path": "ts",
@@ -9271,6 +9356,10 @@ Object.freeze({
9271
9356
  "path": "lastUpdated",
9272
9357
  "valueType": "number"
9273
9358
  },
9359
+ {
9360
+ "path": "volts",
9361
+ "valueType": "number"
9362
+ },
9274
9363
  {
9275
9364
  "path": "lastContactAt",
9276
9365
  "valueType": "number"
@@ -9513,10 +9602,6 @@ Object.freeze({
9513
9602
  "datetime"
9514
9603
  ]
9515
9604
  },
9516
- {
9517
- "path": "unit",
9518
- "valueType": "string"
9519
- },
9520
9605
  {
9521
9606
  "path": "min",
9522
9607
  "valueType": "number"
@@ -9528,19 +9613,6 @@ Object.freeze({
9528
9613
  {
9529
9614
  "path": "step",
9530
9615
  "valueType": "number"
9531
- },
9532
- {
9533
- "path": "precision",
9534
- "valueType": "number"
9535
- },
9536
- {
9537
- "path": "format",
9538
- "valueType": "enum",
9539
- "enumValues": [
9540
- "date",
9541
- "time",
9542
- "datetime"
9543
- ]
9544
9616
  }
9545
9617
  ],
9546
9618
  "cover": [
@@ -9781,13 +9853,8 @@ Object.freeze({
9781
9853
  "path": "value",
9782
9854
  "valueType": "string"
9783
9855
  }, {
9784
- "path": "format",
9785
- "valueType": "enum",
9786
- "enumValues": [
9787
- "date",
9788
- "time",
9789
- "datetime"
9790
- ]
9856
+ "path": "timestamp",
9857
+ "valueType": "boolean"
9791
9858
  }],
9792
9859
  "event-emitter": [
9793
9860
  {
@@ -9860,6 +9927,20 @@ Object.freeze({
9860
9927
  "path": "detected",
9861
9928
  "valueType": "boolean"
9862
9929
  }],
9930
+ "group-status": [
9931
+ {
9932
+ "path": "included",
9933
+ "valueType": "number"
9934
+ },
9935
+ {
9936
+ "path": "excluded",
9937
+ "valueType": "number"
9938
+ },
9939
+ {
9940
+ "path": "total",
9941
+ "valueType": "number"
9942
+ }
9943
+ ],
9863
9944
  "heating-valve": [{
9864
9945
  "path": "position",
9865
9946
  "valueType": "number"
@@ -9894,20 +9975,10 @@ Object.freeze({
9894
9975
  "valueType": "number"
9895
9976
  }
9896
9977
  ],
9897
- "humidity-sensor": [
9898
- {
9899
- "path": "percent",
9900
- "valueType": "number"
9901
- },
9902
- {
9903
- "path": "unit",
9904
- "valueType": "string"
9905
- },
9906
- {
9907
- "path": "precision",
9908
- "valueType": "number"
9909
- }
9910
- ],
9978
+ "humidity-sensor": [{
9979
+ "path": "percent",
9980
+ "valueType": "number"
9981
+ }],
9911
9982
  "image": [{
9912
9983
  "path": "url",
9913
9984
  "valueType": "string"
@@ -10246,20 +10317,10 @@ Object.freeze({
10246
10317
  "valueType": "boolean"
10247
10318
  }
10248
10319
  ],
10249
- "numeric-sensor": [
10250
- {
10251
- "path": "value",
10252
- "valueType": "number"
10253
- },
10254
- {
10255
- "path": "unit",
10256
- "valueType": "string"
10257
- },
10258
- {
10259
- "path": "precision",
10260
- "valueType": "number"
10261
- }
10262
- ],
10320
+ "numeric-sensor": [{
10321
+ "path": "value",
10322
+ "valueType": "number"
10323
+ }],
10263
10324
  "pet-feeder": [
10264
10325
  {
10265
10326
  "path": "foodLevel",
@@ -10351,14 +10412,6 @@ Object.freeze({
10351
10412
  {
10352
10413
  "path": "powerFactor",
10353
10414
  "valueType": "number"
10354
- },
10355
- {
10356
- "path": "unit",
10357
- "valueType": "string"
10358
- },
10359
- {
10360
- "path": "precision",
10361
- "valueType": "number"
10362
10415
  }
10363
10416
  ],
10364
10417
  "presence": [
@@ -10387,20 +10440,10 @@ Object.freeze({
10387
10440
  "valueType": "number"
10388
10441
  }
10389
10442
  ],
10390
- "pressure-sensor": [
10391
- {
10392
- "path": "hpa",
10393
- "valueType": "number"
10394
- },
10395
- {
10396
- "path": "unit",
10397
- "valueType": "string"
10398
- },
10399
- {
10400
- "path": "precision",
10401
- "valueType": "number"
10402
- }
10403
- ],
10443
+ "pressure-sensor": [{
10444
+ "path": "hpa",
10445
+ "valueType": "number"
10446
+ }],
10404
10447
  "privacy-mask": [{
10405
10448
  "path": "enabled",
10406
10449
  "valueType": "boolean"
@@ -10711,20 +10754,10 @@ Object.freeze({
10711
10754
  "path": "tampered",
10712
10755
  "valueType": "boolean"
10713
10756
  }],
10714
- "temperature-sensor": [
10715
- {
10716
- "path": "celsius",
10717
- "valueType": "number"
10718
- },
10719
- {
10720
- "path": "unit",
10721
- "valueType": "string"
10722
- },
10723
- {
10724
- "path": "precision",
10725
- "valueType": "number"
10726
- }
10727
- ],
10757
+ "temperature-sensor": [{
10758
+ "path": "celsius",
10759
+ "valueType": "number"
10760
+ }],
10728
10761
  "update": [
10729
10762
  {
10730
10763
  "path": "currentVersion",
@@ -10835,10 +10868,6 @@ Object.freeze({
10835
10868
  "path": "temperature",
10836
10869
  "valueType": "number"
10837
10870
  },
10838
- {
10839
- "path": "temperatureUnit",
10840
- "valueType": "string"
10841
- },
10842
10871
  {
10843
10872
  "path": "humidity",
10844
10873
  "valueType": "number"
@@ -10847,18 +10876,10 @@ Object.freeze({
10847
10876
  "path": "pressure",
10848
10877
  "valueType": "number"
10849
10878
  },
10850
- {
10851
- "path": "pressureUnit",
10852
- "valueType": "string"
10853
- },
10854
10879
  {
10855
10880
  "path": "windSpeed",
10856
10881
  "valueType": "number"
10857
10882
  },
10858
- {
10859
- "path": "windSpeedUnit",
10860
- "valueType": "string"
10861
- },
10862
10883
  {
10863
10884
  "path": "windBearing",
10864
10885
  "valueType": "number"
@@ -11413,7 +11434,7 @@ function errMsg(err) {
11413
11434
  return String(err);
11414
11435
  }
11415
11436
  //#endregion
11416
- //#region ../types/dist/composition-kEl9mevu.mjs
11437
+ //#region ../types/dist/composition-OhpuespA.mjs
11417
11438
  var AUDIO_ENCODER_BY_CODEC = {
11418
11439
  opus: "libopus",
11419
11440
  aac: "aac",
@@ -13004,15 +13025,74 @@ function validateExpressionSource(src) {
13004
13025
  }
13005
13026
  return null;
13006
13027
  }
13007
- /** A composed device's stableId is this prefix plus the block id. */
13008
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
13009
- /** Longest snippet a `code` source may carry (slice 4). */
13010
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
13028
+ /**
13029
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
13030
+ * id. Split out so helper schemas can name it without a cycle through
13031
+ * `composition.ts` (D659).
13032
+ */
13011
13033
  var CompositionSourceRefSchema = object({
13012
13034
  /** The owning addon; `stableId` is unique only within it. */
13013
13035
  addonId: string().min(1),
13014
13036
  stableId: string().min(1)
13015
13037
  });
13038
+ var GROUP_TEMPLATE_IDS = [
13039
+ "temperature",
13040
+ "humidity",
13041
+ "pressure",
13042
+ "illuminance",
13043
+ "power",
13044
+ "energy",
13045
+ "battery",
13046
+ "numeric",
13047
+ "motion",
13048
+ "contact",
13049
+ "leak",
13050
+ "smoke",
13051
+ "occupancy",
13052
+ "binary",
13053
+ "switch",
13054
+ "light"
13055
+ ];
13056
+ var NUMERIC_AGGREGATES = [
13057
+ "mean",
13058
+ "median",
13059
+ "min",
13060
+ "max",
13061
+ "sum"
13062
+ ];
13063
+ var BINARY_AGGREGATES = ["any", "all"];
13064
+ /** How an actuator group decides it is on. Unknown members never count as off. */
13065
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
13066
+ var AGGREGATE_REDUCERS = [
13067
+ "mean",
13068
+ "median",
13069
+ "min",
13070
+ "max",
13071
+ "sum",
13072
+ "any",
13073
+ "all"
13074
+ ];
13075
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
13076
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
13077
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
13078
+ /** One member. The field is stored so occupancy can name motion or binary. */
13079
+ var HelperMemberSchema = object({
13080
+ source: CompositionSourceRefSchema,
13081
+ cap: string().min(1),
13082
+ fieldPath: string().min(1)
13083
+ });
13084
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
13085
+ kind: literal("group"),
13086
+ templateId: GroupTemplateIdSchema,
13087
+ aggregate: AggregateReducerSchema,
13088
+ members: array(HelperMemberSchema).min(1).max(64),
13089
+ /** Actuator groups only. Readings leave this unset. */
13090
+ active: ActiveCriterionSchema.optional()
13091
+ })]);
13092
+ /** A composed device's stableId is this prefix plus the block id. */
13093
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
13094
+ /** Longest snippet a `code` source may carry (slice 4). */
13095
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
13016
13096
  /** One field of one capability of one source device. */
13017
13097
  var CompositionFieldReadSchema = object({
13018
13098
  source: CompositionSourceRefSchema,
@@ -13056,7 +13136,33 @@ var CompositionCodeSourceSchema = object({
13056
13136
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
13057
13137
  CompositionFromSourceSchema,
13058
13138
  CompositionExpressionSourceSchema,
13059
- CompositionCodeSourceSchema
13139
+ CompositionCodeSourceSchema,
13140
+ object({
13141
+ kind: literal("aggregate"),
13142
+ reducer: _enum([
13143
+ "mean",
13144
+ "median",
13145
+ "min",
13146
+ "max",
13147
+ "sum",
13148
+ "any",
13149
+ "all"
13150
+ ]),
13151
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
13152
+ /** `value` is the reading; the others are the member counts. */
13153
+ publish: _enum([
13154
+ "value",
13155
+ "included",
13156
+ "excluded",
13157
+ "total"
13158
+ ]),
13159
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
13160
+ empty: _enum([
13161
+ "null",
13162
+ "omit",
13163
+ "offline"
13164
+ ])
13165
+ })
13060
13166
  ]);
13061
13167
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
13062
13168
  kind: literal("forward"),
@@ -13102,7 +13208,13 @@ var CompositionSchema = object({
13102
13208
  kind: literal("existing"),
13103
13209
  device: CompositionSourceRefSchema
13104
13210
  })]),
13105
- features: array(CompositionFeatureSchema).min(1).max(16)
13211
+ features: array(CompositionFeatureSchema).min(1).max(16),
13212
+ /**
13213
+ * Present when the block is a helper template. Absent on a free
13214
+ * composition (the room example, device 4603). The editor reads this,
13215
+ * never the compiled features.
13216
+ */
13217
+ helper: HelperSpecSchema.optional()
13106
13218
  }).superRefine((composition, ctx) => {
13107
13219
  const seen = /* @__PURE__ */ new Set();
13108
13220
  composition.features.forEach((feature, index) => {
@@ -21032,7 +21144,11 @@ var CompositionProblemSchema = object({
21032
21144
  "item-field-reserved",
21033
21145
  "target-missing",
21034
21146
  "target-unreadable",
21035
- "not-replaceable"
21147
+ "not-replaceable",
21148
+ "native-capability-replace",
21149
+ "mixed-quantity",
21150
+ "helper-mismatch",
21151
+ "member-constraint"
21036
21152
  ]),
21037
21153
  path: string(),
21038
21154
  message: string()
@@ -21053,7 +21169,8 @@ var CompositionUnavailableModeSchema = _enum([
21053
21169
  "null",
21054
21170
  "offline",
21055
21171
  "unknown",
21056
- "release"
21172
+ "release",
21173
+ "omit"
21057
21174
  ]);
21058
21175
  /**
21059
21176
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -21090,9 +21207,11 @@ var CompositionFieldPlanSchema = object({
21090
21207
  reads: array(CompositionFieldReadSchema)
21091
21208
  });
21092
21209
  /**
21093
- * What a feature does to its target (D663): `new` builds a capability on a new
21094
- * device; on an existing device it `add`s a capability the device lacks, or
21095
- * `replace`s named fields of one it has natively.
21210
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
21211
+ * new device; on an existing device it `add`s a capability the device lacks.
21212
+ * Replacing a capability the device already serves is refused
21213
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
21214
+ * wire for a claim the hub already holds.
21096
21215
  */
21097
21216
  var CompositionFeatureModeSchema = _enum([
21098
21217
  "new",
@@ -21265,6 +21384,275 @@ Object.freeze({
21265
21384
  result: ["number", "null"]
21266
21385
  }
21267
21386
  });
21387
+ /**
21388
+ * One catalog row per base reading a group helper can build (D944 §3).
21389
+ * Data, not a form default written into the block.
21390
+ */
21391
+ var NOW = {
21392
+ fieldPath: "lastFetchedAt",
21393
+ expr: "now"
21394
+ };
21395
+ var CHANGED = {
21396
+ fieldPath: "lastChangedAt",
21397
+ expr: "now"
21398
+ };
21399
+ function numeric(input) {
21400
+ return {
21401
+ ...input,
21402
+ family: "numeric",
21403
+ deviceType: DeviceType.Sensor,
21404
+ memberDeviceType: DeviceType.Sensor,
21405
+ commandCaps: [],
21406
+ baseUnit: baseUnitOf(input.quantity),
21407
+ aggregates: NUMERIC_AGGREGATES,
21408
+ accept: [{
21409
+ cap: input.outputCap,
21410
+ fieldPath: input.outputField
21411
+ }]
21412
+ };
21413
+ }
21414
+ function binary(input) {
21415
+ return {
21416
+ ...input,
21417
+ family: "binary",
21418
+ deviceType: DeviceType.Sensor,
21419
+ memberDeviceType: DeviceType.Sensor,
21420
+ commandCaps: [],
21421
+ quantity: "none",
21422
+ baseUnit: void 0,
21423
+ aggregates: BINARY_AGGREGATES,
21424
+ empty: "offline"
21425
+ };
21426
+ }
21427
+ var GROUP_TEMPLATES = [
21428
+ numeric({
21429
+ id: "temperature",
21430
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
21431
+ role: DeviceRole.TemperatureSensor,
21432
+ outputCap: "temperature-sensor",
21433
+ outputField: "celsius",
21434
+ quantity: "temperature",
21435
+ defaultAggregate: "mean",
21436
+ empty: "null",
21437
+ companions: [NOW]
21438
+ }),
21439
+ numeric({
21440
+ id: "humidity",
21441
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
21442
+ role: DeviceRole.HumiditySensor,
21443
+ outputCap: "humidity-sensor",
21444
+ outputField: "percent",
21445
+ quantity: "humidity",
21446
+ defaultAggregate: "mean",
21447
+ empty: "null",
21448
+ companions: [NOW]
21449
+ }),
21450
+ numeric({
21451
+ id: "pressure",
21452
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
21453
+ role: DeviceRole.PressureSensor,
21454
+ outputCap: "pressure-sensor",
21455
+ outputField: "hpa",
21456
+ quantity: "pressure",
21457
+ defaultAggregate: "mean",
21458
+ empty: "null",
21459
+ companions: [NOW]
21460
+ }),
21461
+ numeric({
21462
+ id: "illuminance",
21463
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
21464
+ role: DeviceRole.AmbientLightSensor,
21465
+ outputCap: "ambient-light-sensor",
21466
+ outputField: "lux",
21467
+ quantity: "illuminance",
21468
+ defaultAggregate: "mean",
21469
+ empty: "null",
21470
+ companions: [NOW]
21471
+ }),
21472
+ numeric({
21473
+ id: "power",
21474
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
21475
+ role: DeviceRole.PowerSensor,
21476
+ outputCap: "power-meter",
21477
+ outputField: "watts",
21478
+ quantity: "power",
21479
+ defaultAggregate: "sum",
21480
+ empty: "omit",
21481
+ companions: [NOW]
21482
+ }),
21483
+ numeric({
21484
+ id: "energy",
21485
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
21486
+ role: DeviceRole.EnergySensor,
21487
+ outputCap: "power-meter",
21488
+ outputField: "kwhTotal",
21489
+ quantity: "energy",
21490
+ defaultAggregate: "sum",
21491
+ empty: "omit",
21492
+ companions: [NOW]
21493
+ }),
21494
+ numeric({
21495
+ id: "battery",
21496
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
21497
+ role: DeviceRole.BatterySensor,
21498
+ outputCap: "battery",
21499
+ outputField: "percentage",
21500
+ quantity: "percentage",
21501
+ defaultAggregate: "mean",
21502
+ empty: "null",
21503
+ companions: [
21504
+ {
21505
+ fieldPath: "charging",
21506
+ expr: "\"unknown\""
21507
+ },
21508
+ {
21509
+ fieldPath: "sleeping",
21510
+ expr: "false"
21511
+ },
21512
+ {
21513
+ fieldPath: "lastUpdated",
21514
+ expr: "now"
21515
+ }
21516
+ ]
21517
+ }),
21518
+ numeric({
21519
+ id: "numeric",
21520
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
21521
+ role: DeviceRole.NumericSensor,
21522
+ outputCap: "numeric-sensor",
21523
+ outputField: "value",
21524
+ quantity: "device",
21525
+ defaultAggregate: "mean",
21526
+ empty: "null",
21527
+ companions: [NOW]
21528
+ }),
21529
+ binary({
21530
+ id: "motion",
21531
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
21532
+ role: DeviceRole.MotionSensor,
21533
+ outputCap: "motion",
21534
+ outputField: "detected",
21535
+ defaultAggregate: "any",
21536
+ accept: [{
21537
+ cap: "motion",
21538
+ fieldPath: "detected"
21539
+ }],
21540
+ companions: [{
21541
+ fieldPath: "lastDetectedAt",
21542
+ expr: "null"
21543
+ }, {
21544
+ fieldPath: "autoClearAfterMs",
21545
+ expr: "null"
21546
+ }]
21547
+ }),
21548
+ binary({
21549
+ id: "contact",
21550
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
21551
+ role: DeviceRole.ContactSensor,
21552
+ outputCap: "contact",
21553
+ outputField: "entryOpen",
21554
+ defaultAggregate: "any",
21555
+ accept: [{
21556
+ cap: "contact",
21557
+ fieldPath: "entryOpen"
21558
+ }],
21559
+ companions: [CHANGED]
21560
+ }),
21561
+ binary({
21562
+ id: "leak",
21563
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
21564
+ role: DeviceRole.LeakSensor,
21565
+ outputCap: "flood",
21566
+ outputField: "flooded",
21567
+ defaultAggregate: "any",
21568
+ accept: [{
21569
+ cap: "flood",
21570
+ fieldPath: "flooded"
21571
+ }],
21572
+ companions: [CHANGED]
21573
+ }),
21574
+ binary({
21575
+ id: "smoke",
21576
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
21577
+ role: DeviceRole.SmokeSensor,
21578
+ outputCap: "smoke",
21579
+ outputField: "detected",
21580
+ defaultAggregate: "any",
21581
+ accept: [{
21582
+ cap: "smoke",
21583
+ fieldPath: "detected"
21584
+ }],
21585
+ companions: [CHANGED]
21586
+ }),
21587
+ binary({
21588
+ id: "occupancy",
21589
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
21590
+ role: DeviceRole.BinarySensor,
21591
+ outputCap: "binary",
21592
+ outputField: "on",
21593
+ defaultAggregate: "any",
21594
+ accept: [{
21595
+ cap: "motion",
21596
+ fieldPath: "detected"
21597
+ }, {
21598
+ cap: "binary",
21599
+ fieldPath: "on"
21600
+ }],
21601
+ companions: [CHANGED]
21602
+ }),
21603
+ binary({
21604
+ id: "binary",
21605
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
21606
+ role: DeviceRole.BinarySensor,
21607
+ outputCap: "binary",
21608
+ outputField: "on",
21609
+ defaultAggregate: "any",
21610
+ accept: [{
21611
+ cap: "binary",
21612
+ fieldPath: "on"
21613
+ }],
21614
+ companions: [CHANGED]
21615
+ }),
21616
+ actuator({
21617
+ id: "switch",
21618
+ summary: "On when at least one switch is on, or when all of them are. Offline and unknown members are left out of that decision, never read as off, and still receive the command.",
21619
+ deviceType: DeviceType.Switch,
21620
+ outputCap: "switch",
21621
+ commandCaps: ["switch"]
21622
+ }),
21623
+ actuator({
21624
+ id: "light",
21625
+ summary: "On when at least one light is on, or when all of them are. Brightness and colour are offered only when every member has them. A command is not sent to a member that lacks it.",
21626
+ deviceType: DeviceType.Light,
21627
+ outputCap: "switch",
21628
+ commandCaps: [
21629
+ "switch",
21630
+ "brightness",
21631
+ "color"
21632
+ ]
21633
+ })
21634
+ ];
21635
+ function actuator(input) {
21636
+ return {
21637
+ ...input,
21638
+ family: "actuator",
21639
+ role: void 0,
21640
+ memberDeviceType: input.deviceType,
21641
+ outputField: "on",
21642
+ quantity: "none",
21643
+ baseUnit: void 0,
21644
+ aggregates: BINARY_AGGREGATES,
21645
+ defaultAggregate: "any",
21646
+ empty: "offline",
21647
+ accept: [{
21648
+ cap: "switch",
21649
+ fieldPath: "on"
21650
+ }],
21651
+ companions: [CHANGED]
21652
+ };
21653
+ }
21654
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
21655
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
21268
21656
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
21269
21657
  /**
21270
21658
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -23009,12 +23397,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
23009
23397
  * providers detect renames by matching `uniqueId` and patch `id`
23010
23398
  * in-place so dispatch keeps working.
23011
23399
  *
23012
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
23013
- * `precision`) live in the cap STATUS SLICE so they flow live with every
23014
- * state push and are the single source of truth. The `device_class` tag is
23015
- * an HA-specific input that is consumed at the discovery boundary
23016
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
23017
- * stored after that point.
23400
+ * **Display unit and precision are NOT stored here.** They are a read-time
23401
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
23402
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
23403
+ * neither is repeated on each sample. The `device_class` tag is an
23404
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
23405
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
23018
23406
  *
23019
23407
  * The `raw` escape hatch carries provider-specific attributes that don't
23020
23408
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -23028,6 +23416,14 @@ var SourceInfoSchema = object({
23028
23416
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
23029
23417
  * Used to detect rename when `id` changes. */
23030
23418
  uniqueId: string().optional(),
23419
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
23420
+ quantity: ReadingQuantitySchema.optional(),
23421
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
23422
+ dateTimeShape: _enum([
23423
+ "date",
23424
+ "time",
23425
+ "datetime"
23426
+ ]).optional(),
23031
23427
  /** Provider-specific extras that don't fit the structured slots.
23032
23428
  * Whitelist what goes here per provider — do NOT dump entire HA
23033
23429
  * attribute blobs (would explode the meta JSON column). */
@@ -43944,7 +44340,8 @@ method(object({
43944
44340
  "enum"
43945
44341
  ]),
43946
44342
  enumValues: array(string()).optional(),
43947
- item: boolean().optional()
44343
+ item: boolean().optional(),
44344
+ quantity: ReadingQuantitySchema.optional()
43948
44345
  })).readonly(),
43949
44346
  itemArray: object({
43950
44347
  path: string(),
@@ -48154,8 +48551,8 @@ var MaskPolygonShapeSchema = object({
48154
48551
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
48155
48552
  var MaskGridShapeSchema = object({
48156
48553
  kind: literal("grid"),
48157
- gridWidth: number(),
48158
- gridHeight: number(),
48554
+ gridWidth: number().meta(readingMeta("none")),
48555
+ gridHeight: number().meta(readingMeta("none")),
48159
48556
  cells: array(boolean())
48160
48557
  });
48161
48558
  discriminatedUnion("kind", [
@@ -48267,6 +48664,8 @@ var NcSystemEventKindSchema = _enum([
48267
48664
  "detection-blind",
48268
48665
  "motion-noisy",
48269
48666
  "motion-calm",
48667
+ "package-llm-silent",
48668
+ "package-llm-answered",
48270
48669
  "recording-stalled",
48271
48670
  "recording-healthy",
48272
48671
  "recording-degraded",
@@ -60121,6 +60520,28 @@ var PackageVersionInfoSchema = object({
60121
60520
  deprecated: string().optional(),
60122
60521
  distTags: array(string()).readonly()
60123
60522
  });
60523
+ /**
60524
+ * One published document. `ok` is markdown from the registry. `missing` means
60525
+ * the registry answered and the file was not there. `error` is a registry or
60526
+ * tarball failure — never an empty string standing in for either.
60527
+ */
60528
+ var PackageDocBodySchema = discriminatedUnion("status", [
60529
+ object({
60530
+ status: literal("ok"),
60531
+ markdown: string()
60532
+ }),
60533
+ object({ status: literal("missing") }),
60534
+ object({
60535
+ status: literal("error"),
60536
+ message: string()
60537
+ })
60538
+ ]);
60539
+ var PackageDocsSchema = object({
60540
+ name: string(),
60541
+ version: string(),
60542
+ readme: PackageDocBodySchema,
60543
+ changelog: PackageDocBodySchema
60544
+ });
60124
60545
  var SearchResultSchema = object({
60125
60546
  name: string(),
60126
60547
  version: string(),
@@ -60339,7 +60760,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
60339
60760
  }), object({ success: literal(true) }), {
60340
60761
  kind: "mutation",
60341
60762
  auth: "admin"
60342
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
60763
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
60764
+ name: string().min(1),
60765
+ version: string().min(1).optional()
60766
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
60343
60767
  kind: "mutation",
60344
60768
  auth: "admin"
60345
60769
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -60389,46 +60813,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
60389
60813
  }), LogStreamEntrySchema, { kind: "subscription" });
60390
60814
  object({
60391
60815
  /** Carbon dioxide concentration in ppm. */
60392
- co2Ppm: number().min(0).optional(),
60816
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
60393
60817
  /** Total volatile organic compounds in ppb. */
60394
- vocPpb: number().min(0).optional(),
60818
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
60395
60819
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
60396
- pm25: number().min(0).optional(),
60820
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
60397
60821
  /** Particulate matter ≤ 10 μm in µg/m³. */
60398
- pm10: number().min(0).optional(),
60822
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
60399
60823
  /** Composite AQI value (typically 0..500). */
60400
- aqi: number().optional(),
60824
+ aqi: number().optional().meta(readingMeta("index")),
60401
60825
  /** Ms epoch when the slice was last updated. */
60402
- lastFetchedAt: number(),
60403
- /** Live display unit of the single metric this slice carries (e.g. HA
60404
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
60405
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
60406
- * per slice is unambiguous. */
60407
- unit: string().optional(),
60408
- /** Suggested decimal places for numeric display.
60409
- * Populated live from the upstream source when provided (e.g. HA
60410
- * `attributes.suggested_display_precision`). Falls back to
60411
- * auto-formatting when absent. */
60412
- precision: number().int().min(0).max(10).optional()
60826
+ lastFetchedAt: number()
60413
60827
  });
60414
60828
  DeviceType.Sensor;
60415
60829
  object({
60416
- /** Current illuminance in lux (lx). */
60417
- lux: number().min(0).meta({
60418
- unit: "lx",
60830
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
60831
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
60832
+ precision: 0,
60419
60833
  label: "Illuminance"
60420
- }),
60834
+ })),
60421
60835
  /** Ms epoch when the slice was last updated. */
60422
- lastFetchedAt: number(),
60423
- /** Live display unit from the upstream source (e.g. HA
60424
- * `attributes.unit_of_measurement`). The UI prefers this over the
60425
- * role's canonical unit. Absent → fall back to the canonical unit. */
60426
- unit: string().optional(),
60427
- /** Suggested decimal places for numeric display.
60428
- * Populated live from the upstream source when provided (e.g. HA
60429
- * `attributes.suggested_display_precision`). Falls back to
60430
- * auto-formatting when absent. */
60431
- precision: number().int().min(0).max(10).optional()
60836
+ lastFetchedAt: number()
60432
60837
  });
60433
60838
  DeviceType.Sensor;
60434
60839
  /**
@@ -60573,10 +60978,7 @@ var BatteryStatusSchema = object({
60573
60978
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
60574
60979
  * declines to band a non-finite reading, which is the correct shape.
60575
60980
  */
60576
- percentage: number().min(0).max(100).nullable().meta({
60577
- unit: "%",
60578
- label: "Charge"
60579
- }),
60981
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
60580
60982
  /**
60581
60983
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
60582
60984
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -60603,7 +61005,14 @@ var BatteryStatusSchema = object({
60603
61005
  */
60604
61006
  sleeping: boolean().meta({ label: "Sleeping" }),
60605
61007
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60606
- lastUpdated: number(),
61008
+ lastUpdated: number().meta(readingMeta("none")),
61009
+ /**
61010
+ * Battery voltage in volts, for a cell the firmware reports as voltage
61011
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
61012
+ * that only report charge. A second reading on this cap, not a second
61013
+ * `numeric-sensor` on the same device.
61014
+ */
61015
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
60607
61016
  /**
60608
61017
  * Ms epoch of the last time the device PROVED it was reachable — a
60609
61018
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -60655,7 +61064,6 @@ var BATTERY_DEVICE_TYPES = [
60655
61064
  /** Generic Customize target for the percentage reading (D936). */
60656
61065
  var batteryCustomize = {
60657
61066
  version: 1,
60658
- advanced: "derive",
60659
61067
  targets: [{
60660
61068
  id: "percentage",
60661
61069
  cardinality: "scalar",
@@ -60664,7 +61072,7 @@ var batteryCustomize = {
60664
61072
  stateField: "percentage"
60665
61073
  },
60666
61074
  deviceTypes: BATTERY_DEVICE_TYPES,
60667
- apply: "add-or-replace",
61075
+ apply: "add",
60668
61076
  path: "percentage",
60669
61077
  sources: [{
60670
61078
  kind: "field",
@@ -60785,10 +61193,16 @@ onStatusChanged: { data: object({
60785
61193
  /**
60786
61194
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
60787
61195
  */
60788
- history: { fields: { percentage: {
60789
- kind: "gauge",
60790
- unit: "%"
60791
- } } },
61196
+ history: { fields: {
61197
+ percentage: {
61198
+ kind: "gauge",
61199
+ unit: "%"
61200
+ },
61201
+ volts: {
61202
+ kind: "gauge",
61203
+ unit: "V"
61204
+ }
61205
+ } },
60792
61206
  customize: batteryCustomize,
60793
61207
  /** Clock fields: written, but excluded from the compare that decides
60794
61208
  * whether persisting is worth a SQLite commit. */
@@ -60802,7 +61216,7 @@ object({
60802
61216
  DeviceType.Sensor;
60803
61217
  object({
60804
61218
  /** Current level as 0..100 inclusive. Firmware-reported. */
60805
- percentage: number().min(0).max(100),
61219
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
60806
61220
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
60807
61221
  lastChangedAt: number()
60808
61222
  });
@@ -61187,21 +61601,21 @@ object({
61187
61601
  /** Single setpoint in Celsius. Used by single-target modes
61188
61602
  * (heat / cool / dry). Null when the device is in a range mode
61189
61603
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
61190
- target: number().nullable(),
61604
+ target: number().nullable().meta(readingMeta("temperature")),
61191
61605
  /** Upper bound of the dual setpoint in Celsius. Populated only
61192
61606
  * in `heat_cool` mode. */
61193
- targetHigh: number().nullable(),
61607
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
61194
61608
  /** Lower bound of the dual setpoint in Celsius. Populated only
61195
61609
  * in `heat_cool` mode. */
61196
- targetLow: number().nullable(),
61610
+ targetLow: number().nullable().meta(readingMeta("temperature")),
61197
61611
  /** Target relative humidity (0..100). Null when the device has no
61198
61612
  * humidity control. */
61199
- targetHumidity: number().min(0).max(100).nullable(),
61613
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
61200
61614
  /** Current measured temperature in Celsius. Read-only — pushed by
61201
61615
  * the device's internal sensor. */
61202
- currentTemp: number().nullable(),
61616
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
61203
61617
  /** Current measured relative humidity (0..100). Read-only. */
61204
- currentHumidity: number().min(0).max(100).nullable(),
61618
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
61205
61619
  /** Vertical louver swing on/off. Null when the device has no vertical
61206
61620
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
61207
61621
  swingVertical: boolean().nullable(),
@@ -61209,11 +61623,11 @@ object({
61209
61623
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
61210
61624
  swingHorizontal: boolean().nullable(),
61211
61625
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
61212
- minTemp: number().nullable().optional(),
61626
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
61213
61627
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
61214
- maxTemp: number().nullable().optional(),
61628
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
61215
61629
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
61216
- tempStep: number().positive().nullable().optional(),
61630
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
61217
61631
  /** Ms epoch when the slice was last updated (push or command). */
61218
61632
  lastFetchedAt: number()
61219
61633
  });
@@ -61292,17 +61706,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
61292
61706
  * modes to expose in the picker without a slice fetch.
61293
61707
  */
61294
61708
  var RgbTripletSchema = object({
61295
- r: number().int().min(0).max(255),
61296
- g: number().int().min(0).max(255),
61297
- b: number().int().min(0).max(255)
61709
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
61710
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
61711
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
61298
61712
  });
61299
61713
  var HsvTripletSchema = object({
61300
61714
  /** Hue in degrees, 0..360. */
61301
- h: number().min(0).max(360),
61715
+ h: number().min(0).max(360).meta(readingMeta("none")),
61302
61716
  /** Saturation as 0..100 inclusive. */
61303
- s: number().min(0).max(100),
61717
+ s: number().min(0).max(100).meta(readingMeta("none")),
61304
61718
  /** Value/brightness as 0..100 inclusive. */
61305
- v: number().min(0).max(100)
61719
+ v: number().min(0).max(100).meta(readingMeta("none"))
61306
61720
  });
61307
61721
  var ColorInputSchema = discriminatedUnion("mode", [
61308
61722
  object({
@@ -61331,7 +61745,7 @@ object({
61331
61745
  /** Populated when `mode === 'hsv'`. */
61332
61746
  hsv: HsvTripletSchema.optional(),
61333
61747
  /** Populated when `mode === 'mired'`. */
61334
- mireds: number().int().optional(),
61748
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
61335
61749
  /** Ms epoch of the last operator-driven change. */
61336
61750
  lastChangedAt: number()
61337
61751
  });
@@ -61455,18 +61869,12 @@ _enum([
61455
61869
  "empty",
61456
61870
  "full"
61457
61871
  ]);
61458
- /** Units a remaining-life counter is reported in. Closed: the panel renders
61459
- * each. */
61460
- var ConsumableRemainingUnitSchema = _enum([
61461
- "days",
61462
- "hours",
61463
- "cycles"
61464
- ]);
61465
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
61466
- * days). `value` null = unknown (D393). */
61872
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
61873
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
61874
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
61467
61875
  var ConsumableRemainingSchema = object({
61468
- value: number().min(0).nullable(),
61469
- unit: ConsumableRemainingUnitSchema
61876
+ value: number().min(0).nullable().meta(readingMeta("device")),
61877
+ kind: ConsumableRemainingKindSchema
61470
61878
  });
61471
61879
  var ConsumablesStatusSchema = object({
61472
61880
  items: array(object({
@@ -61475,7 +61883,7 @@ var ConsumablesStatusSchema = object({
61475
61883
  /** Display name. */
61476
61884
  label: string().min(1),
61477
61885
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
61478
- level: number().min(0).max(100).nullable(),
61886
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61479
61887
  /** The provider's discrete verdict when known (binary mode). */
61480
61888
  status: ConsumableStatusSchema.nullable(),
61481
61889
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -61492,7 +61900,7 @@ var ConsumablesStatusSchema = object({
61492
61900
  })),
61493
61901
  lastChangedAt: number()
61494
61902
  });
61495
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
61903
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
61496
61904
  Object.values(DeviceType), method(object({
61497
61905
  deviceId: number().int().nonnegative(),
61498
61906
  key: string().min(1)
@@ -61517,39 +61925,17 @@ object({
61517
61925
  /** Current value. Numeric kind → number; select/text/datetime → string.
61518
61926
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
61519
61927
  * or time-only — HA accepts all three forms). */
61520
- value: union$1([number(), string()]),
61928
+ value: union$1([number().meta(readingMeta("device")), string()]),
61521
61929
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
61522
61930
  options: array(string()),
61523
61931
  /** Ms epoch when the slice was last updated. */
61524
61932
  lastChangedAt: number(),
61525
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
61526
- * Populated live from HA `attributes.unit_of_measurement` on each push.
61527
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61528
- unit: string().optional(),
61529
- /** Minimum allowed value. Populated live from HA `attributes.min`.
61530
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61531
- min: number().optional(),
61532
- /** Maximum allowed value. Populated live from HA `attributes.max`.
61533
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61534
- max: number().optional(),
61535
- /** Step increment. Populated live from HA `attributes.step`.
61536
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
61537
- step: number().optional(),
61538
- /** Suggested decimal places for numeric display. Populated live from HA
61539
- * `attributes.suggested_display_precision`, or derived from `step` when the
61540
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
61541
- * other kinds. Falls back to auto-formatting when absent. */
61542
- precision: number().int().min(0).max(10).optional(),
61543
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
61544
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
61545
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
61546
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
61547
- * `has_date`/`has_time` attributes on each push. */
61548
- format: _enum([
61549
- "date",
61550
- "time",
61551
- "datetime"
61552
- ]).optional()
61933
+ /** Minimum allowed value, in the device quantity's base unit. */
61934
+ min: number().optional().meta(readingMeta("device")),
61935
+ /** Maximum allowed value, in the device quantity's base unit. */
61936
+ max: number().optional().meta(readingMeta("device")),
61937
+ /** Step increment, in the device quantity's base unit. */
61938
+ step: number().optional().meta(readingMeta("device"))
61553
61939
  });
61554
61940
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
61555
61941
  object({
@@ -61587,9 +61973,9 @@ object({
61587
61973
  ]),
61588
61974
  /** 0 = fully closed, 100 = fully open. Null when the device has
61589
61975
  * no intermediate position surface. */
61590
- position: number().min(0).max(100).nullable(),
61976
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61591
61977
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
61592
- tiltPosition: number().min(0).max(100).nullable(),
61978
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61593
61979
  /** Ms epoch when the slice was last updated. */
61594
61980
  lastChangedAt: number()
61595
61981
  });
@@ -61647,9 +62033,9 @@ var NormalizedRangeSchema$1 = object({
61647
62033
  object({
61648
62034
  mode: DayNightModeSchema,
61649
62035
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
61650
- sensitivity: number().optional(),
62036
+ sensitivity: number().optional().meta(readingMeta("none")),
61651
62037
  /** Delay before the IR-cut filter flips, in seconds. */
61652
- switchDelaySec: number().optional(),
62038
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
61653
62039
  lastFetchedAt: number()
61654
62040
  });
61655
62041
  /**
@@ -61804,11 +62190,11 @@ object({
61804
62190
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
61805
62191
  availablePrograms: array(string()),
61806
62192
  /** Seconds remaining on the active cycle, or null when not running / not known. */
61807
- remainingSeconds: number().int().nonnegative().nullable(),
62193
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
61808
62194
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
61809
- delayedStartSeconds: number().int().nonnegative().nullable(),
62195
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
61810
62196
  /** Cycle completion 0..100, or null when not applicable. */
61811
- progressPercent: number().min(0).max(100).nullable(),
62197
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61812
62198
  /** True when the door is reported open; null when the door state is not known. */
61813
62199
  doorOpen: boolean().nullable(),
61814
62200
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -61832,7 +62218,7 @@ object({
61832
62218
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
61833
62219
  machineCareDue: boolean().nullable(),
61834
62220
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
61835
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
62221
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
61836
62222
  /** Option states: null when the appliance does not expose the option or has not said. */
61837
62223
  halfLoad: boolean().nullable(),
61838
62224
  hygienePlus: boolean().nullable(),
@@ -61847,7 +62233,7 @@ object({
61847
62233
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
61848
62234
  activeEvents: array(string()),
61849
62235
  /** Numeric error code when the appliance reports one; null otherwise. */
61850
- errorCode: number().nullable(),
62236
+ errorCode: number().nullable().meta(readingMeta("none")),
61851
62237
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
61852
62238
  errorLabel: string().nullable(),
61853
62239
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -61993,24 +62379,14 @@ object({
61993
62379
  /** Ms epoch of the last press. null = never observed since this provider started. */
61994
62380
  lastPressedAt: number().nullable(),
61995
62381
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
61996
- pressCountSinceStart: number()
62382
+ pressCountSinceStart: number().meta(readingMeta("count"))
61997
62383
  });
61998
62384
  object({
61999
62385
  deviceId: number(),
62000
62386
  timestamp: number()
62001
62387
  });
62002
62388
  DeviceType.Button, DeviceType.Camera;
62003
- /**
62004
- * Enum-state sensor — a string value picked from a finite option set.
62005
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
62006
- * states, weather conditions, contact-source channels, mode strings).
62007
- * The option set is stable across observations; it is persisted in the
62008
- * device config blob (via the control slice at adoption) and is NOT
62009
- * stored in `sourceInfo`.
62010
- */
62011
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
62012
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
62013
- var EnumSensorDateTimeFormatSchema = _enum([
62389
+ _enum([
62014
62390
  "date",
62015
62391
  "time",
62016
62392
  "datetime"
@@ -62018,12 +62394,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
62018
62394
  object({
62019
62395
  value: string(),
62020
62396
  /**
62021
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
62022
- * as a locale date/time/datetime; absent for plain enum sensors. HA
62023
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
62024
- * a time-only sensor → `'time'`.
62397
+ * Set when `value` is a timestamp rather than an enum state, so history
62398
+ * does not learn ISO strings as states. The display shape (date / time /
62399
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
62025
62400
  */
62026
- format: EnumSensorDateTimeFormatSchema.optional(),
62401
+ timestamp: boolean().optional(),
62027
62402
  /** Ms epoch when the slice was last updated. */
62028
62403
  lastFetchedAt: number()
62029
62404
  });
@@ -62042,13 +62417,13 @@ var EventFireSchema = object({
62042
62417
  deviceId: number(),
62043
62418
  eventType: string(),
62044
62419
  data: record(string(), unknown()).nullable(),
62045
- timestamp: number(),
62046
- seq: number()
62420
+ timestamp: number().meta(readingMeta("none")),
62421
+ seq: number().meta(readingMeta("count"))
62047
62422
  });
62048
62423
  object({
62049
62424
  eventTypes: array(string()),
62050
62425
  lastEvent: EventFireSchema.nullable(),
62051
- eventCountSinceStart: number()
62426
+ eventCountSinceStart: number().meta(readingMeta("count"))
62052
62427
  });
62053
62428
  DeviceType.EventEmitter;
62054
62429
  var IdentitySchema = object({
@@ -62437,10 +62812,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
62437
62812
  object({
62438
62813
  /** Active speed as 0..100 inclusive. Null when the device has no
62439
62814
  * speed surface (single-speed fan). */
62440
- percentage: number().min(0).max(100).nullable(),
62815
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62441
62816
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
62442
62817
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
62443
- percentageStep: number().positive().optional(),
62818
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
62444
62819
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
62445
62820
  * — empty string when no preset is active or supported. */
62446
62821
  preset: string(),
@@ -62522,7 +62897,7 @@ var FeatureProbeStatusSchema = object({
62522
62897
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
62523
62898
  model: string().nullable(),
62524
62899
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
62525
- channelCount: number().nullable(),
62900
+ channelCount: number().nullable().meta(readingMeta("count")),
62526
62901
  /**
62527
62902
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
62528
62903
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -62554,9 +62929,39 @@ object({
62554
62929
  lastChangedAt: number()
62555
62930
  });
62556
62931
  DeviceType.Sensor;
62932
+ object({
62933
+ /** One row per member that has been commanded. A later command replaces that member's row. */
62934
+ results: array(object({
62935
+ deviceId: number().int().nonnegative(),
62936
+ cap: string().min(1),
62937
+ method: string().min(1),
62938
+ ok: boolean(),
62939
+ error: string().optional(),
62940
+ /** Ms epoch of the attempt. */
62941
+ at: number()
62942
+ })) });
62943
+ DeviceType.Switch, DeviceType.Light;
62944
+ object({
62945
+ /** Members whose current value entered the aggregate. */
62946
+ included: number().int().nonnegative().meta(readingMeta("count", {
62947
+ precision: 0,
62948
+ label: "Reporting"
62949
+ })),
62950
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
62951
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
62952
+ precision: 0,
62953
+ label: "Excluded"
62954
+ })),
62955
+ /** `included` + `excluded`. */
62956
+ total: number().int().nonnegative().meta(readingMeta("count", {
62957
+ precision: 0,
62958
+ label: "Members"
62959
+ }))
62960
+ });
62961
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
62557
62962
  object({
62558
62963
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
62559
- position: number().min(0).max(100).nullable(),
62964
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62560
62965
  /** Ms epoch when the slice was last updated (push or poll). */
62561
62966
  lastFetchedAt: number()
62562
62967
  });
@@ -62565,9 +62970,9 @@ object({
62565
62970
  /** Whether the humidifier is currently on. */
62566
62971
  on: boolean(),
62567
62972
  /** Current measured relative humidity (0..100). Null when not reported. */
62568
- currentHumidity: number().min(0).max(100).nullable(),
62973
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
62569
62974
  /** Target relative humidity (0..100). Null when no setpoint surface. */
62570
- targetHumidity: number().min(0).max(100).nullable(),
62975
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
62571
62976
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
62572
62977
  * device has no mode surface. */
62573
62978
  mode: string().nullable(),
@@ -62577,9 +62982,9 @@ object({
62577
62982
  * `idle` / `off`). Null when the device doesn't report it. */
62578
62983
  action: string().nullable(),
62579
62984
  /** HA `min_humidity` attribute. Null → UI uses 0. */
62580
- minHumidity: number().nullable(),
62985
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
62581
62986
  /** HA `max_humidity` attribute. Null → UI uses 100. */
62582
- maxHumidity: number().nullable(),
62987
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
62583
62988
  /** Ms epoch when the slice was last updated. */
62584
62989
  lastChangedAt: number()
62585
62990
  });
@@ -62603,22 +63008,13 @@ DeviceType.Humidifier, method(object({
62603
63008
  auth: "protected"
62604
63009
  });
62605
63010
  object({
62606
- /** Current relative humidity, 0..100. */
62607
- percent: number().min(0).max(100).meta({
62608
- unit: "%",
63011
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
63012
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
63013
+ precision: 0,
62609
63014
  label: "Humidity"
62610
- }),
63015
+ })),
62611
63016
  /** Ms epoch when the slice was last updated. */
62612
- lastFetchedAt: number(),
62613
- /** Live display unit from the upstream source (e.g. HA
62614
- * `attributes.unit_of_measurement`). The UI prefers this over the
62615
- * role's canonical unit. Absent → fall back to the canonical unit. */
62616
- unit: string().optional(),
62617
- /** Suggested decimal places for numeric display.
62618
- * Populated live from the upstream source when provided (e.g. HA
62619
- * `attributes.suggested_display_precision`). Falls back to
62620
- * auto-formatting when absent. */
62621
- precision: number().int().min(0).max(10).optional()
63017
+ lastFetchedAt: number()
62622
63018
  });
62623
63019
  DeviceType.Sensor, DeviceType.Thermostat;
62624
63020
  object({
@@ -62626,7 +63022,7 @@ object({
62626
63022
  * entity exposes no `entity_picture` (yet). */
62627
63023
  url: string().nullable(),
62628
63024
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
62629
- lastUpdated: number().nullable()
63025
+ lastUpdated: number().nullable().meta(readingMeta("none"))
62630
63026
  });
62631
63027
  DeviceType.Image;
62632
63028
  /**
@@ -62682,19 +63078,19 @@ var NormalizedRangeSchema = object({
62682
63078
  });
62683
63079
  object({
62684
63080
  /** Normalized 0–100. */
62685
- brightness: number().optional(),
63081
+ brightness: number().optional().meta(readingMeta("none")),
62686
63082
  /** Normalized 0–100. */
62687
- contrast: number().optional(),
63083
+ contrast: number().optional().meta(readingMeta("none")),
62688
63084
  /** Normalized 0–100. */
62689
- saturation: number().optional(),
63085
+ saturation: number().optional().meta(readingMeta("none")),
62690
63086
  /** Normalized 0–100. */
62691
- sharpness: number().optional(),
63087
+ sharpness: number().optional().meta(readingMeta("none")),
62692
63088
  mirror: boolean().optional(),
62693
63089
  flip: boolean().optional(),
62694
63090
  rotate: ImageRotateSchema.optional(),
62695
63091
  whiteBalance: WhiteBalanceModeSchema.optional(),
62696
63092
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
62697
- warmth: number().optional(),
63093
+ warmth: number().optional().meta(readingMeta("none")),
62698
63094
  exposureMode: ExposureModeSchema.optional(),
62699
63095
  backlightMode: BacklightModeSchema.optional(),
62700
63096
  lastFetchedAt: number()
@@ -62939,10 +63335,10 @@ var IntercomAbilitySchema = object({
62939
63335
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
62940
63336
  codecs: array(string()),
62941
63337
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
62942
- sampleRate: number(),
63338
+ sampleRate: number().meta(readingMeta("none")),
62943
63339
  duplex: _enum(["half", "full"]),
62944
63340
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
62945
- maxBacklogMs: number()
63341
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
62946
63342
  });
62947
63343
  var IntercomStatusSchema = object({
62948
63344
  /** True while a talk session is open. */
@@ -63053,13 +63449,13 @@ object({
63053
63449
  activity: LawnMowerActivitySchema,
63054
63450
  /** 0..100 battery percentage. Null when the device has no battery
63055
63451
  * reading. */
63056
- batteryLevel: number().min(0).max(100).nullable(),
63452
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63057
63453
  /** 0..100 mowing-completion percentage of the current task, or null when no
63058
63454
  * task is active / progress is unavailable. */
63059
- progressPercent: number().min(0).max(100).nullable(),
63455
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63060
63456
  /** Current device/event code (dynamic — mostly status, sometimes an error),
63061
63457
  * or null when unknown. */
63062
- currentCode: number().nullable(),
63458
+ currentCode: number().nullable().meta(readingMeta("none")),
63063
63459
  /** Human label for {@link currentCode}, or null when undecodable. */
63064
63460
  currentCodeLabel: string().nullable(),
63065
63461
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -63521,7 +63917,7 @@ object({
63521
63917
  state: MediaPlayerStateSchema,
63522
63918
  /** Volume as 0..100 inclusive. Null when the device has no volume
63523
63919
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
63524
- volumeLevel: number().min(0).max(100).nullable(),
63920
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63525
63921
  /** Mute toggle distinct from volume=0. Null when no mute surface.
63526
63922
  * Pair with `DeviceFeature.MediaPlayerMute`. */
63527
63923
  isMuted: boolean().nullable(),
@@ -63534,10 +63930,10 @@ object({
63534
63930
  currentMedia: MediaInfoSchema.nullable(),
63535
63931
  /** Current playback position in ms. Null when not seekable or
63536
63932
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
63537
- positionMs: number().int().nonnegative().nullable(),
63933
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
63538
63934
  /** Total duration of the current media in ms. Null when unknown
63539
63935
  * (live stream) or nothing is playing. */
63540
- durationMs: number().int().nonnegative().nullable(),
63936
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
63541
63937
  /** Shuffle toggle. Null when no shuffle surface.
63542
63938
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
63543
63939
  shuffle: boolean().nullable(),
@@ -63837,7 +64233,7 @@ object({
63837
64233
  * it, every provider seeded it at boot, and every session left it
63838
64234
  * `detected: true` after its detach.
63839
64235
  */
63840
- autoClearAfterMs: number().nullable()
64236
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
63841
64237
  });
63842
64238
  /**
63843
64239
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -63887,7 +64283,7 @@ var MotionZoneRegionSchema = object({
63887
64283
  });
63888
64284
  object({
63889
64285
  enabled: boolean(),
63890
- sensitivity: number(),
64286
+ sensitivity: number().meta(readingMeta("none")),
63891
64287
  /** Grid region(s). Today exactly one `grid` shape. */
63892
64288
  regions: array(MotionZoneRegionSchema),
63893
64289
  lastFetchedAt: number()
@@ -64191,7 +64587,7 @@ var NavigationStatusSchema = object({
64191
64587
  following: boolean(),
64192
64588
  flash: boolean(),
64193
64589
  lightMode: NavigationLightModeSchema,
64194
- lightLevel: number().min(40).max(100),
64590
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
64195
64591
  /** Ms epoch when the slice was last updated. */
64196
64592
  lastChangedAt: number()
64197
64593
  });
@@ -64237,13 +64633,13 @@ var NetworkLinkStatusSchema = object({
64237
64633
  * one whose reading has not landed must not be drawn at 0 %. Consumers
64238
64634
  * SKIP a null rather than coerce it.
64239
64635
  */
64240
- signalPercent: number().min(0).max(100).nullable(),
64636
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64241
64637
  /** Raw received signal strength in dBm, when the firmware reports one. */
64242
- rssiDbm: number().optional(),
64638
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
64243
64639
  /** Network name of a wireless link, when the firmware reports it. */
64244
64640
  ssid: string().optional(),
64245
64641
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
64246
- lastUpdated: number()
64642
+ lastUpdated: number().meta(readingMeta("none"))
64247
64643
  });
64248
64644
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
64249
64645
  deviceId: number(),
@@ -64562,7 +64958,7 @@ object({
64562
64958
  lastError: string().nullable(),
64563
64959
  /** Number of deliveries currently buffered server-side (rate-limited
64564
64960
  * notifiers may queue). 0 when the provider sends synchronously. */
64565
- queueDepth: number().int().nonnegative(),
64961
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
64566
64962
  /** Per-feature capability matrix — authoritative source for the
64567
64963
  * compose-form field gating. */
64568
64964
  supports: NotifierSupportsSchema
@@ -64612,15 +65008,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
64612
65008
  acceptedAt: number()
64613
65009
  });
64614
65010
  object({
64615
- value: number(),
64616
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
64617
- * Populated live from the upstream source on each state push.
64618
- * Absent for unitless sensors. */
64619
- unit: string().optional(),
64620
- /** Suggested decimal places for numeric display.
64621
- * Populated live from the upstream source when provided.
64622
- * Falls back to auto-formatting when absent. */
64623
- precision: number().int().min(0).max(10).optional(),
65011
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
65012
+ value: number().nullable().meta(readingMeta("device")),
64624
65013
  /** Ms epoch when the slice was last updated. */
64625
65014
  lastFetchedAt: number()
64626
65015
  });
@@ -64910,23 +65299,26 @@ object({
64910
65299
  /** Food currently in the bowl (grams). Null when the device has not
64911
65300
  * reported a reading yet. On dual-hopper models this is the combined
64912
65301
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
64913
- foodLevel: number().nullable(),
65302
+ foodLevel: number().nullable().meta(readingMeta("none")),
64914
65303
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
64915
65304
  * single-hopper models. */
64916
- food1: number().nullable(),
65305
+ food1: number().nullable().meta(readingMeta("none")),
64917
65306
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
64918
65307
  * single-hopper models. */
64919
- food2: number().nullable(),
65308
+ food2: number().nullable().meta(readingMeta("none")),
64920
65309
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
64921
65310
  * (`device_class: problem`, on = low). True when the bowl is empty /
64922
65311
  * below the feeder's low threshold. */
64923
65312
  lowFood: boolean(),
64924
65313
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
64925
65314
  * device has no battery reading. */
64926
- batteryPower: number().min(0).max(100).nullable(),
64927
- /** Days of desiccant life remaining. Null when the model has no
64928
- * desiccant sensor. */
64929
- desiccantLeftDays: number().nullable(),
65315
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65316
+ /**
65317
+ * Desiccant life remaining, stored in seconds (duration base, D944).
65318
+ * Providers convert from the days the feeder reports. Null when the
65319
+ * model has no desiccant sensor.
65320
+ */
65321
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
64930
65322
  /** True while a feed is in progress. */
64931
65323
  feeding: boolean(),
64932
65324
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -64937,7 +65329,7 @@ object({
64937
65329
  * with `errorCode` for consumers that want the raw integer. */
64938
65330
  error: string().nullable(),
64939
65331
  /** Raw device error code (0 / null = no error). */
64940
- errorCode: number().nullable(),
65332
+ errorCode: number().nullable().meta(readingMeta("none")),
64941
65333
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
64942
65334
  isDualHopper: boolean(),
64943
65335
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -64947,7 +65339,7 @@ object({
64947
65339
  /** Play a chime when dispensing. */
64948
65340
  feedSound: boolean(),
64949
65341
  /** Speaker / prompt volume level (device-scaled integer). */
64950
- volume: number(),
65342
+ volume: number().meta(readingMeta("none")),
64951
65343
  /** Ms epoch when the slice was last refreshed from the cloud. */
64952
65344
  lastFetchedAt: number()
64953
65345
  });
@@ -65384,36 +65776,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
65384
65776
  });
65385
65777
  object({
65386
65778
  /** Instantaneous power draw in watts. */
65387
- watts: number().optional().meta({
65388
- unit: "W",
65389
- label: "Power"
65390
- }),
65779
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
65391
65780
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
65392
- kwhTotal: number().optional().meta({
65393
- unit: "kWh",
65394
- label: "Energy"
65395
- }),
65781
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
65396
65782
  /** Voltage in volts. */
65397
- volts: number().optional(),
65783
+ volts: number().optional().meta(readingMeta("voltage")),
65398
65784
  /** Current in amperes. */
65399
- amps: number().optional(),
65400
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
65401
- va: number().optional(),
65785
+ amps: number().optional().meta(readingMeta("current")),
65786
+ /** Apparent power in volt-amperes. */
65787
+ va: number().optional().meta(readingMeta("apparent-power")),
65402
65788
  /** Power factor (0..1) if reported. */
65403
- powerFactor: number().min(0).max(1).optional(),
65789
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
65404
65790
  /** Ms epoch when the slice was last updated. */
65405
- lastFetchedAt: number(),
65406
- /** Live display unit of the single metric this slice carries (e.g. HA
65407
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
65408
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
65409
- * unit per slice is unambiguous. The UI prefers this over the role's
65410
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
65411
- unit: string().optional(),
65412
- /** Suggested decimal places for numeric display.
65413
- * Populated live from the upstream source when provided (e.g. HA
65414
- * `attributes.suggested_display_precision`). Falls back to
65415
- * auto-formatting when absent. */
65416
- precision: number().int().min(0).max(10).optional()
65791
+ lastFetchedAt: number()
65417
65792
  });
65418
65793
  DeviceType.Sensor;
65419
65794
  /**
@@ -65434,11 +65809,11 @@ DeviceType.Sensor;
65434
65809
  */
65435
65810
  var GpsLocationSchema = object({
65436
65811
  /** Latitude in decimal degrees, -90..90. */
65437
- latitude: number().min(-90).max(90),
65812
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
65438
65813
  /** Longitude in decimal degrees, -180..180. */
65439
- longitude: number().min(-180).max(180),
65814
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
65440
65815
  /** Reported accuracy in meters (lower = better). */
65441
- accuracyMeters: number().nonnegative()
65816
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
65442
65817
  });
65443
65818
  object({
65444
65819
  /** `home` / `not_home` / any user-defined zone name. */
@@ -65450,25 +65825,16 @@ object({
65450
65825
  gps: GpsLocationSchema.nullable(),
65451
65826
  /** Optional battery level of the tracking device (0..100). Null
65452
65827
  * when not reported. */
65453
- batteryPercent: number().min(0).max(100).nullable(),
65828
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65454
65829
  /** Ms epoch when the slice was last updated. */
65455
65830
  lastChangedAt: number()
65456
65831
  });
65457
65832
  DeviceType.Presence;
65458
65833
  object({
65459
- /** Current pressure in hPa. */
65460
- hpa: number(),
65834
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
65835
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
65461
65836
  /** Ms epoch when the slice was last updated. */
65462
- lastFetchedAt: number(),
65463
- /** Live display unit from the upstream source (e.g. HA
65464
- * `attributes.unit_of_measurement`). The UI prefers this over the
65465
- * role's canonical unit. Absent → fall back to the canonical unit. */
65466
- unit: string().optional(),
65467
- /** Suggested decimal places for numeric display.
65468
- * Populated live from the upstream source when provided (e.g. HA
65469
- * `attributes.suggested_display_precision`). Falls back to
65470
- * auto-formatting when absent. */
65471
- precision: number().int().min(0).max(10).optional()
65837
+ lastFetchedAt: number()
65472
65838
  });
65473
65839
  DeviceType.Sensor;
65474
65840
  /**
@@ -65801,8 +66167,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
65801
66167
  }), _void(), { kind: "mutation" });
65802
66168
  var PtzAutotrackSettingsSchema = object({
65803
66169
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
65804
- stopDelaySeconds: number().int().min(0).max(300),
65805
- disappearDelaySeconds: number().int().min(0).max(300)
66170
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
66171
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
65806
66172
  });
65807
66173
  /**
65808
66174
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -65859,7 +66225,7 @@ object({
65859
66225
  /** null = unknown — never coerced to false (D393). */
65860
66226
  running: boolean().nullable(),
65861
66227
  /** Epoch ms the current boolean began; null while `running` is null. */
65862
- since: number().nullable(),
66228
+ since: number().nullable().meta(readingMeta("none")),
65863
66229
  source: RunningStateSourceSchema.meta({ label: "Source" }),
65864
66230
  phase: string().nullable(),
65865
66231
  /** null until a reading has been published (D393). */
@@ -67547,9 +67913,9 @@ var RecordTriggerSchema = _enum([
67547
67913
  */
67548
67914
  var RecordWindowSchema = object({
67549
67915
  trigger: RecordTriggerSchema,
67550
- day: number().int().min(0).max(6),
67551
- startMinute: number().int().min(0).max(1439),
67552
- endMinute: number().int().min(1).max(1440)
67916
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
67917
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
67918
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
67553
67919
  });
67554
67920
  /** Status of one physical volume, as the camera itself describes it. */
67555
67921
  var OnboardStorageVolumeSchema = object({
@@ -67618,10 +67984,10 @@ object({
67618
67984
  /** Master "record to the card at all" switch. */
67619
67985
  enabled: boolean().nullable(),
67620
67986
  overwriteWhenFull: boolean().nullable(),
67621
- preRecordSec: number().nullable(),
67622
- postRecordSec: number().nullable(),
67987
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
67988
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
67623
67989
  /** Length of one recorded file, in minutes. */
67624
- segmentMinutes: number().nullable(),
67990
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
67625
67991
  /** The primary track's weekly windows, flattened. */
67626
67992
  windows: array(RecordWindowSchema),
67627
67993
  /**
@@ -67637,7 +68003,7 @@ object({
67637
68003
  * provider reporting a non-zero count also reports the schedule as not
67638
68004
  * writable.
67639
68005
  */
67640
- unreadableWindows: number(),
68006
+ unreadableWindows: number().meta(readingMeta("count")),
67641
68007
  /**
67642
68008
  * The camera is scheduled to record and has NO usable storage.
67643
68009
  *
@@ -68308,7 +68674,7 @@ var CamStreamDescriptorSchema = object({
68308
68674
  url: string().optional(),
68309
68675
  codec: string().optional(),
68310
68676
  resolution: CamStreamResolutionSchema.optional(),
68311
- fps: number().positive().optional(),
68677
+ fps: number().positive().optional().meta(readingMeta("rate")),
68312
68678
  label: string().optional(),
68313
68679
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
68314
68680
  deviceFeatures: array(string()).optional(),
@@ -68338,18 +68704,18 @@ var StreamProfileSchema = _enum([
68338
68704
  ]);
68339
68705
  /** The current encoding config of one stream profile. */
68340
68706
  var StreamProfileConfigSchema = object({
68341
- width: number(),
68342
- height: number(),
68707
+ width: number().meta(readingMeta("none")),
68708
+ height: number().meta(readingMeta("none")),
68343
68709
  codec: _enum(["h264", "h265"]),
68344
- framerate: number(),
68345
- bitrate: number(),
68710
+ framerate: number().meta(readingMeta("rate")),
68711
+ bitrate: number().meta(readingMeta("bitrate")),
68346
68712
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
68347
68713
  encoderProfile: _enum([
68348
68714
  "high",
68349
68715
  "main",
68350
68716
  "baseline"
68351
68717
  ]).optional(),
68352
- gop: number().optional(),
68718
+ gop: number().optional().meta(readingMeta("none")),
68353
68719
  /**
68354
68720
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
68355
68721
  *
@@ -68992,23 +69358,13 @@ object({
68992
69358
  });
68993
69359
  DeviceType.Sensor;
68994
69360
  object({
68995
- /** Current temperature in Celsius. */
68996
- celsius: number().meta({
68997
- unit: "°C",
69361
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
69362
+ celsius: number().nullable().meta(readingMeta("temperature", {
69363
+ precision: 1,
68998
69364
  label: "Temperature"
68999
- }),
69365
+ })),
69000
69366
  /** Ms epoch when the slice was last updated (push or poll). */
69001
- lastFetchedAt: number(),
69002
- /** Live display unit from the upstream source (e.g. HA
69003
- * `attributes.unit_of_measurement`). The UI prefers this over the
69004
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
69005
- * Absent → the UI falls back to the role's canonical unit. */
69006
- unit: string().optional(),
69007
- /** Suggested decimal places for numeric display.
69008
- * Populated live from the upstream source when provided (e.g. HA
69009
- * `attributes.suggested_display_precision`). Falls back to
69010
- * auto-formatting when absent. */
69011
- precision: number().int().min(0).max(10).optional()
69367
+ lastFetchedAt: number()
69012
69368
  });
69013
69369
  DeviceType.Sensor, DeviceType.Thermostat;
69014
69370
  /**
@@ -69260,16 +69616,16 @@ object({
69260
69616
  ]),
69261
69617
  /** 0..100 battery percentage. Null when the device has no battery
69262
69618
  * reading. */
69263
- batteryLevel: number().min(0).max(100).nullable(),
69619
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
69264
69620
  /** Current fan-speed token (provider-verbatim). Null when unknown or
69265
69621
  * the vacuum has no speed control. */
69266
69622
  fanSpeed: string().nullable(),
69267
69623
  /** Speed tokens the hardware accepts — drives the UI selector. */
69268
69624
  availableFanSpeeds: array(string()),
69269
69625
  /** 0..100 cleaning-completion percentage of the current task, or null. */
69270
- progressPercent: number().min(0).max(100).nullable(),
69626
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
69271
69627
  /** Current error code (0 / null = no error). */
69272
- errorCode: number().nullable(),
69628
+ errorCode: number().nullable().meta(readingMeta("none")),
69273
69629
  /** Human label for {@link errorCode}, or null when none / undecodable. */
69274
69630
  errorLabel: string().nullable(),
69275
69631
  /** Ms epoch when the slice was last updated. */
@@ -69308,7 +69664,7 @@ object({
69308
69664
  ]),
69309
69665
  /** 0 = fully closed, 100 = fully open. Null when the device has no
69310
69666
  * intermediate position surface. */
69311
- position: number().min(0).max(100).nullable(),
69667
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
69312
69668
  /** Ms epoch when the slice was last updated. */
69313
69669
  lastChangedAt: number()
69314
69670
  });
@@ -69336,9 +69692,9 @@ object({
69336
69692
  DeviceType.Sensor;
69337
69693
  object({
69338
69694
  /** Current measured temperature. Null when not reported. */
69339
- currentTemp: number().nullable(),
69695
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
69340
69696
  /** Target temperature setpoint. Null when no setpoint surface. */
69341
- targetTemp: number().nullable(),
69697
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
69342
69698
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
69343
69699
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
69344
69700
  * device reports an unknown state. */
@@ -69349,9 +69705,9 @@ object({
69349
69705
  * has no away surface. */
69350
69706
  away: boolean().nullable(),
69351
69707
  /** HA `min_temp` attribute. Null when not reported. */
69352
- minTemp: number().nullable(),
69708
+ minTemp: number().nullable().meta(readingMeta("temperature")),
69353
69709
  /** HA `max_temp` attribute. Null when not reported. */
69354
- maxTemp: number().nullable(),
69710
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
69355
69711
  /** Ms epoch when the slice was last updated. */
69356
69712
  lastChangedAt: number()
69357
69713
  });
@@ -69375,25 +69731,18 @@ DeviceType.WaterHeater, method(object({
69375
69731
  auth: "protected"
69376
69732
  });
69377
69733
  object({
69378
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
69379
- * when no condition has been reported yet. */
69734
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
69380
69735
  condition: string().nullable(),
69381
- /** Current temperature in the reported unit. Null when not provided. */
69382
- temperature: number().nullable(),
69383
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
69384
- temperatureUnit: string().nullable(),
69736
+ /** Current temperature in Celsius. Null when not provided or refused. */
69737
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
69385
69738
  /** Relative humidity (0..100). Null when not provided. */
69386
- humidity: number().min(0).max(100).nullable(),
69387
- /** Barometric pressure in the reported unit. Null when not provided. */
69388
- pressure: number().nullable(),
69389
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
69390
- pressureUnit: string().nullable(),
69391
- /** Wind speed in the reported unit. Null when not provided. */
69392
- windSpeed: number().nullable(),
69393
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
69394
- windSpeedUnit: string().nullable(),
69739
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
69740
+ /** Barometric pressure in hPa. Null when not provided or refused. */
69741
+ pressure: number().nullable().meta(readingMeta("pressure")),
69742
+ /** Wind speed in metres per second. Null when not provided or refused. */
69743
+ windSpeed: number().nullable().meta(readingMeta("speed")),
69395
69744
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
69396
- windBearing: number().nullable(),
69745
+ windBearing: number().nullable().meta(readingMeta("angle")),
69397
69746
  /** Ms epoch when the slice was last updated. */
69398
69747
  lastFetchedAt: number()
69399
69748
  });
@@ -70363,6 +70712,13 @@ Object.freeze({
70363
70712
  access: "view",
70364
70713
  role: "admin"
70365
70714
  },
70715
+ "addons.getPackageDocs": {
70716
+ capName: "addons",
70717
+ capScope: "system",
70718
+ addonId: null,
70719
+ access: "view",
70720
+ role: "admin"
70721
+ },
70366
70722
  "addons.getVersions": {
70367
70723
  capName: "addons",
70368
70724
  capScope: "system",
@@ -73471,6 +73827,20 @@ Object.freeze({
73471
73827
  access: "view",
73472
73828
  role: "viewer"
73473
73829
  },
73830
+ "groupCommands.getStatus": {
73831
+ capName: "group-commands",
73832
+ capScope: "device",
73833
+ addonId: null,
73834
+ access: "view",
73835
+ role: "viewer"
73836
+ },
73837
+ "groupStatus.getStatus": {
73838
+ capName: "group-status",
73839
+ capScope: "device",
73840
+ addonId: null,
73841
+ access: "view",
73842
+ role: "viewer"
73843
+ },
73474
73844
  "heatingValve.getStatus": {
73475
73845
  capName: "heating-valve",
73476
73846
  capScope: "device",
@@ -83293,6 +83663,37 @@ var SENSOR_CAP_VOCABULARY = {
83293
83663
  icon: "flame"
83294
83664
  }]
83295
83665
  },
83666
+ "group-commands": {
83667
+ scope: "device",
83668
+ noun: t("Group command", "Comando di gruppo")
83669
+ },
83670
+ "group-status": {
83671
+ scope: "device",
83672
+ noun: t("Group", "Gruppo"),
83673
+ state: [
83674
+ {
83675
+ kind: "numeric",
83676
+ field: "included",
83677
+ quantity: t("the members reporting", "i membri che rispondono"),
83678
+ label: t("Reporting", "Rispondono"),
83679
+ icon: "users"
83680
+ },
83681
+ {
83682
+ kind: "numeric",
83683
+ field: "excluded",
83684
+ quantity: t("the members left out", "i membri esclusi"),
83685
+ label: t("Excluded", "Esclusi"),
83686
+ icon: "users"
83687
+ },
83688
+ {
83689
+ kind: "numeric",
83690
+ field: "total",
83691
+ quantity: t("the member count", "il numero di membri"),
83692
+ label: t("Members", "Membri"),
83693
+ icon: "sigma"
83694
+ }
83695
+ ]
83696
+ },
83296
83697
  "humidity-sensor": {
83297
83698
  scope: "device",
83298
83699
  noun: t("Humidity", "Umidità"),
@@ -83326,7 +83727,6 @@ var SENSOR_CAP_VOCABULARY = {
83326
83727
  field: "value",
83327
83728
  quantity: t("the reading", "il valore"),
83328
83729
  label: t("Reading", "Valore"),
83329
- unitField: "unit",
83330
83730
  icon: "gauge"
83331
83731
  }]
83332
83732
  },
@@ -83462,7 +83862,7 @@ var SENSOR_CAP_VOCABULARY = {
83462
83862
  field: "temperature",
83463
83863
  quantity: t("the outdoor temperature", "la temperatura esterna"),
83464
83864
  label: t("Temperature", "Temperatura"),
83465
- unitField: "temperatureUnit",
83865
+ unit: "°C",
83466
83866
  icon: "thermometer"
83467
83867
  },
83468
83868
  {
@@ -83478,7 +83878,7 @@ var SENSOR_CAP_VOCABULARY = {
83478
83878
  field: "windSpeed",
83479
83879
  quantity: t("the wind speed", "la velocità del vento"),
83480
83880
  label: t("Wind", "Vento"),
83481
- unitField: "windSpeedUnit",
83881
+ unit: "m/s",
83482
83882
  icon: "wind"
83483
83883
  },
83484
83884
  {
@@ -83486,7 +83886,7 @@ var SENSOR_CAP_VOCABULARY = {
83486
83886
  field: "pressure",
83487
83887
  quantity: t("the air pressure", "la pressione atmosferica"),
83488
83888
  label: t("Pressure", "Pressione"),
83489
- unitField: "pressureUnit",
83889
+ unit: "hPa",
83490
83890
  icon: "gauge"
83491
83891
  }
83492
83892
  ]
@@ -84759,6 +85159,16 @@ Object.freeze({
84759
85159
  form: "single",
84760
85160
  optional: false
84761
85161
  }],
85162
+ "groupCommands.getStatus": [{
85163
+ name: "deviceId",
85164
+ form: "single",
85165
+ optional: false
85166
+ }],
85167
+ "groupStatus.getStatus": [{
85168
+ name: "deviceId",
85169
+ form: "single",
85170
+ optional: false
85171
+ }],
84762
85172
  "heatingValve.getStatus": [{
84763
85173
  name: "deviceId",
84764
85174
  form: "single",
@@ -86793,6 +87203,8 @@ new Map(Object.entries({
86793
87203
  "detection-blind": "device-events",
86794
87204
  "motion-noisy": "device-events",
86795
87205
  "motion-calm": "device-events",
87206
+ "package-llm-silent": "device-events",
87207
+ "package-llm-answered": "device-events",
86796
87208
  "recording-stalled": "device-events",
86797
87209
  "recording-healthy": "device-events",
86798
87210
  "recording-degraded": "device-events",
@@ -86876,6 +87288,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
86876
87288
  label: "Motion without a subject",
86877
87289
  kinds: ["motion-noisy", "motion-calm"]
86878
87290
  },
87291
+ {
87292
+ id: "package-llm",
87293
+ family: "device-events",
87294
+ label: "Package confirm",
87295
+ kinds: ["package-llm-silent", "package-llm-answered"]
87296
+ },
86879
87297
  {
86880
87298
  id: "recording",
86881
87299
  family: "device-events",
@@ -87057,6 +87475,8 @@ var DEVICE_EVENTS = [
87057
87475
  "detection-blind",
87058
87476
  "motion-noisy",
87059
87477
  "motion-calm",
87478
+ "package-llm-silent",
87479
+ "package-llm-answered",
87060
87480
  "recording-stalled",
87061
87481
  "recording-healthy",
87062
87482
  "recording-degraded",
@@ -87325,6 +87745,15 @@ new Map([
87325
87745
  snapshotLabel: "Cameras with constant motion now",
87326
87746
  snapshotSection: null
87327
87747
  },
87748
+ {
87749
+ id: "package-llm-confirm",
87750
+ family: "device-events",
87751
+ label: "Package confirm (not answering / answering again)",
87752
+ bad: ["package-llm-silent"],
87753
+ recovery: ["package-llm-answered"],
87754
+ snapshotLabel: "Cameras whose package confirm is not answering",
87755
+ snapshotSection: null
87756
+ },
87328
87757
  {
87329
87758
  id: "recording-health",
87330
87759
  family: "device-events",