@camstack/addon-provider-onvif 1.2.148 → 1.2.149

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.
package/dist/addon.js CHANGED
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region \0rolldown/runtime.js
3
3
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
4
4
  //#endregion
5
- //#region ../types/dist/event-category-DHXxB8t9.mjs
5
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
6
6
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
7
7
  EventCategory["SystemBoot"] = "system.boot";
8
8
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -328,6 +328,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
328
328
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
329
329
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
330
330
  /**
331
+ * A camera's package LLM confirm has failed closed three times in a row
332
+ * (D931, 2026-10-26). One edge per camera until
333
+ * {@link PipelinePackageLlmAnswered}.
334
+ */
335
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
336
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
337
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
338
+ /**
331
339
  * Per-camera pipeline config was mutated by the orchestrator
332
340
  * (a settings change via `setAgentInferenceDevices` /
333
341
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5437,7 +5445,7 @@ var ZodIssueCode = {
5437
5445
  var ZodFirstPartyTypeKind;
5438
5446
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5439
5447
  //#endregion
5440
- //#region ../types/dist/sleep-BErAxhul.mjs
5448
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5441
5449
  /**
5442
5450
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5443
5451
  * window to float samples (D455).
@@ -7924,6 +7932,99 @@ var RecordKindDeclarationSchema = object({
7924
7932
  }).strict();
7925
7933
  /** The kind names: lower-case, dash separated, short enough for an index name. */
7926
7934
  var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
7935
+ var READING_QUANTITIES = [
7936
+ "temperature",
7937
+ "pressure",
7938
+ "speed",
7939
+ "precipitation",
7940
+ "precipitation-rate",
7941
+ "length",
7942
+ "area",
7943
+ "illuminance",
7944
+ "irradiance",
7945
+ "power",
7946
+ "energy",
7947
+ "voltage",
7948
+ "current",
7949
+ "apparent-power",
7950
+ "humidity",
7951
+ "percentage",
7952
+ "concentration-volume",
7953
+ "concentration-mass",
7954
+ "concentration-ppb",
7955
+ "volume",
7956
+ "duration",
7957
+ "duration-ms",
7958
+ "information",
7959
+ "sound-level",
7960
+ "signal-level",
7961
+ "colour-temperature",
7962
+ "angle",
7963
+ "bitrate",
7964
+ "rate",
7965
+ "mass",
7966
+ "index",
7967
+ "ratio",
7968
+ "count",
7969
+ "none",
7970
+ "device"
7971
+ ];
7972
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
7973
+ new Set(READING_QUANTITIES);
7974
+ /**
7975
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
7976
+ * (or an index / ratio / count). `device` means the cap has no single unit.
7977
+ */
7978
+ var QUANTITY_BASE = {
7979
+ temperature: "°C",
7980
+ pressure: "hPa",
7981
+ speed: "m/s",
7982
+ precipitation: "mm",
7983
+ "precipitation-rate": "mm/h",
7984
+ length: "m",
7985
+ area: "m²",
7986
+ illuminance: "lx",
7987
+ irradiance: "W/m²",
7988
+ power: "W",
7989
+ energy: "kWh",
7990
+ voltage: "V",
7991
+ current: "A",
7992
+ "apparent-power": "VA",
7993
+ humidity: "%",
7994
+ percentage: "%",
7995
+ "concentration-volume": "ppm",
7996
+ "concentration-mass": "µg/m³",
7997
+ "concentration-ppb": "ppb",
7998
+ volume: "L",
7999
+ duration: "s",
8000
+ "duration-ms": "ms",
8001
+ information: "B",
8002
+ "sound-level": "dBFS",
8003
+ "signal-level": "dBm",
8004
+ "colour-temperature": "mired",
8005
+ angle: "°",
8006
+ bitrate: "kbps",
8007
+ rate: "fps",
8008
+ mass: "kg",
8009
+ index: "none",
8010
+ ratio: "none",
8011
+ count: "none",
8012
+ none: "none",
8013
+ device: "device"
8014
+ };
8015
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8016
+ function baseUnitOf(quantity) {
8017
+ const symbol = QUANTITY_BASE[quantity];
8018
+ if (symbol === "none" || symbol === "device") return void 0;
8019
+ return symbol;
8020
+ }
8021
+ function readingMeta(quantity, extras) {
8022
+ return {
8023
+ quantity,
8024
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8025
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8026
+ };
8027
+ }
7927
8028
  /** Shared Zod schemas used across streaming capabilities. */
7928
8029
  var CamProfileSchema = _enum([
7929
8030
  "high",
@@ -7951,8 +8052,8 @@ var CamStreamKindSchema = _enum([
7951
8052
  "derived"
7952
8053
  ]);
7953
8054
  var CamStreamResolutionSchema = object({
7954
- width: number().int().positive(),
7955
- height: number().int().positive()
8055
+ width: number().int().positive().meta(readingMeta("none")),
8056
+ height: number().int().positive().meta(readingMeta("none"))
7956
8057
  });
7957
8058
  var CameraStreamSchema = object({
7958
8059
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8466,6 +8567,8 @@ var CAP_NAMES = Object.freeze([
8466
8567
  "filesystem-browse",
8467
8568
  "flood",
8468
8569
  "gas",
8570
+ "group-commands",
8571
+ "group-status",
8469
8572
  "heating-valve",
8470
8573
  "humidifier",
8471
8574
  "humidity-sensor",
@@ -8595,14 +8698,6 @@ Object.freeze({
8595
8698
  {
8596
8699
  "path": "aqi",
8597
8700
  "valueType": "number"
8598
- },
8599
- {
8600
- "path": "unit",
8601
- "valueType": "string"
8602
- },
8603
- {
8604
- "path": "precision",
8605
- "valueType": "number"
8606
8701
  }
8607
8702
  ],
8608
8703
  "alarm-panel": [
@@ -8638,20 +8733,10 @@ Object.freeze({
8638
8733
  "valueType": "boolean"
8639
8734
  }
8640
8735
  ],
8641
- "ambient-light-sensor": [
8642
- {
8643
- "path": "lux",
8644
- "valueType": "number"
8645
- },
8646
- {
8647
- "path": "unit",
8648
- "valueType": "string"
8649
- },
8650
- {
8651
- "path": "precision",
8652
- "valueType": "number"
8653
- }
8654
- ],
8736
+ "ambient-light-sensor": [{
8737
+ "path": "lux",
8738
+ "valueType": "number"
8739
+ }],
8655
8740
  "audio-metrics": [
8656
8741
  {
8657
8742
  "path": "ts",
@@ -8731,6 +8816,10 @@ Object.freeze({
8731
8816
  "path": "lastUpdated",
8732
8817
  "valueType": "number"
8733
8818
  },
8819
+ {
8820
+ "path": "volts",
8821
+ "valueType": "number"
8822
+ },
8734
8823
  {
8735
8824
  "path": "lastContactAt",
8736
8825
  "valueType": "number"
@@ -8973,10 +9062,6 @@ Object.freeze({
8973
9062
  "datetime"
8974
9063
  ]
8975
9064
  },
8976
- {
8977
- "path": "unit",
8978
- "valueType": "string"
8979
- },
8980
9065
  {
8981
9066
  "path": "min",
8982
9067
  "valueType": "number"
@@ -8988,19 +9073,6 @@ Object.freeze({
8988
9073
  {
8989
9074
  "path": "step",
8990
9075
  "valueType": "number"
8991
- },
8992
- {
8993
- "path": "precision",
8994
- "valueType": "number"
8995
- },
8996
- {
8997
- "path": "format",
8998
- "valueType": "enum",
8999
- "enumValues": [
9000
- "date",
9001
- "time",
9002
- "datetime"
9003
- ]
9004
9076
  }
9005
9077
  ],
9006
9078
  "cover": [
@@ -9241,13 +9313,8 @@ Object.freeze({
9241
9313
  "path": "value",
9242
9314
  "valueType": "string"
9243
9315
  }, {
9244
- "path": "format",
9245
- "valueType": "enum",
9246
- "enumValues": [
9247
- "date",
9248
- "time",
9249
- "datetime"
9250
- ]
9316
+ "path": "timestamp",
9317
+ "valueType": "boolean"
9251
9318
  }],
9252
9319
  "event-emitter": [
9253
9320
  {
@@ -9320,6 +9387,20 @@ Object.freeze({
9320
9387
  "path": "detected",
9321
9388
  "valueType": "boolean"
9322
9389
  }],
9390
+ "group-status": [
9391
+ {
9392
+ "path": "included",
9393
+ "valueType": "number"
9394
+ },
9395
+ {
9396
+ "path": "excluded",
9397
+ "valueType": "number"
9398
+ },
9399
+ {
9400
+ "path": "total",
9401
+ "valueType": "number"
9402
+ }
9403
+ ],
9323
9404
  "heating-valve": [{
9324
9405
  "path": "position",
9325
9406
  "valueType": "number"
@@ -9354,20 +9435,10 @@ Object.freeze({
9354
9435
  "valueType": "number"
9355
9436
  }
9356
9437
  ],
9357
- "humidity-sensor": [
9358
- {
9359
- "path": "percent",
9360
- "valueType": "number"
9361
- },
9362
- {
9363
- "path": "unit",
9364
- "valueType": "string"
9365
- },
9366
- {
9367
- "path": "precision",
9368
- "valueType": "number"
9369
- }
9370
- ],
9438
+ "humidity-sensor": [{
9439
+ "path": "percent",
9440
+ "valueType": "number"
9441
+ }],
9371
9442
  "image": [{
9372
9443
  "path": "url",
9373
9444
  "valueType": "string"
@@ -9706,20 +9777,10 @@ Object.freeze({
9706
9777
  "valueType": "boolean"
9707
9778
  }
9708
9779
  ],
9709
- "numeric-sensor": [
9710
- {
9711
- "path": "value",
9712
- "valueType": "number"
9713
- },
9714
- {
9715
- "path": "unit",
9716
- "valueType": "string"
9717
- },
9718
- {
9719
- "path": "precision",
9720
- "valueType": "number"
9721
- }
9722
- ],
9780
+ "numeric-sensor": [{
9781
+ "path": "value",
9782
+ "valueType": "number"
9783
+ }],
9723
9784
  "pet-feeder": [
9724
9785
  {
9725
9786
  "path": "foodLevel",
@@ -9811,14 +9872,6 @@ Object.freeze({
9811
9872
  {
9812
9873
  "path": "powerFactor",
9813
9874
  "valueType": "number"
9814
- },
9815
- {
9816
- "path": "unit",
9817
- "valueType": "string"
9818
- },
9819
- {
9820
- "path": "precision",
9821
- "valueType": "number"
9822
9875
  }
9823
9876
  ],
9824
9877
  "presence": [
@@ -9847,20 +9900,10 @@ Object.freeze({
9847
9900
  "valueType": "number"
9848
9901
  }
9849
9902
  ],
9850
- "pressure-sensor": [
9851
- {
9852
- "path": "hpa",
9853
- "valueType": "number"
9854
- },
9855
- {
9856
- "path": "unit",
9857
- "valueType": "string"
9858
- },
9859
- {
9860
- "path": "precision",
9861
- "valueType": "number"
9862
- }
9863
- ],
9903
+ "pressure-sensor": [{
9904
+ "path": "hpa",
9905
+ "valueType": "number"
9906
+ }],
9864
9907
  "privacy-mask": [{
9865
9908
  "path": "enabled",
9866
9909
  "valueType": "boolean"
@@ -10171,20 +10214,10 @@ Object.freeze({
10171
10214
  "path": "tampered",
10172
10215
  "valueType": "boolean"
10173
10216
  }],
10174
- "temperature-sensor": [
10175
- {
10176
- "path": "celsius",
10177
- "valueType": "number"
10178
- },
10179
- {
10180
- "path": "unit",
10181
- "valueType": "string"
10182
- },
10183
- {
10184
- "path": "precision",
10185
- "valueType": "number"
10186
- }
10187
- ],
10217
+ "temperature-sensor": [{
10218
+ "path": "celsius",
10219
+ "valueType": "number"
10220
+ }],
10188
10221
  "update": [
10189
10222
  {
10190
10223
  "path": "currentVersion",
@@ -10295,10 +10328,6 @@ Object.freeze({
10295
10328
  "path": "temperature",
10296
10329
  "valueType": "number"
10297
10330
  },
10298
- {
10299
- "path": "temperatureUnit",
10300
- "valueType": "string"
10301
- },
10302
10331
  {
10303
10332
  "path": "humidity",
10304
10333
  "valueType": "number"
@@ -10307,18 +10336,10 @@ Object.freeze({
10307
10336
  "path": "pressure",
10308
10337
  "valueType": "number"
10309
10338
  },
10310
- {
10311
- "path": "pressureUnit",
10312
- "valueType": "string"
10313
- },
10314
10339
  {
10315
10340
  "path": "windSpeed",
10316
10341
  "valueType": "number"
10317
10342
  },
10318
- {
10319
- "path": "windSpeedUnit",
10320
- "valueType": "string"
10321
- },
10322
10343
  {
10323
10344
  "path": "windBearing",
10324
10345
  "valueType": "number"
@@ -10721,7 +10742,7 @@ function errMsg(err) {
10721
10742
  return String(err);
10722
10743
  }
10723
10744
  //#endregion
10724
- //#region ../types/dist/composition-kEl9mevu.mjs
10745
+ //#region ../types/dist/composition-OhpuespA.mjs
10725
10746
  /**
10726
10747
  * The ffmpeg build every CamStack node downloads, per platform and arch.
10727
10748
  *
@@ -11476,15 +11497,74 @@ function validateExpressionSource(src) {
11476
11497
  }
11477
11498
  return null;
11478
11499
  }
11479
- /** A composed device's stableId is this prefix plus the block id. */
11480
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11481
- /** Longest snippet a `code` source may carry (slice 4). */
11482
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11500
+ /**
11501
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11502
+ * id. Split out so helper schemas can name it without a cycle through
11503
+ * `composition.ts` (D659).
11504
+ */
11483
11505
  var CompositionSourceRefSchema = object({
11484
11506
  /** The owning addon; `stableId` is unique only within it. */
11485
11507
  addonId: string().min(1),
11486
11508
  stableId: string().min(1)
11487
11509
  });
11510
+ var GROUP_TEMPLATE_IDS = [
11511
+ "temperature",
11512
+ "humidity",
11513
+ "pressure",
11514
+ "illuminance",
11515
+ "power",
11516
+ "energy",
11517
+ "battery",
11518
+ "numeric",
11519
+ "motion",
11520
+ "contact",
11521
+ "leak",
11522
+ "smoke",
11523
+ "occupancy",
11524
+ "binary",
11525
+ "switch",
11526
+ "light"
11527
+ ];
11528
+ var NUMERIC_AGGREGATES = [
11529
+ "mean",
11530
+ "median",
11531
+ "min",
11532
+ "max",
11533
+ "sum"
11534
+ ];
11535
+ var BINARY_AGGREGATES = ["any", "all"];
11536
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11537
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11538
+ var AGGREGATE_REDUCERS = [
11539
+ "mean",
11540
+ "median",
11541
+ "min",
11542
+ "max",
11543
+ "sum",
11544
+ "any",
11545
+ "all"
11546
+ ];
11547
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11548
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11549
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11550
+ /** One member. The field is stored so occupancy can name motion or binary. */
11551
+ var HelperMemberSchema = object({
11552
+ source: CompositionSourceRefSchema,
11553
+ cap: string().min(1),
11554
+ fieldPath: string().min(1)
11555
+ });
11556
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
11557
+ kind: literal("group"),
11558
+ templateId: GroupTemplateIdSchema,
11559
+ aggregate: AggregateReducerSchema,
11560
+ members: array(HelperMemberSchema).min(1).max(64),
11561
+ /** Actuator groups only. Readings leave this unset. */
11562
+ active: ActiveCriterionSchema.optional()
11563
+ })]);
11564
+ /** A composed device's stableId is this prefix plus the block id. */
11565
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11566
+ /** Longest snippet a `code` source may carry (slice 4). */
11567
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11488
11568
  /** One field of one capability of one source device. */
11489
11569
  var CompositionFieldReadSchema = object({
11490
11570
  source: CompositionSourceRefSchema,
@@ -11528,7 +11608,33 @@ var CompositionCodeSourceSchema = object({
11528
11608
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11529
11609
  CompositionFromSourceSchema,
11530
11610
  CompositionExpressionSourceSchema,
11531
- CompositionCodeSourceSchema
11611
+ CompositionCodeSourceSchema,
11612
+ object({
11613
+ kind: literal("aggregate"),
11614
+ reducer: _enum([
11615
+ "mean",
11616
+ "median",
11617
+ "min",
11618
+ "max",
11619
+ "sum",
11620
+ "any",
11621
+ "all"
11622
+ ]),
11623
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
11624
+ /** `value` is the reading; the others are the member counts. */
11625
+ publish: _enum([
11626
+ "value",
11627
+ "included",
11628
+ "excluded",
11629
+ "total"
11630
+ ]),
11631
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
11632
+ empty: _enum([
11633
+ "null",
11634
+ "omit",
11635
+ "offline"
11636
+ ])
11637
+ })
11532
11638
  ]);
11533
11639
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11534
11640
  kind: literal("forward"),
@@ -11574,7 +11680,13 @@ var CompositionSchema = object({
11574
11680
  kind: literal("existing"),
11575
11681
  device: CompositionSourceRefSchema
11576
11682
  })]),
11577
- features: array(CompositionFeatureSchema).min(1).max(16)
11683
+ features: array(CompositionFeatureSchema).min(1).max(16),
11684
+ /**
11685
+ * Present when the block is a helper template. Absent on a free
11686
+ * composition (the room example, device 4603). The editor reads this,
11687
+ * never the compiled features.
11688
+ */
11689
+ helper: HelperSpecSchema.optional()
11578
11690
  }).superRefine((composition, ctx) => {
11579
11691
  const seen = /* @__PURE__ */ new Set();
11580
11692
  composition.features.forEach((feature, index) => {
@@ -17676,7 +17788,11 @@ var CompositionProblemSchema = object({
17676
17788
  "item-field-reserved",
17677
17789
  "target-missing",
17678
17790
  "target-unreadable",
17679
- "not-replaceable"
17791
+ "not-replaceable",
17792
+ "native-capability-replace",
17793
+ "mixed-quantity",
17794
+ "helper-mismatch",
17795
+ "member-constraint"
17680
17796
  ]),
17681
17797
  path: string(),
17682
17798
  message: string()
@@ -17697,7 +17813,8 @@ var CompositionUnavailableModeSchema = _enum([
17697
17813
  "null",
17698
17814
  "offline",
17699
17815
  "unknown",
17700
- "release"
17816
+ "release",
17817
+ "omit"
17701
17818
  ]);
17702
17819
  /**
17703
17820
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -17734,9 +17851,11 @@ var CompositionFieldPlanSchema = object({
17734
17851
  reads: array(CompositionFieldReadSchema)
17735
17852
  });
17736
17853
  /**
17737
- * What a feature does to its target (D663): `new` builds a capability on a new
17738
- * device; on an existing device it `add`s a capability the device lacks, or
17739
- * `replace`s named fields of one it has natively.
17854
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
17855
+ * new device; on an existing device it `add`s a capability the device lacks.
17856
+ * Replacing a capability the device already serves is refused
17857
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
17858
+ * wire for a claim the hub already holds.
17740
17859
  */
17741
17860
  var CompositionFeatureModeSchema = _enum([
17742
17861
  "new",
@@ -17909,6 +18028,275 @@ Object.freeze({
17909
18028
  result: ["number", "null"]
17910
18029
  }
17911
18030
  });
18031
+ /**
18032
+ * One catalog row per base reading a group helper can build (D944 §3).
18033
+ * Data, not a form default written into the block.
18034
+ */
18035
+ var NOW = {
18036
+ fieldPath: "lastFetchedAt",
18037
+ expr: "now"
18038
+ };
18039
+ var CHANGED = {
18040
+ fieldPath: "lastChangedAt",
18041
+ expr: "now"
18042
+ };
18043
+ function numeric(input) {
18044
+ return {
18045
+ ...input,
18046
+ family: "numeric",
18047
+ deviceType: DeviceType.Sensor,
18048
+ memberDeviceType: DeviceType.Sensor,
18049
+ commandCaps: [],
18050
+ baseUnit: baseUnitOf(input.quantity),
18051
+ aggregates: NUMERIC_AGGREGATES,
18052
+ accept: [{
18053
+ cap: input.outputCap,
18054
+ fieldPath: input.outputField
18055
+ }]
18056
+ };
18057
+ }
18058
+ function binary(input) {
18059
+ return {
18060
+ ...input,
18061
+ family: "binary",
18062
+ deviceType: DeviceType.Sensor,
18063
+ memberDeviceType: DeviceType.Sensor,
18064
+ commandCaps: [],
18065
+ quantity: "none",
18066
+ baseUnit: void 0,
18067
+ aggregates: BINARY_AGGREGATES,
18068
+ empty: "offline"
18069
+ };
18070
+ }
18071
+ var GROUP_TEMPLATES = [
18072
+ numeric({
18073
+ id: "temperature",
18074
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18075
+ role: DeviceRole.TemperatureSensor,
18076
+ outputCap: "temperature-sensor",
18077
+ outputField: "celsius",
18078
+ quantity: "temperature",
18079
+ defaultAggregate: "mean",
18080
+ empty: "null",
18081
+ companions: [NOW]
18082
+ }),
18083
+ numeric({
18084
+ id: "humidity",
18085
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18086
+ role: DeviceRole.HumiditySensor,
18087
+ outputCap: "humidity-sensor",
18088
+ outputField: "percent",
18089
+ quantity: "humidity",
18090
+ defaultAggregate: "mean",
18091
+ empty: "null",
18092
+ companions: [NOW]
18093
+ }),
18094
+ numeric({
18095
+ id: "pressure",
18096
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18097
+ role: DeviceRole.PressureSensor,
18098
+ outputCap: "pressure-sensor",
18099
+ outputField: "hpa",
18100
+ quantity: "pressure",
18101
+ defaultAggregate: "mean",
18102
+ empty: "null",
18103
+ companions: [NOW]
18104
+ }),
18105
+ numeric({
18106
+ id: "illuminance",
18107
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18108
+ role: DeviceRole.AmbientLightSensor,
18109
+ outputCap: "ambient-light-sensor",
18110
+ outputField: "lux",
18111
+ quantity: "illuminance",
18112
+ defaultAggregate: "mean",
18113
+ empty: "null",
18114
+ companions: [NOW]
18115
+ }),
18116
+ numeric({
18117
+ id: "power",
18118
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18119
+ role: DeviceRole.PowerSensor,
18120
+ outputCap: "power-meter",
18121
+ outputField: "watts",
18122
+ quantity: "power",
18123
+ defaultAggregate: "sum",
18124
+ empty: "omit",
18125
+ companions: [NOW]
18126
+ }),
18127
+ numeric({
18128
+ id: "energy",
18129
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18130
+ role: DeviceRole.EnergySensor,
18131
+ outputCap: "power-meter",
18132
+ outputField: "kwhTotal",
18133
+ quantity: "energy",
18134
+ defaultAggregate: "sum",
18135
+ empty: "omit",
18136
+ companions: [NOW]
18137
+ }),
18138
+ numeric({
18139
+ id: "battery",
18140
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18141
+ role: DeviceRole.BatterySensor,
18142
+ outputCap: "battery",
18143
+ outputField: "percentage",
18144
+ quantity: "percentage",
18145
+ defaultAggregate: "mean",
18146
+ empty: "null",
18147
+ companions: [
18148
+ {
18149
+ fieldPath: "charging",
18150
+ expr: "\"unknown\""
18151
+ },
18152
+ {
18153
+ fieldPath: "sleeping",
18154
+ expr: "false"
18155
+ },
18156
+ {
18157
+ fieldPath: "lastUpdated",
18158
+ expr: "now"
18159
+ }
18160
+ ]
18161
+ }),
18162
+ numeric({
18163
+ id: "numeric",
18164
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18165
+ role: DeviceRole.NumericSensor,
18166
+ outputCap: "numeric-sensor",
18167
+ outputField: "value",
18168
+ quantity: "device",
18169
+ defaultAggregate: "mean",
18170
+ empty: "null",
18171
+ companions: [NOW]
18172
+ }),
18173
+ binary({
18174
+ id: "motion",
18175
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18176
+ role: DeviceRole.MotionSensor,
18177
+ outputCap: "motion",
18178
+ outputField: "detected",
18179
+ defaultAggregate: "any",
18180
+ accept: [{
18181
+ cap: "motion",
18182
+ fieldPath: "detected"
18183
+ }],
18184
+ companions: [{
18185
+ fieldPath: "lastDetectedAt",
18186
+ expr: "null"
18187
+ }, {
18188
+ fieldPath: "autoClearAfterMs",
18189
+ expr: "null"
18190
+ }]
18191
+ }),
18192
+ binary({
18193
+ id: "contact",
18194
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18195
+ role: DeviceRole.ContactSensor,
18196
+ outputCap: "contact",
18197
+ outputField: "entryOpen",
18198
+ defaultAggregate: "any",
18199
+ accept: [{
18200
+ cap: "contact",
18201
+ fieldPath: "entryOpen"
18202
+ }],
18203
+ companions: [CHANGED]
18204
+ }),
18205
+ binary({
18206
+ id: "leak",
18207
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18208
+ role: DeviceRole.LeakSensor,
18209
+ outputCap: "flood",
18210
+ outputField: "flooded",
18211
+ defaultAggregate: "any",
18212
+ accept: [{
18213
+ cap: "flood",
18214
+ fieldPath: "flooded"
18215
+ }],
18216
+ companions: [CHANGED]
18217
+ }),
18218
+ binary({
18219
+ id: "smoke",
18220
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18221
+ role: DeviceRole.SmokeSensor,
18222
+ outputCap: "smoke",
18223
+ outputField: "detected",
18224
+ defaultAggregate: "any",
18225
+ accept: [{
18226
+ cap: "smoke",
18227
+ fieldPath: "detected"
18228
+ }],
18229
+ companions: [CHANGED]
18230
+ }),
18231
+ binary({
18232
+ id: "occupancy",
18233
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18234
+ role: DeviceRole.BinarySensor,
18235
+ outputCap: "binary",
18236
+ outputField: "on",
18237
+ defaultAggregate: "any",
18238
+ accept: [{
18239
+ cap: "motion",
18240
+ fieldPath: "detected"
18241
+ }, {
18242
+ cap: "binary",
18243
+ fieldPath: "on"
18244
+ }],
18245
+ companions: [CHANGED]
18246
+ }),
18247
+ binary({
18248
+ id: "binary",
18249
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18250
+ role: DeviceRole.BinarySensor,
18251
+ outputCap: "binary",
18252
+ outputField: "on",
18253
+ defaultAggregate: "any",
18254
+ accept: [{
18255
+ cap: "binary",
18256
+ fieldPath: "on"
18257
+ }],
18258
+ companions: [CHANGED]
18259
+ }),
18260
+ actuator({
18261
+ id: "switch",
18262
+ 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.",
18263
+ deviceType: DeviceType.Switch,
18264
+ outputCap: "switch",
18265
+ commandCaps: ["switch"]
18266
+ }),
18267
+ actuator({
18268
+ id: "light",
18269
+ 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.",
18270
+ deviceType: DeviceType.Light,
18271
+ outputCap: "switch",
18272
+ commandCaps: [
18273
+ "switch",
18274
+ "brightness",
18275
+ "color"
18276
+ ]
18277
+ })
18278
+ ];
18279
+ function actuator(input) {
18280
+ return {
18281
+ ...input,
18282
+ family: "actuator",
18283
+ role: void 0,
18284
+ memberDeviceType: input.deviceType,
18285
+ outputField: "on",
18286
+ quantity: "none",
18287
+ baseUnit: void 0,
18288
+ aggregates: BINARY_AGGREGATES,
18289
+ defaultAggregate: "any",
18290
+ empty: "offline",
18291
+ accept: [{
18292
+ cap: "switch",
18293
+ fieldPath: "on"
18294
+ }],
18295
+ companions: [CHANGED]
18296
+ };
18297
+ }
18298
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18299
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
17912
18300
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
17913
18301
  /**
17914
18302
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -19735,12 +20123,12 @@ function buildDeviceAdminUrl(parts) {
19735
20123
  * providers detect renames by matching `uniqueId` and patch `id`
19736
20124
  * in-place so dispatch keeps working.
19737
20125
  *
19738
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
19739
- * `precision`) live in the cap STATUS SLICE so they flow live with every
19740
- * state push and are the single source of truth. The `device_class` tag is
19741
- * an HA-specific input that is consumed at the discovery boundary
19742
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
19743
- * stored after that point.
20126
+ * **Display unit and precision are NOT stored here.** They are a read-time
20127
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20128
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20129
+ * neither is repeated on each sample. The `device_class` tag is an
20130
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20131
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
19744
20132
  *
19745
20133
  * The `raw` escape hatch carries provider-specific attributes that don't
19746
20134
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -19754,6 +20142,14 @@ var SourceInfoSchema = object({
19754
20142
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
19755
20143
  * Used to detect rename when `id` changes. */
19756
20144
  uniqueId: string().optional(),
20145
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20146
+ quantity: ReadingQuantitySchema.optional(),
20147
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20148
+ dateTimeShape: _enum([
20149
+ "date",
20150
+ "time",
20151
+ "datetime"
20152
+ ]).optional(),
19757
20153
  /** Provider-specific extras that don't fit the structured slots.
19758
20154
  * Whitelist what goes here per provider — do NOT dump entire HA
19759
20155
  * attribute blobs (would explode the meta JSON column). */
@@ -40747,7 +41143,8 @@ method(object({
40747
41143
  "enum"
40748
41144
  ]),
40749
41145
  enumValues: array(string()).optional(),
40750
- item: boolean().optional()
41146
+ item: boolean().optional(),
41147
+ quantity: ReadingQuantitySchema.optional()
40751
41148
  })).readonly(),
40752
41149
  itemArray: object({
40753
41150
  path: string(),
@@ -44547,8 +44944,8 @@ var MaskPolygonShapeSchema = object({
44547
44944
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44548
44945
  var MaskGridShapeSchema = object({
44549
44946
  kind: literal("grid"),
44550
- gridWidth: number(),
44551
- gridHeight: number(),
44947
+ gridWidth: number().meta(readingMeta("none")),
44948
+ gridHeight: number().meta(readingMeta("none")),
44552
44949
  cells: array(boolean())
44553
44950
  });
44554
44951
  discriminatedUnion("kind", [
@@ -44660,6 +45057,8 @@ var NcSystemEventKindSchema = _enum([
44660
45057
  "detection-blind",
44661
45058
  "motion-noisy",
44662
45059
  "motion-calm",
45060
+ "package-llm-silent",
45061
+ "package-llm-answered",
44663
45062
  "recording-stalled",
44664
45063
  "recording-healthy",
44665
45064
  "recording-degraded",
@@ -55778,6 +56177,28 @@ var PackageVersionInfoSchema = object({
55778
56177
  deprecated: string().optional(),
55779
56178
  distTags: array(string()).readonly()
55780
56179
  });
56180
+ /**
56181
+ * One published document. `ok` is markdown from the registry. `missing` means
56182
+ * the registry answered and the file was not there. `error` is a registry or
56183
+ * tarball failure — never an empty string standing in for either.
56184
+ */
56185
+ var PackageDocBodySchema = discriminatedUnion("status", [
56186
+ object({
56187
+ status: literal("ok"),
56188
+ markdown: string()
56189
+ }),
56190
+ object({ status: literal("missing") }),
56191
+ object({
56192
+ status: literal("error"),
56193
+ message: string()
56194
+ })
56195
+ ]);
56196
+ var PackageDocsSchema = object({
56197
+ name: string(),
56198
+ version: string(),
56199
+ readme: PackageDocBodySchema,
56200
+ changelog: PackageDocBodySchema
56201
+ });
55781
56202
  var SearchResultSchema = object({
55782
56203
  name: string(),
55783
56204
  version: string(),
@@ -55996,7 +56417,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
55996
56417
  }), object({ success: literal(true) }), {
55997
56418
  kind: "mutation",
55998
56419
  auth: "admin"
55999
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
56420
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
56421
+ name: string().min(1),
56422
+ version: string().min(1).optional()
56423
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
56000
56424
  kind: "mutation",
56001
56425
  auth: "admin"
56002
56426
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -56046,46 +56470,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56046
56470
  }), LogStreamEntrySchema, { kind: "subscription" });
56047
56471
  object({
56048
56472
  /** Carbon dioxide concentration in ppm. */
56049
- co2Ppm: number().min(0).optional(),
56473
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
56050
56474
  /** Total volatile organic compounds in ppb. */
56051
- vocPpb: number().min(0).optional(),
56475
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56052
56476
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56053
- pm25: number().min(0).optional(),
56477
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56054
56478
  /** Particulate matter ≤ 10 μm in µg/m³. */
56055
- pm10: number().min(0).optional(),
56479
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
56056
56480
  /** Composite AQI value (typically 0..500). */
56057
- aqi: number().optional(),
56481
+ aqi: number().optional().meta(readingMeta("index")),
56058
56482
  /** Ms epoch when the slice was last updated. */
56059
- lastFetchedAt: number(),
56060
- /** Live display unit of the single metric this slice carries (e.g. HA
56061
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
56062
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
56063
- * per slice is unambiguous. */
56064
- unit: string().optional(),
56065
- /** Suggested decimal places for numeric display.
56066
- * Populated live from the upstream source when provided (e.g. HA
56067
- * `attributes.suggested_display_precision`). Falls back to
56068
- * auto-formatting when absent. */
56069
- precision: number().int().min(0).max(10).optional()
56483
+ lastFetchedAt: number()
56070
56484
  });
56071
56485
  DeviceType.Sensor;
56072
56486
  object({
56073
- /** Current illuminance in lux (lx). */
56074
- lux: number().min(0).meta({
56075
- unit: "lx",
56487
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56488
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
56489
+ precision: 0,
56076
56490
  label: "Illuminance"
56077
- }),
56491
+ })),
56078
56492
  /** Ms epoch when the slice was last updated. */
56079
- lastFetchedAt: number(),
56080
- /** Live display unit from the upstream source (e.g. HA
56081
- * `attributes.unit_of_measurement`). The UI prefers this over the
56082
- * role's canonical unit. Absent → fall back to the canonical unit. */
56083
- unit: string().optional(),
56084
- /** Suggested decimal places for numeric display.
56085
- * Populated live from the upstream source when provided (e.g. HA
56086
- * `attributes.suggested_display_precision`). Falls back to
56087
- * auto-formatting when absent. */
56088
- precision: number().int().min(0).max(10).optional()
56493
+ lastFetchedAt: number()
56089
56494
  });
56090
56495
  DeviceType.Sensor;
56091
56496
  /**
@@ -56230,10 +56635,7 @@ var BatteryStatusSchema = object({
56230
56635
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
56231
56636
  * declines to band a non-finite reading, which is the correct shape.
56232
56637
  */
56233
- percentage: number().min(0).max(100).nullable().meta({
56234
- unit: "%",
56235
- label: "Charge"
56236
- }),
56638
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
56237
56639
  /**
56238
56640
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
56239
56641
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -56260,7 +56662,14 @@ var BatteryStatusSchema = object({
56260
56662
  */
56261
56663
  sleeping: boolean().meta({ label: "Sleeping" }),
56262
56664
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
56263
- lastUpdated: number(),
56665
+ lastUpdated: number().meta(readingMeta("none")),
56666
+ /**
56667
+ * Battery voltage in volts, for a cell the firmware reports as voltage
56668
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
56669
+ * that only report charge. A second reading on this cap, not a second
56670
+ * `numeric-sensor` on the same device.
56671
+ */
56672
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
56264
56673
  /**
56265
56674
  * Ms epoch of the last time the device PROVED it was reachable — a
56266
56675
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -56320,7 +56729,7 @@ object({
56320
56729
  DeviceType.Sensor;
56321
56730
  object({
56322
56731
  /** Current level as 0..100 inclusive. Firmware-reported. */
56323
- percentage: number().min(0).max(100),
56732
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
56324
56733
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
56325
56734
  lastChangedAt: number()
56326
56735
  });
@@ -56705,21 +57114,21 @@ object({
56705
57114
  /** Single setpoint in Celsius. Used by single-target modes
56706
57115
  * (heat / cool / dry). Null when the device is in a range mode
56707
57116
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
56708
- target: number().nullable(),
57117
+ target: number().nullable().meta(readingMeta("temperature")),
56709
57118
  /** Upper bound of the dual setpoint in Celsius. Populated only
56710
57119
  * in `heat_cool` mode. */
56711
- targetHigh: number().nullable(),
57120
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
56712
57121
  /** Lower bound of the dual setpoint in Celsius. Populated only
56713
57122
  * in `heat_cool` mode. */
56714
- targetLow: number().nullable(),
57123
+ targetLow: number().nullable().meta(readingMeta("temperature")),
56715
57124
  /** Target relative humidity (0..100). Null when the device has no
56716
57125
  * humidity control. */
56717
- targetHumidity: number().min(0).max(100).nullable(),
57126
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56718
57127
  /** Current measured temperature in Celsius. Read-only — pushed by
56719
57128
  * the device's internal sensor. */
56720
- currentTemp: number().nullable(),
57129
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
56721
57130
  /** Current measured relative humidity (0..100). Read-only. */
56722
- currentHumidity: number().min(0).max(100).nullable(),
57131
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56723
57132
  /** Vertical louver swing on/off. Null when the device has no vertical
56724
57133
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
56725
57134
  swingVertical: boolean().nullable(),
@@ -56727,11 +57136,11 @@ object({
56727
57136
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
56728
57137
  swingHorizontal: boolean().nullable(),
56729
57138
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
56730
- minTemp: number().nullable().optional(),
57139
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
56731
57140
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
56732
- maxTemp: number().nullable().optional(),
57141
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
56733
57142
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
56734
- tempStep: number().positive().nullable().optional(),
57143
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
56735
57144
  /** Ms epoch when the slice was last updated (push or command). */
56736
57145
  lastFetchedAt: number()
56737
57146
  });
@@ -56810,17 +57219,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
56810
57219
  * modes to expose in the picker without a slice fetch.
56811
57220
  */
56812
57221
  var RgbTripletSchema = object({
56813
- r: number().int().min(0).max(255),
56814
- g: number().int().min(0).max(255),
56815
- b: number().int().min(0).max(255)
57222
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
57223
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
57224
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
56816
57225
  });
56817
57226
  var HsvTripletSchema = object({
56818
57227
  /** Hue in degrees, 0..360. */
56819
- h: number().min(0).max(360),
57228
+ h: number().min(0).max(360).meta(readingMeta("none")),
56820
57229
  /** Saturation as 0..100 inclusive. */
56821
- s: number().min(0).max(100),
57230
+ s: number().min(0).max(100).meta(readingMeta("none")),
56822
57231
  /** Value/brightness as 0..100 inclusive. */
56823
- v: number().min(0).max(100)
57232
+ v: number().min(0).max(100).meta(readingMeta("none"))
56824
57233
  });
56825
57234
  var ColorInputSchema = discriminatedUnion("mode", [
56826
57235
  object({
@@ -56849,7 +57258,7 @@ object({
56849
57258
  /** Populated when `mode === 'hsv'`. */
56850
57259
  hsv: HsvTripletSchema.optional(),
56851
57260
  /** Populated when `mode === 'mired'`. */
56852
- mireds: number().int().optional(),
57261
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
56853
57262
  /** Ms epoch of the last operator-driven change. */
56854
57263
  lastChangedAt: number()
56855
57264
  });
@@ -56973,18 +57382,12 @@ _enum([
56973
57382
  "empty",
56974
57383
  "full"
56975
57384
  ]);
56976
- /** Units a remaining-life counter is reported in. Closed: the panel renders
56977
- * each. */
56978
- var ConsumableRemainingUnitSchema = _enum([
56979
- "days",
56980
- "hours",
56981
- "cycles"
56982
- ]);
56983
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
56984
- * days). `value` null = unknown (D393). */
57385
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
57386
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
57387
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
56985
57388
  var ConsumableRemainingSchema = object({
56986
- value: number().min(0).nullable(),
56987
- unit: ConsumableRemainingUnitSchema
57389
+ value: number().min(0).nullable().meta(readingMeta("device")),
57390
+ kind: ConsumableRemainingKindSchema
56988
57391
  });
56989
57392
  var ConsumablesStatusSchema = object({
56990
57393
  items: array(object({
@@ -56993,7 +57396,7 @@ var ConsumablesStatusSchema = object({
56993
57396
  /** Display name. */
56994
57397
  label: string().min(1),
56995
57398
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
56996
- level: number().min(0).max(100).nullable(),
57399
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
56997
57400
  /** The provider's discrete verdict when known (binary mode). */
56998
57401
  status: ConsumableStatusSchema.nullable(),
56999
57402
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -57010,7 +57413,7 @@ var ConsumablesStatusSchema = object({
57010
57413
  })),
57011
57414
  lastChangedAt: number()
57012
57415
  });
57013
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57416
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57014
57417
  Object.values(DeviceType), method(object({
57015
57418
  deviceId: number().int().nonnegative(),
57016
57419
  key: string().min(1)
@@ -57035,39 +57438,17 @@ object({
57035
57438
  /** Current value. Numeric kind → number; select/text/datetime → string.
57036
57439
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
57037
57440
  * or time-only — HA accepts all three forms). */
57038
- value: union([number(), string()]),
57441
+ value: union([number().meta(readingMeta("device")), string()]),
57039
57442
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
57040
57443
  options: array(string()),
57041
57444
  /** Ms epoch when the slice was last updated. */
57042
57445
  lastChangedAt: number(),
57043
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
57044
- * Populated live from HA `attributes.unit_of_measurement` on each push.
57045
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57046
- unit: string().optional(),
57047
- /** Minimum allowed value. Populated live from HA `attributes.min`.
57048
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57049
- min: number().optional(),
57050
- /** Maximum allowed value. Populated live from HA `attributes.max`.
57051
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57052
- max: number().optional(),
57053
- /** Step increment. Populated live from HA `attributes.step`.
57054
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57055
- step: number().optional(),
57056
- /** Suggested decimal places for numeric display. Populated live from HA
57057
- * `attributes.suggested_display_precision`, or derived from `step` when the
57058
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
57059
- * other kinds. Falls back to auto-formatting when absent. */
57060
- precision: number().int().min(0).max(10).optional(),
57061
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
57062
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
57063
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
57064
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
57065
- * `has_date`/`has_time` attributes on each push. */
57066
- format: _enum([
57067
- "date",
57068
- "time",
57069
- "datetime"
57070
- ]).optional()
57446
+ /** Minimum allowed value, in the device quantity's base unit. */
57447
+ min: number().optional().meta(readingMeta("device")),
57448
+ /** Maximum allowed value, in the device quantity's base unit. */
57449
+ max: number().optional().meta(readingMeta("device")),
57450
+ /** Step increment, in the device quantity's base unit. */
57451
+ step: number().optional().meta(readingMeta("device"))
57071
57452
  });
57072
57453
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
57073
57454
  object({
@@ -57105,9 +57486,9 @@ object({
57105
57486
  ]),
57106
57487
  /** 0 = fully closed, 100 = fully open. Null when the device has
57107
57488
  * no intermediate position surface. */
57108
- position: number().min(0).max(100).nullable(),
57489
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57109
57490
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
57110
- tiltPosition: number().min(0).max(100).nullable(),
57491
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57111
57492
  /** Ms epoch when the slice was last updated. */
57112
57493
  lastChangedAt: number()
57113
57494
  });
@@ -57165,9 +57546,9 @@ var NormalizedRangeSchema$1 = object({
57165
57546
  object({
57166
57547
  mode: DayNightModeSchema,
57167
57548
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
57168
- sensitivity: number().optional(),
57549
+ sensitivity: number().optional().meta(readingMeta("none")),
57169
57550
  /** Delay before the IR-cut filter flips, in seconds. */
57170
- switchDelaySec: number().optional(),
57551
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
57171
57552
  lastFetchedAt: number()
57172
57553
  });
57173
57554
  /**
@@ -57322,11 +57703,11 @@ object({
57322
57703
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
57323
57704
  availablePrograms: array(string()),
57324
57705
  /** Seconds remaining on the active cycle, or null when not running / not known. */
57325
- remainingSeconds: number().int().nonnegative().nullable(),
57706
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57326
57707
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
57327
- delayedStartSeconds: number().int().nonnegative().nullable(),
57708
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57328
57709
  /** Cycle completion 0..100, or null when not applicable. */
57329
- progressPercent: number().min(0).max(100).nullable(),
57710
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57330
57711
  /** True when the door is reported open; null when the door state is not known. */
57331
57712
  doorOpen: boolean().nullable(),
57332
57713
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -57350,7 +57731,7 @@ object({
57350
57731
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
57351
57732
  machineCareDue: boolean().nullable(),
57352
57733
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
57353
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
57734
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
57354
57735
  /** Option states: null when the appliance does not expose the option or has not said. */
57355
57736
  halfLoad: boolean().nullable(),
57356
57737
  hygienePlus: boolean().nullable(),
@@ -57365,7 +57746,7 @@ object({
57365
57746
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
57366
57747
  activeEvents: array(string()),
57367
57748
  /** Numeric error code when the appliance reports one; null otherwise. */
57368
- errorCode: number().nullable(),
57749
+ errorCode: number().nullable().meta(readingMeta("none")),
57369
57750
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
57370
57751
  errorLabel: string().nullable(),
57371
57752
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -57511,24 +57892,14 @@ object({
57511
57892
  /** Ms epoch of the last press. null = never observed since this provider started. */
57512
57893
  lastPressedAt: number().nullable(),
57513
57894
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
57514
- pressCountSinceStart: number()
57895
+ pressCountSinceStart: number().meta(readingMeta("count"))
57515
57896
  });
57516
57897
  object({
57517
57898
  deviceId: number(),
57518
57899
  timestamp: number()
57519
57900
  });
57520
57901
  DeviceType.Button, DeviceType.Camera;
57521
- /**
57522
- * Enum-state sensor — a string value picked from a finite option set.
57523
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
57524
- * states, weather conditions, contact-source channels, mode strings).
57525
- * The option set is stable across observations; it is persisted in the
57526
- * device config blob (via the control slice at adoption) and is NOT
57527
- * stored in `sourceInfo`.
57528
- */
57529
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
57530
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
57531
- var EnumSensorDateTimeFormatSchema = _enum([
57902
+ _enum([
57532
57903
  "date",
57533
57904
  "time",
57534
57905
  "datetime"
@@ -57536,12 +57907,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
57536
57907
  object({
57537
57908
  value: string(),
57538
57909
  /**
57539
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
57540
- * as a locale date/time/datetime; absent for plain enum sensors. HA
57541
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
57542
- * a time-only sensor → `'time'`.
57910
+ * Set when `value` is a timestamp rather than an enum state, so history
57911
+ * does not learn ISO strings as states. The display shape (date / time /
57912
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
57543
57913
  */
57544
- format: EnumSensorDateTimeFormatSchema.optional(),
57914
+ timestamp: boolean().optional(),
57545
57915
  /** Ms epoch when the slice was last updated. */
57546
57916
  lastFetchedAt: number()
57547
57917
  });
@@ -57560,13 +57930,13 @@ var EventFireSchema = object({
57560
57930
  deviceId: number(),
57561
57931
  eventType: string(),
57562
57932
  data: record(string(), unknown()).nullable(),
57563
- timestamp: number(),
57564
- seq: number()
57933
+ timestamp: number().meta(readingMeta("none")),
57934
+ seq: number().meta(readingMeta("count"))
57565
57935
  });
57566
57936
  object({
57567
57937
  eventTypes: array(string()),
57568
57938
  lastEvent: EventFireSchema.nullable(),
57569
- eventCountSinceStart: number()
57939
+ eventCountSinceStart: number().meta(readingMeta("count"))
57570
57940
  });
57571
57941
  DeviceType.EventEmitter;
57572
57942
  var IdentitySchema = object({
@@ -57955,10 +58325,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
57955
58325
  object({
57956
58326
  /** Active speed as 0..100 inclusive. Null when the device has no
57957
58327
  * speed surface (single-speed fan). */
57958
- percentage: number().min(0).max(100).nullable(),
58328
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57959
58329
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
57960
58330
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
57961
- percentageStep: number().positive().optional(),
58331
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
57962
58332
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
57963
58333
  * — empty string when no preset is active or supported. */
57964
58334
  preset: string(),
@@ -58040,7 +58410,7 @@ var FeatureProbeStatusSchema = object({
58040
58410
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
58041
58411
  model: string().nullable(),
58042
58412
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
58043
- channelCount: number().nullable(),
58413
+ channelCount: number().nullable().meta(readingMeta("count")),
58044
58414
  /**
58045
58415
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
58046
58416
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -58072,9 +58442,39 @@ object({
58072
58442
  lastChangedAt: number()
58073
58443
  });
58074
58444
  DeviceType.Sensor;
58445
+ object({
58446
+ /** One row per member that has been commanded. A later command replaces that member's row. */
58447
+ results: array(object({
58448
+ deviceId: number().int().nonnegative(),
58449
+ cap: string().min(1),
58450
+ method: string().min(1),
58451
+ ok: boolean(),
58452
+ error: string().optional(),
58453
+ /** Ms epoch of the attempt. */
58454
+ at: number()
58455
+ })) });
58456
+ DeviceType.Switch, DeviceType.Light;
58457
+ object({
58458
+ /** Members whose current value entered the aggregate. */
58459
+ included: number().int().nonnegative().meta(readingMeta("count", {
58460
+ precision: 0,
58461
+ label: "Reporting"
58462
+ })),
58463
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
58464
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
58465
+ precision: 0,
58466
+ label: "Excluded"
58467
+ })),
58468
+ /** `included` + `excluded`. */
58469
+ total: number().int().nonnegative().meta(readingMeta("count", {
58470
+ precision: 0,
58471
+ label: "Members"
58472
+ }))
58473
+ });
58474
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
58075
58475
  object({
58076
58476
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
58077
- position: number().min(0).max(100).nullable(),
58477
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58078
58478
  /** Ms epoch when the slice was last updated (push or poll). */
58079
58479
  lastFetchedAt: number()
58080
58480
  });
@@ -58083,9 +58483,9 @@ object({
58083
58483
  /** Whether the humidifier is currently on. */
58084
58484
  on: boolean(),
58085
58485
  /** Current measured relative humidity (0..100). Null when not reported. */
58086
- currentHumidity: number().min(0).max(100).nullable(),
58486
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58087
58487
  /** Target relative humidity (0..100). Null when no setpoint surface. */
58088
- targetHumidity: number().min(0).max(100).nullable(),
58488
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58089
58489
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
58090
58490
  * device has no mode surface. */
58091
58491
  mode: string().nullable(),
@@ -58095,9 +58495,9 @@ object({
58095
58495
  * `idle` / `off`). Null when the device doesn't report it. */
58096
58496
  action: string().nullable(),
58097
58497
  /** HA `min_humidity` attribute. Null → UI uses 0. */
58098
- minHumidity: number().nullable(),
58498
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
58099
58499
  /** HA `max_humidity` attribute. Null → UI uses 100. */
58100
- maxHumidity: number().nullable(),
58500
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
58101
58501
  /** Ms epoch when the slice was last updated. */
58102
58502
  lastChangedAt: number()
58103
58503
  });
@@ -58121,22 +58521,13 @@ DeviceType.Humidifier, method(object({
58121
58521
  auth: "protected"
58122
58522
  });
58123
58523
  object({
58124
- /** Current relative humidity, 0..100. */
58125
- percent: number().min(0).max(100).meta({
58126
- unit: "%",
58524
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
58525
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
58526
+ precision: 0,
58127
58527
  label: "Humidity"
58128
- }),
58528
+ })),
58129
58529
  /** Ms epoch when the slice was last updated. */
58130
- lastFetchedAt: number(),
58131
- /** Live display unit from the upstream source (e.g. HA
58132
- * `attributes.unit_of_measurement`). The UI prefers this over the
58133
- * role's canonical unit. Absent → fall back to the canonical unit. */
58134
- unit: string().optional(),
58135
- /** Suggested decimal places for numeric display.
58136
- * Populated live from the upstream source when provided (e.g. HA
58137
- * `attributes.suggested_display_precision`). Falls back to
58138
- * auto-formatting when absent. */
58139
- precision: number().int().min(0).max(10).optional()
58530
+ lastFetchedAt: number()
58140
58531
  });
58141
58532
  DeviceType.Sensor, DeviceType.Thermostat;
58142
58533
  object({
@@ -58144,7 +58535,7 @@ object({
58144
58535
  * entity exposes no `entity_picture` (yet). */
58145
58536
  url: string().nullable(),
58146
58537
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
58147
- lastUpdated: number().nullable()
58538
+ lastUpdated: number().nullable().meta(readingMeta("none"))
58148
58539
  });
58149
58540
  DeviceType.Image;
58150
58541
  /**
@@ -58200,19 +58591,19 @@ var NormalizedRangeSchema = object({
58200
58591
  });
58201
58592
  object({
58202
58593
  /** Normalized 0–100. */
58203
- brightness: number().optional(),
58594
+ brightness: number().optional().meta(readingMeta("none")),
58204
58595
  /** Normalized 0–100. */
58205
- contrast: number().optional(),
58596
+ contrast: number().optional().meta(readingMeta("none")),
58206
58597
  /** Normalized 0–100. */
58207
- saturation: number().optional(),
58598
+ saturation: number().optional().meta(readingMeta("none")),
58208
58599
  /** Normalized 0–100. */
58209
- sharpness: number().optional(),
58600
+ sharpness: number().optional().meta(readingMeta("none")),
58210
58601
  mirror: boolean().optional(),
58211
58602
  flip: boolean().optional(),
58212
58603
  rotate: ImageRotateSchema.optional(),
58213
58604
  whiteBalance: WhiteBalanceModeSchema.optional(),
58214
58605
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
58215
- warmth: number().optional(),
58606
+ warmth: number().optional().meta(readingMeta("none")),
58216
58607
  exposureMode: ExposureModeSchema.optional(),
58217
58608
  backlightMode: BacklightModeSchema.optional(),
58218
58609
  lastFetchedAt: number()
@@ -58457,10 +58848,10 @@ var IntercomAbilitySchema = object({
58457
58848
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
58458
58849
  codecs: array(string()),
58459
58850
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
58460
- sampleRate: number(),
58851
+ sampleRate: number().meta(readingMeta("none")),
58461
58852
  duplex: _enum(["half", "full"]),
58462
58853
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
58463
- maxBacklogMs: number()
58854
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
58464
58855
  });
58465
58856
  var IntercomStatusSchema = object({
58466
58857
  /** True while a talk session is open. */
@@ -58571,13 +58962,13 @@ object({
58571
58962
  activity: LawnMowerActivitySchema,
58572
58963
  /** 0..100 battery percentage. Null when the device has no battery
58573
58964
  * reading. */
58574
- batteryLevel: number().min(0).max(100).nullable(),
58965
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58575
58966
  /** 0..100 mowing-completion percentage of the current task, or null when no
58576
58967
  * task is active / progress is unavailable. */
58577
- progressPercent: number().min(0).max(100).nullable(),
58968
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58578
58969
  /** Current device/event code (dynamic — mostly status, sometimes an error),
58579
58970
  * or null when unknown. */
58580
- currentCode: number().nullable(),
58971
+ currentCode: number().nullable().meta(readingMeta("none")),
58581
58972
  /** Human label for {@link currentCode}, or null when undecodable. */
58582
58973
  currentCodeLabel: string().nullable(),
58583
58974
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -59039,7 +59430,7 @@ object({
59039
59430
  state: MediaPlayerStateSchema,
59040
59431
  /** Volume as 0..100 inclusive. Null when the device has no volume
59041
59432
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59042
- volumeLevel: number().min(0).max(100).nullable(),
59433
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59043
59434
  /** Mute toggle distinct from volume=0. Null when no mute surface.
59044
59435
  * Pair with `DeviceFeature.MediaPlayerMute`. */
59045
59436
  isMuted: boolean().nullable(),
@@ -59052,10 +59443,10 @@ object({
59052
59443
  currentMedia: MediaInfoSchema.nullable(),
59053
59444
  /** Current playback position in ms. Null when not seekable or
59054
59445
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59055
- positionMs: number().int().nonnegative().nullable(),
59446
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59056
59447
  /** Total duration of the current media in ms. Null when unknown
59057
59448
  * (live stream) or nothing is playing. */
59058
- durationMs: number().int().nonnegative().nullable(),
59449
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59059
59450
  /** Shuffle toggle. Null when no shuffle surface.
59060
59451
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59061
59452
  shuffle: boolean().nullable(),
@@ -59355,7 +59746,7 @@ object({
59355
59746
  * it, every provider seeded it at boot, and every session left it
59356
59747
  * `detected: true` after its detach.
59357
59748
  */
59358
- autoClearAfterMs: number().nullable()
59749
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
59359
59750
  });
59360
59751
  /**
59361
59752
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -59405,7 +59796,7 @@ var MotionZoneRegionSchema = object({
59405
59796
  });
59406
59797
  object({
59407
59798
  enabled: boolean(),
59408
- sensitivity: number(),
59799
+ sensitivity: number().meta(readingMeta("none")),
59409
59800
  /** Grid region(s). Today exactly one `grid` shape. */
59410
59801
  regions: array(MotionZoneRegionSchema),
59411
59802
  lastFetchedAt: number()
@@ -59709,7 +60100,7 @@ var NavigationStatusSchema = object({
59709
60100
  following: boolean(),
59710
60101
  flash: boolean(),
59711
60102
  lightMode: NavigationLightModeSchema,
59712
- lightLevel: number().min(40).max(100),
60103
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
59713
60104
  /** Ms epoch when the slice was last updated. */
59714
60105
  lastChangedAt: number()
59715
60106
  });
@@ -59755,13 +60146,13 @@ var NetworkLinkStatusSchema = object({
59755
60146
  * one whose reading has not landed must not be drawn at 0 %. Consumers
59756
60147
  * SKIP a null rather than coerce it.
59757
60148
  */
59758
- signalPercent: number().min(0).max(100).nullable(),
60149
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59759
60150
  /** Raw received signal strength in dBm, when the firmware reports one. */
59760
- rssiDbm: number().optional(),
60151
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
59761
60152
  /** Network name of a wireless link, when the firmware reports it. */
59762
60153
  ssid: string().optional(),
59763
60154
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
59764
- lastUpdated: number()
60155
+ lastUpdated: number().meta(readingMeta("none"))
59765
60156
  });
59766
60157
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
59767
60158
  deviceId: number(),
@@ -60080,7 +60471,7 @@ object({
60080
60471
  lastError: string().nullable(),
60081
60472
  /** Number of deliveries currently buffered server-side (rate-limited
60082
60473
  * notifiers may queue). 0 when the provider sends synchronously. */
60083
- queueDepth: number().int().nonnegative(),
60474
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
60084
60475
  /** Per-feature capability matrix — authoritative source for the
60085
60476
  * compose-form field gating. */
60086
60477
  supports: NotifierSupportsSchema
@@ -60130,15 +60521,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
60130
60521
  acceptedAt: number()
60131
60522
  });
60132
60523
  object({
60133
- value: number(),
60134
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
60135
- * Populated live from the upstream source on each state push.
60136
- * Absent for unitless sensors. */
60137
- unit: string().optional(),
60138
- /** Suggested decimal places for numeric display.
60139
- * Populated live from the upstream source when provided.
60140
- * Falls back to auto-formatting when absent. */
60141
- precision: number().int().min(0).max(10).optional(),
60524
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
60525
+ value: number().nullable().meta(readingMeta("device")),
60142
60526
  /** Ms epoch when the slice was last updated. */
60143
60527
  lastFetchedAt: number()
60144
60528
  });
@@ -60428,23 +60812,26 @@ object({
60428
60812
  /** Food currently in the bowl (grams). Null when the device has not
60429
60813
  * reported a reading yet. On dual-hopper models this is the combined
60430
60814
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
60431
- foodLevel: number().nullable(),
60815
+ foodLevel: number().nullable().meta(readingMeta("none")),
60432
60816
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
60433
60817
  * single-hopper models. */
60434
- food1: number().nullable(),
60818
+ food1: number().nullable().meta(readingMeta("none")),
60435
60819
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
60436
60820
  * single-hopper models. */
60437
- food2: number().nullable(),
60821
+ food2: number().nullable().meta(readingMeta("none")),
60438
60822
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
60439
60823
  * (`device_class: problem`, on = low). True when the bowl is empty /
60440
60824
  * below the feeder's low threshold. */
60441
60825
  lowFood: boolean(),
60442
60826
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
60443
60827
  * device has no battery reading. */
60444
- batteryPower: number().min(0).max(100).nullable(),
60445
- /** Days of desiccant life remaining. Null when the model has no
60446
- * desiccant sensor. */
60447
- desiccantLeftDays: number().nullable(),
60828
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60829
+ /**
60830
+ * Desiccant life remaining, stored in seconds (duration base, D944).
60831
+ * Providers convert from the days the feeder reports. Null when the
60832
+ * model has no desiccant sensor.
60833
+ */
60834
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
60448
60835
  /** True while a feed is in progress. */
60449
60836
  feeding: boolean(),
60450
60837
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -60455,7 +60842,7 @@ object({
60455
60842
  * with `errorCode` for consumers that want the raw integer. */
60456
60843
  error: string().nullable(),
60457
60844
  /** Raw device error code (0 / null = no error). */
60458
- errorCode: number().nullable(),
60845
+ errorCode: number().nullable().meta(readingMeta("none")),
60459
60846
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
60460
60847
  isDualHopper: boolean(),
60461
60848
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -60465,7 +60852,7 @@ object({
60465
60852
  /** Play a chime when dispensing. */
60466
60853
  feedSound: boolean(),
60467
60854
  /** Speaker / prompt volume level (device-scaled integer). */
60468
- volume: number(),
60855
+ volume: number().meta(readingMeta("none")),
60469
60856
  /** Ms epoch when the slice was last refreshed from the cloud. */
60470
60857
  lastFetchedAt: number()
60471
60858
  });
@@ -60902,36 +61289,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
60902
61289
  });
60903
61290
  object({
60904
61291
  /** Instantaneous power draw in watts. */
60905
- watts: number().optional().meta({
60906
- unit: "W",
60907
- label: "Power"
60908
- }),
61292
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
60909
61293
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
60910
- kwhTotal: number().optional().meta({
60911
- unit: "kWh",
60912
- label: "Energy"
60913
- }),
61294
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
60914
61295
  /** Voltage in volts. */
60915
- volts: number().optional(),
61296
+ volts: number().optional().meta(readingMeta("voltage")),
60916
61297
  /** Current in amperes. */
60917
- amps: number().optional(),
60918
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
60919
- va: number().optional(),
61298
+ amps: number().optional().meta(readingMeta("current")),
61299
+ /** Apparent power in volt-amperes. */
61300
+ va: number().optional().meta(readingMeta("apparent-power")),
60920
61301
  /** Power factor (0..1) if reported. */
60921
- powerFactor: number().min(0).max(1).optional(),
61302
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
60922
61303
  /** Ms epoch when the slice was last updated. */
60923
- lastFetchedAt: number(),
60924
- /** Live display unit of the single metric this slice carries (e.g. HA
60925
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
60926
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
60927
- * unit per slice is unambiguous. The UI prefers this over the role's
60928
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
60929
- unit: string().optional(),
60930
- /** Suggested decimal places for numeric display.
60931
- * Populated live from the upstream source when provided (e.g. HA
60932
- * `attributes.suggested_display_precision`). Falls back to
60933
- * auto-formatting when absent. */
60934
- precision: number().int().min(0).max(10).optional()
61304
+ lastFetchedAt: number()
60935
61305
  });
60936
61306
  DeviceType.Sensor;
60937
61307
  /**
@@ -60952,11 +61322,11 @@ DeviceType.Sensor;
60952
61322
  */
60953
61323
  var GpsLocationSchema = object({
60954
61324
  /** Latitude in decimal degrees, -90..90. */
60955
- latitude: number().min(-90).max(90),
61325
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
60956
61326
  /** Longitude in decimal degrees, -180..180. */
60957
- longitude: number().min(-180).max(180),
61327
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
60958
61328
  /** Reported accuracy in meters (lower = better). */
60959
- accuracyMeters: number().nonnegative()
61329
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
60960
61330
  });
60961
61331
  object({
60962
61332
  /** `home` / `not_home` / any user-defined zone name. */
@@ -60968,25 +61338,16 @@ object({
60968
61338
  gps: GpsLocationSchema.nullable(),
60969
61339
  /** Optional battery level of the tracking device (0..100). Null
60970
61340
  * when not reported. */
60971
- batteryPercent: number().min(0).max(100).nullable(),
61341
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60972
61342
  /** Ms epoch when the slice was last updated. */
60973
61343
  lastChangedAt: number()
60974
61344
  });
60975
61345
  DeviceType.Presence;
60976
61346
  object({
60977
- /** Current pressure in hPa. */
60978
- hpa: number(),
61347
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
61348
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
60979
61349
  /** Ms epoch when the slice was last updated. */
60980
- lastFetchedAt: number(),
60981
- /** Live display unit from the upstream source (e.g. HA
60982
- * `attributes.unit_of_measurement`). The UI prefers this over the
60983
- * role's canonical unit. Absent → fall back to the canonical unit. */
60984
- unit: string().optional(),
60985
- /** Suggested decimal places for numeric display.
60986
- * Populated live from the upstream source when provided (e.g. HA
60987
- * `attributes.suggested_display_precision`). Falls back to
60988
- * auto-formatting when absent. */
60989
- precision: number().int().min(0).max(10).optional()
61350
+ lastFetchedAt: number()
60990
61351
  });
60991
61352
  DeviceType.Sensor;
60992
61353
  /**
@@ -61411,8 +61772,8 @@ var ptzCapability = {
61411
61772
  };
61412
61773
  var PtzAutotrackSettingsSchema = object({
61413
61774
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
61414
- stopDelaySeconds: number().int().min(0).max(300),
61415
- disappearDelaySeconds: number().int().min(0).max(300)
61775
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
61776
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
61416
61777
  });
61417
61778
  /**
61418
61779
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -61469,7 +61830,7 @@ object({
61469
61830
  /** null = unknown — never coerced to false (D393). */
61470
61831
  running: boolean().nullable(),
61471
61832
  /** Epoch ms the current boolean began; null while `running` is null. */
61472
- since: number().nullable(),
61833
+ since: number().nullable().meta(readingMeta("none")),
61473
61834
  source: RunningStateSourceSchema.meta({ label: "Source" }),
61474
61835
  phase: string().nullable(),
61475
61836
  /** null until a reading has been published (D393). */
@@ -62667,9 +63028,9 @@ var RecordTriggerSchema = _enum([
62667
63028
  */
62668
63029
  var RecordWindowSchema = object({
62669
63030
  trigger: RecordTriggerSchema,
62670
- day: number().int().min(0).max(6),
62671
- startMinute: number().int().min(0).max(1439),
62672
- endMinute: number().int().min(1).max(1440)
63031
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
63032
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
63033
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
62673
63034
  });
62674
63035
  /** Status of one physical volume, as the camera itself describes it. */
62675
63036
  var OnboardStorageVolumeSchema = object({
@@ -62738,10 +63099,10 @@ object({
62738
63099
  /** Master "record to the card at all" switch. */
62739
63100
  enabled: boolean().nullable(),
62740
63101
  overwriteWhenFull: boolean().nullable(),
62741
- preRecordSec: number().nullable(),
62742
- postRecordSec: number().nullable(),
63102
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
63103
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
62743
63104
  /** Length of one recorded file, in minutes. */
62744
- segmentMinutes: number().nullable(),
63105
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
62745
63106
  /** The primary track's weekly windows, flattened. */
62746
63107
  windows: array(RecordWindowSchema),
62747
63108
  /**
@@ -62757,7 +63118,7 @@ object({
62757
63118
  * provider reporting a non-zero count also reports the schedule as not
62758
63119
  * writable.
62759
63120
  */
62760
- unreadableWindows: number(),
63121
+ unreadableWindows: number().meta(readingMeta("count")),
62761
63122
  /**
62762
63123
  * The camera is scheduled to record and has NO usable storage.
62763
63124
  *
@@ -63394,7 +63755,7 @@ var CamStreamDescriptorSchema = object({
63394
63755
  url: string().optional(),
63395
63756
  codec: string().optional(),
63396
63757
  resolution: CamStreamResolutionSchema.optional(),
63397
- fps: number().positive().optional(),
63758
+ fps: number().positive().optional().meta(readingMeta("rate")),
63398
63759
  label: string().optional(),
63399
63760
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
63400
63761
  deviceFeatures: array(string()).optional(),
@@ -63424,18 +63785,18 @@ var StreamProfileSchema = _enum([
63424
63785
  ]);
63425
63786
  /** The current encoding config of one stream profile. */
63426
63787
  var StreamProfileConfigSchema = object({
63427
- width: number(),
63428
- height: number(),
63788
+ width: number().meta(readingMeta("none")),
63789
+ height: number().meta(readingMeta("none")),
63429
63790
  codec: _enum(["h264", "h265"]),
63430
- framerate: number(),
63431
- bitrate: number(),
63791
+ framerate: number().meta(readingMeta("rate")),
63792
+ bitrate: number().meta(readingMeta("bitrate")),
63432
63793
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
63433
63794
  encoderProfile: _enum([
63434
63795
  "high",
63435
63796
  "main",
63436
63797
  "baseline"
63437
63798
  ]).optional(),
63438
- gop: number().optional(),
63799
+ gop: number().optional().meta(readingMeta("none")),
63439
63800
  /**
63440
63801
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
63441
63802
  *
@@ -64078,23 +64439,13 @@ object({
64078
64439
  });
64079
64440
  DeviceType.Sensor;
64080
64441
  object({
64081
- /** Current temperature in Celsius. */
64082
- celsius: number().meta({
64083
- unit: "°C",
64442
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64443
+ celsius: number().nullable().meta(readingMeta("temperature", {
64444
+ precision: 1,
64084
64445
  label: "Temperature"
64085
- }),
64446
+ })),
64086
64447
  /** Ms epoch when the slice was last updated (push or poll). */
64087
- lastFetchedAt: number(),
64088
- /** Live display unit from the upstream source (e.g. HA
64089
- * `attributes.unit_of_measurement`). The UI prefers this over the
64090
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
64091
- * Absent → the UI falls back to the role's canonical unit. */
64092
- unit: string().optional(),
64093
- /** Suggested decimal places for numeric display.
64094
- * Populated live from the upstream source when provided (e.g. HA
64095
- * `attributes.suggested_display_precision`). Falls back to
64096
- * auto-formatting when absent. */
64097
- precision: number().int().min(0).max(10).optional()
64448
+ lastFetchedAt: number()
64098
64449
  });
64099
64450
  DeviceType.Sensor, DeviceType.Thermostat;
64100
64451
  /**
@@ -64346,16 +64697,16 @@ object({
64346
64697
  ]),
64347
64698
  /** 0..100 battery percentage. Null when the device has no battery
64348
64699
  * reading. */
64349
- batteryLevel: number().min(0).max(100).nullable(),
64700
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64350
64701
  /** Current fan-speed token (provider-verbatim). Null when unknown or
64351
64702
  * the vacuum has no speed control. */
64352
64703
  fanSpeed: string().nullable(),
64353
64704
  /** Speed tokens the hardware accepts — drives the UI selector. */
64354
64705
  availableFanSpeeds: array(string()),
64355
64706
  /** 0..100 cleaning-completion percentage of the current task, or null. */
64356
- progressPercent: number().min(0).max(100).nullable(),
64707
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64357
64708
  /** Current error code (0 / null = no error). */
64358
- errorCode: number().nullable(),
64709
+ errorCode: number().nullable().meta(readingMeta("none")),
64359
64710
  /** Human label for {@link errorCode}, or null when none / undecodable. */
64360
64711
  errorLabel: string().nullable(),
64361
64712
  /** Ms epoch when the slice was last updated. */
@@ -64394,7 +64745,7 @@ object({
64394
64745
  ]),
64395
64746
  /** 0 = fully closed, 100 = fully open. Null when the device has no
64396
64747
  * intermediate position surface. */
64397
- position: number().min(0).max(100).nullable(),
64748
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64398
64749
  /** Ms epoch when the slice was last updated. */
64399
64750
  lastChangedAt: number()
64400
64751
  });
@@ -64422,9 +64773,9 @@ object({
64422
64773
  DeviceType.Sensor;
64423
64774
  object({
64424
64775
  /** Current measured temperature. Null when not reported. */
64425
- currentTemp: number().nullable(),
64776
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
64426
64777
  /** Target temperature setpoint. Null when no setpoint surface. */
64427
- targetTemp: number().nullable(),
64778
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
64428
64779
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
64429
64780
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
64430
64781
  * device reports an unknown state. */
@@ -64435,9 +64786,9 @@ object({
64435
64786
  * has no away surface. */
64436
64787
  away: boolean().nullable(),
64437
64788
  /** HA `min_temp` attribute. Null when not reported. */
64438
- minTemp: number().nullable(),
64789
+ minTemp: number().nullable().meta(readingMeta("temperature")),
64439
64790
  /** HA `max_temp` attribute. Null when not reported. */
64440
- maxTemp: number().nullable(),
64791
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
64441
64792
  /** Ms epoch when the slice was last updated. */
64442
64793
  lastChangedAt: number()
64443
64794
  });
@@ -64461,25 +64812,18 @@ DeviceType.WaterHeater, method(object({
64461
64812
  auth: "protected"
64462
64813
  });
64463
64814
  object({
64464
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
64465
- * when no condition has been reported yet. */
64815
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
64466
64816
  condition: string().nullable(),
64467
- /** Current temperature in the reported unit. Null when not provided. */
64468
- temperature: number().nullable(),
64469
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
64470
- temperatureUnit: string().nullable(),
64817
+ /** Current temperature in Celsius. Null when not provided or refused. */
64818
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
64471
64819
  /** Relative humidity (0..100). Null when not provided. */
64472
- humidity: number().min(0).max(100).nullable(),
64473
- /** Barometric pressure in the reported unit. Null when not provided. */
64474
- pressure: number().nullable(),
64475
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
64476
- pressureUnit: string().nullable(),
64477
- /** Wind speed in the reported unit. Null when not provided. */
64478
- windSpeed: number().nullable(),
64479
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
64480
- windSpeedUnit: string().nullable(),
64820
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
64821
+ /** Barometric pressure in hPa. Null when not provided or refused. */
64822
+ pressure: number().nullable().meta(readingMeta("pressure")),
64823
+ /** Wind speed in metres per second. Null when not provided or refused. */
64824
+ windSpeed: number().nullable().meta(readingMeta("speed")),
64481
64825
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
64482
- windBearing: number().nullable(),
64826
+ windBearing: number().nullable().meta(readingMeta("angle")),
64483
64827
  /** Ms epoch when the slice was last updated. */
64484
64828
  lastFetchedAt: number()
64485
64829
  });
@@ -66216,6 +66560,13 @@ Object.freeze({
66216
66560
  access: "view",
66217
66561
  role: "admin"
66218
66562
  },
66563
+ "addons.getPackageDocs": {
66564
+ capName: "addons",
66565
+ capScope: "system",
66566
+ addonId: null,
66567
+ access: "view",
66568
+ role: "admin"
66569
+ },
66219
66570
  "addons.getVersions": {
66220
66571
  capName: "addons",
66221
66572
  capScope: "system",
@@ -69324,6 +69675,20 @@ Object.freeze({
69324
69675
  access: "view",
69325
69676
  role: "viewer"
69326
69677
  },
69678
+ "groupCommands.getStatus": {
69679
+ capName: "group-commands",
69680
+ capScope: "device",
69681
+ addonId: null,
69682
+ access: "view",
69683
+ role: "viewer"
69684
+ },
69685
+ "groupStatus.getStatus": {
69686
+ capName: "group-status",
69687
+ capScope: "device",
69688
+ addonId: null,
69689
+ access: "view",
69690
+ role: "viewer"
69691
+ },
69327
69692
  "heatingValve.getStatus": {
69328
69693
  capName: "heating-valve",
69329
69694
  capScope: "device",
@@ -79146,6 +79511,37 @@ var SENSOR_CAP_VOCABULARY = {
79146
79511
  icon: "flame"
79147
79512
  }]
79148
79513
  },
79514
+ "group-commands": {
79515
+ scope: "device",
79516
+ noun: t("Group command", "Comando di gruppo")
79517
+ },
79518
+ "group-status": {
79519
+ scope: "device",
79520
+ noun: t("Group", "Gruppo"),
79521
+ state: [
79522
+ {
79523
+ kind: "numeric",
79524
+ field: "included",
79525
+ quantity: t("the members reporting", "i membri che rispondono"),
79526
+ label: t("Reporting", "Rispondono"),
79527
+ icon: "users"
79528
+ },
79529
+ {
79530
+ kind: "numeric",
79531
+ field: "excluded",
79532
+ quantity: t("the members left out", "i membri esclusi"),
79533
+ label: t("Excluded", "Esclusi"),
79534
+ icon: "users"
79535
+ },
79536
+ {
79537
+ kind: "numeric",
79538
+ field: "total",
79539
+ quantity: t("the member count", "il numero di membri"),
79540
+ label: t("Members", "Membri"),
79541
+ icon: "sigma"
79542
+ }
79543
+ ]
79544
+ },
79149
79545
  "humidity-sensor": {
79150
79546
  scope: "device",
79151
79547
  noun: t("Humidity", "Umidità"),
@@ -79179,7 +79575,6 @@ var SENSOR_CAP_VOCABULARY = {
79179
79575
  field: "value",
79180
79576
  quantity: t("the reading", "il valore"),
79181
79577
  label: t("Reading", "Valore"),
79182
- unitField: "unit",
79183
79578
  icon: "gauge"
79184
79579
  }]
79185
79580
  },
@@ -79315,7 +79710,7 @@ var SENSOR_CAP_VOCABULARY = {
79315
79710
  field: "temperature",
79316
79711
  quantity: t("the outdoor temperature", "la temperatura esterna"),
79317
79712
  label: t("Temperature", "Temperatura"),
79318
- unitField: "temperatureUnit",
79713
+ unit: "°C",
79319
79714
  icon: "thermometer"
79320
79715
  },
79321
79716
  {
@@ -79331,7 +79726,7 @@ var SENSOR_CAP_VOCABULARY = {
79331
79726
  field: "windSpeed",
79332
79727
  quantity: t("the wind speed", "la velocità del vento"),
79333
79728
  label: t("Wind", "Vento"),
79334
- unitField: "windSpeedUnit",
79729
+ unit: "m/s",
79335
79730
  icon: "wind"
79336
79731
  },
79337
79732
  {
@@ -79339,7 +79734,7 @@ var SENSOR_CAP_VOCABULARY = {
79339
79734
  field: "pressure",
79340
79735
  quantity: t("the air pressure", "la pressione atmosferica"),
79341
79736
  label: t("Pressure", "Pressione"),
79342
- unitField: "pressureUnit",
79737
+ unit: "hPa",
79343
79738
  icon: "gauge"
79344
79739
  }
79345
79740
  ]
@@ -80678,6 +81073,16 @@ Object.freeze({
80678
81073
  form: "single",
80679
81074
  optional: false
80680
81075
  }],
81076
+ "groupCommands.getStatus": [{
81077
+ name: "deviceId",
81078
+ form: "single",
81079
+ optional: false
81080
+ }],
81081
+ "groupStatus.getStatus": [{
81082
+ name: "deviceId",
81083
+ form: "single",
81084
+ optional: false
81085
+ }],
80681
81086
  "heatingValve.getStatus": [{
80682
81087
  name: "deviceId",
80683
81088
  form: "single",
@@ -82662,6 +83067,8 @@ new Map(Object.entries({
82662
83067
  "detection-blind": "device-events",
82663
83068
  "motion-noisy": "device-events",
82664
83069
  "motion-calm": "device-events",
83070
+ "package-llm-silent": "device-events",
83071
+ "package-llm-answered": "device-events",
82665
83072
  "recording-stalled": "device-events",
82666
83073
  "recording-healthy": "device-events",
82667
83074
  "recording-degraded": "device-events",
@@ -82745,6 +83152,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
82745
83152
  label: "Motion without a subject",
82746
83153
  kinds: ["motion-noisy", "motion-calm"]
82747
83154
  },
83155
+ {
83156
+ id: "package-llm",
83157
+ family: "device-events",
83158
+ label: "Package confirm",
83159
+ kinds: ["package-llm-silent", "package-llm-answered"]
83160
+ },
82748
83161
  {
82749
83162
  id: "recording",
82750
83163
  family: "device-events",
@@ -82926,6 +83339,8 @@ var DEVICE_EVENTS = [
82926
83339
  "detection-blind",
82927
83340
  "motion-noisy",
82928
83341
  "motion-calm",
83342
+ "package-llm-silent",
83343
+ "package-llm-answered",
82929
83344
  "recording-stalled",
82930
83345
  "recording-healthy",
82931
83346
  "recording-degraded",
@@ -83194,6 +83609,15 @@ new Map([
83194
83609
  snapshotLabel: "Cameras with constant motion now",
83195
83610
  snapshotSection: null
83196
83611
  },
83612
+ {
83613
+ id: "package-llm-confirm",
83614
+ family: "device-events",
83615
+ label: "Package confirm (not answering / answering again)",
83616
+ bad: ["package-llm-silent"],
83617
+ recovery: ["package-llm-answered"],
83618
+ snapshotLabel: "Cameras whose package confirm is not answering",
83619
+ snapshotSection: null
83620
+ },
83197
83621
  {
83198
83622
  id: "recording-health",
83199
83623
  family: "device-events",