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