@camstack/addon-smtp-nodemailer 1.2.141 → 1.2.142

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.
@@ -36,7 +36,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
36
36
  }) : target, mod));
37
37
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
38
38
  //#endregion
39
- //#region ../types/dist/event-category-DHXxB8t9.mjs
39
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
40
40
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
41
41
  EventCategory["SystemBoot"] = "system.boot";
42
42
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -362,6 +362,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
362
362
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
363
363
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
364
364
  /**
365
+ * A camera's package LLM confirm has failed closed three times in a row
366
+ * (D931, 2026-10-26). One edge per camera until
367
+ * {@link PipelinePackageLlmAnswered}.
368
+ */
369
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
370
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
371
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
372
+ /**
365
373
  * Per-camera pipeline config was mutated by the orchestrator
366
374
  * (a settings change via `setAgentInferenceDevices` /
367
375
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5471,7 +5479,7 @@ var ZodIssueCode = {
5471
5479
  var ZodFirstPartyTypeKind;
5472
5480
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5473
5481
  //#endregion
5474
- //#region ../types/dist/sleep-BErAxhul.mjs
5482
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5475
5483
  /**
5476
5484
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5477
5485
  * window to float samples (D455).
@@ -7958,6 +7966,99 @@ var RecordKindDeclarationSchema = object({
7958
7966
  }).strict();
7959
7967
  /** The kind names: lower-case, dash separated, short enough for an index name. */
7960
7968
  var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
7969
+ var READING_QUANTITIES = [
7970
+ "temperature",
7971
+ "pressure",
7972
+ "speed",
7973
+ "precipitation",
7974
+ "precipitation-rate",
7975
+ "length",
7976
+ "area",
7977
+ "illuminance",
7978
+ "irradiance",
7979
+ "power",
7980
+ "energy",
7981
+ "voltage",
7982
+ "current",
7983
+ "apparent-power",
7984
+ "humidity",
7985
+ "percentage",
7986
+ "concentration-volume",
7987
+ "concentration-mass",
7988
+ "concentration-ppb",
7989
+ "volume",
7990
+ "duration",
7991
+ "duration-ms",
7992
+ "information",
7993
+ "sound-level",
7994
+ "signal-level",
7995
+ "colour-temperature",
7996
+ "angle",
7997
+ "bitrate",
7998
+ "rate",
7999
+ "mass",
8000
+ "index",
8001
+ "ratio",
8002
+ "count",
8003
+ "none",
8004
+ "device"
8005
+ ];
8006
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8007
+ new Set(READING_QUANTITIES);
8008
+ /**
8009
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8010
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8011
+ */
8012
+ var QUANTITY_BASE = {
8013
+ temperature: "°C",
8014
+ pressure: "hPa",
8015
+ speed: "m/s",
8016
+ precipitation: "mm",
8017
+ "precipitation-rate": "mm/h",
8018
+ length: "m",
8019
+ area: "m²",
8020
+ illuminance: "lx",
8021
+ irradiance: "W/m²",
8022
+ power: "W",
8023
+ energy: "kWh",
8024
+ voltage: "V",
8025
+ current: "A",
8026
+ "apparent-power": "VA",
8027
+ humidity: "%",
8028
+ percentage: "%",
8029
+ "concentration-volume": "ppm",
8030
+ "concentration-mass": "µg/m³",
8031
+ "concentration-ppb": "ppb",
8032
+ volume: "L",
8033
+ duration: "s",
8034
+ "duration-ms": "ms",
8035
+ information: "B",
8036
+ "sound-level": "dBFS",
8037
+ "signal-level": "dBm",
8038
+ "colour-temperature": "mired",
8039
+ angle: "°",
8040
+ bitrate: "kbps",
8041
+ rate: "fps",
8042
+ mass: "kg",
8043
+ index: "none",
8044
+ ratio: "none",
8045
+ count: "none",
8046
+ none: "none",
8047
+ device: "device"
8048
+ };
8049
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8050
+ function baseUnitOf(quantity) {
8051
+ const symbol = QUANTITY_BASE[quantity];
8052
+ if (symbol === "none" || symbol === "device") return void 0;
8053
+ return symbol;
8054
+ }
8055
+ function readingMeta(quantity, extras) {
8056
+ return {
8057
+ quantity,
8058
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8059
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8060
+ };
8061
+ }
7961
8062
  /** Shared Zod schemas used across streaming capabilities. */
7962
8063
  var CamProfileSchema = _enum([
7963
8064
  "high",
@@ -7985,8 +8086,8 @@ var CamStreamKindSchema = _enum([
7985
8086
  "derived"
7986
8087
  ]);
7987
8088
  var CamStreamResolutionSchema = object({
7988
- width: number().int().positive(),
7989
- height: number().int().positive()
8089
+ width: number().int().positive().meta(readingMeta("none")),
8090
+ height: number().int().positive().meta(readingMeta("none"))
7990
8091
  });
7991
8092
  var CameraStreamSchema = object({
7992
8093
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8500,6 +8601,8 @@ var CAP_NAMES = Object.freeze([
8500
8601
  "filesystem-browse",
8501
8602
  "flood",
8502
8603
  "gas",
8604
+ "group-commands",
8605
+ "group-status",
8503
8606
  "heating-valve",
8504
8607
  "humidifier",
8505
8608
  "humidity-sensor",
@@ -8629,14 +8732,6 @@ Object.freeze({
8629
8732
  {
8630
8733
  "path": "aqi",
8631
8734
  "valueType": "number"
8632
- },
8633
- {
8634
- "path": "unit",
8635
- "valueType": "string"
8636
- },
8637
- {
8638
- "path": "precision",
8639
- "valueType": "number"
8640
8735
  }
8641
8736
  ],
8642
8737
  "alarm-panel": [
@@ -8672,20 +8767,10 @@ Object.freeze({
8672
8767
  "valueType": "boolean"
8673
8768
  }
8674
8769
  ],
8675
- "ambient-light-sensor": [
8676
- {
8677
- "path": "lux",
8678
- "valueType": "number"
8679
- },
8680
- {
8681
- "path": "unit",
8682
- "valueType": "string"
8683
- },
8684
- {
8685
- "path": "precision",
8686
- "valueType": "number"
8687
- }
8688
- ],
8770
+ "ambient-light-sensor": [{
8771
+ "path": "lux",
8772
+ "valueType": "number"
8773
+ }],
8689
8774
  "audio-metrics": [
8690
8775
  {
8691
8776
  "path": "ts",
@@ -8765,6 +8850,10 @@ Object.freeze({
8765
8850
  "path": "lastUpdated",
8766
8851
  "valueType": "number"
8767
8852
  },
8853
+ {
8854
+ "path": "volts",
8855
+ "valueType": "number"
8856
+ },
8768
8857
  {
8769
8858
  "path": "lastContactAt",
8770
8859
  "valueType": "number"
@@ -9007,10 +9096,6 @@ Object.freeze({
9007
9096
  "datetime"
9008
9097
  ]
9009
9098
  },
9010
- {
9011
- "path": "unit",
9012
- "valueType": "string"
9013
- },
9014
9099
  {
9015
9100
  "path": "min",
9016
9101
  "valueType": "number"
@@ -9022,19 +9107,6 @@ Object.freeze({
9022
9107
  {
9023
9108
  "path": "step",
9024
9109
  "valueType": "number"
9025
- },
9026
- {
9027
- "path": "precision",
9028
- "valueType": "number"
9029
- },
9030
- {
9031
- "path": "format",
9032
- "valueType": "enum",
9033
- "enumValues": [
9034
- "date",
9035
- "time",
9036
- "datetime"
9037
- ]
9038
9110
  }
9039
9111
  ],
9040
9112
  "cover": [
@@ -9275,13 +9347,8 @@ Object.freeze({
9275
9347
  "path": "value",
9276
9348
  "valueType": "string"
9277
9349
  }, {
9278
- "path": "format",
9279
- "valueType": "enum",
9280
- "enumValues": [
9281
- "date",
9282
- "time",
9283
- "datetime"
9284
- ]
9350
+ "path": "timestamp",
9351
+ "valueType": "boolean"
9285
9352
  }],
9286
9353
  "event-emitter": [
9287
9354
  {
@@ -9354,6 +9421,20 @@ Object.freeze({
9354
9421
  "path": "detected",
9355
9422
  "valueType": "boolean"
9356
9423
  }],
9424
+ "group-status": [
9425
+ {
9426
+ "path": "included",
9427
+ "valueType": "number"
9428
+ },
9429
+ {
9430
+ "path": "excluded",
9431
+ "valueType": "number"
9432
+ },
9433
+ {
9434
+ "path": "total",
9435
+ "valueType": "number"
9436
+ }
9437
+ ],
9357
9438
  "heating-valve": [{
9358
9439
  "path": "position",
9359
9440
  "valueType": "number"
@@ -9388,20 +9469,10 @@ Object.freeze({
9388
9469
  "valueType": "number"
9389
9470
  }
9390
9471
  ],
9391
- "humidity-sensor": [
9392
- {
9393
- "path": "percent",
9394
- "valueType": "number"
9395
- },
9396
- {
9397
- "path": "unit",
9398
- "valueType": "string"
9399
- },
9400
- {
9401
- "path": "precision",
9402
- "valueType": "number"
9403
- }
9404
- ],
9472
+ "humidity-sensor": [{
9473
+ "path": "percent",
9474
+ "valueType": "number"
9475
+ }],
9405
9476
  "image": [{
9406
9477
  "path": "url",
9407
9478
  "valueType": "string"
@@ -9740,20 +9811,10 @@ Object.freeze({
9740
9811
  "valueType": "boolean"
9741
9812
  }
9742
9813
  ],
9743
- "numeric-sensor": [
9744
- {
9745
- "path": "value",
9746
- "valueType": "number"
9747
- },
9748
- {
9749
- "path": "unit",
9750
- "valueType": "string"
9751
- },
9752
- {
9753
- "path": "precision",
9754
- "valueType": "number"
9755
- }
9756
- ],
9814
+ "numeric-sensor": [{
9815
+ "path": "value",
9816
+ "valueType": "number"
9817
+ }],
9757
9818
  "pet-feeder": [
9758
9819
  {
9759
9820
  "path": "foodLevel",
@@ -9845,14 +9906,6 @@ Object.freeze({
9845
9906
  {
9846
9907
  "path": "powerFactor",
9847
9908
  "valueType": "number"
9848
- },
9849
- {
9850
- "path": "unit",
9851
- "valueType": "string"
9852
- },
9853
- {
9854
- "path": "precision",
9855
- "valueType": "number"
9856
9909
  }
9857
9910
  ],
9858
9911
  "presence": [
@@ -9881,20 +9934,10 @@ Object.freeze({
9881
9934
  "valueType": "number"
9882
9935
  }
9883
9936
  ],
9884
- "pressure-sensor": [
9885
- {
9886
- "path": "hpa",
9887
- "valueType": "number"
9888
- },
9889
- {
9890
- "path": "unit",
9891
- "valueType": "string"
9892
- },
9893
- {
9894
- "path": "precision",
9895
- "valueType": "number"
9896
- }
9897
- ],
9937
+ "pressure-sensor": [{
9938
+ "path": "hpa",
9939
+ "valueType": "number"
9940
+ }],
9898
9941
  "privacy-mask": [{
9899
9942
  "path": "enabled",
9900
9943
  "valueType": "boolean"
@@ -10205,20 +10248,10 @@ Object.freeze({
10205
10248
  "path": "tampered",
10206
10249
  "valueType": "boolean"
10207
10250
  }],
10208
- "temperature-sensor": [
10209
- {
10210
- "path": "celsius",
10211
- "valueType": "number"
10212
- },
10213
- {
10214
- "path": "unit",
10215
- "valueType": "string"
10216
- },
10217
- {
10218
- "path": "precision",
10219
- "valueType": "number"
10220
- }
10221
- ],
10251
+ "temperature-sensor": [{
10252
+ "path": "celsius",
10253
+ "valueType": "number"
10254
+ }],
10222
10255
  "update": [
10223
10256
  {
10224
10257
  "path": "currentVersion",
@@ -10329,10 +10362,6 @@ Object.freeze({
10329
10362
  "path": "temperature",
10330
10363
  "valueType": "number"
10331
10364
  },
10332
- {
10333
- "path": "temperatureUnit",
10334
- "valueType": "string"
10335
- },
10336
10365
  {
10337
10366
  "path": "humidity",
10338
10367
  "valueType": "number"
@@ -10341,18 +10370,10 @@ Object.freeze({
10341
10370
  "path": "pressure",
10342
10371
  "valueType": "number"
10343
10372
  },
10344
- {
10345
- "path": "pressureUnit",
10346
- "valueType": "string"
10347
- },
10348
10373
  {
10349
10374
  "path": "windSpeed",
10350
10375
  "valueType": "number"
10351
10376
  },
10352
- {
10353
- "path": "windSpeedUnit",
10354
- "valueType": "string"
10355
- },
10356
10377
  {
10357
10378
  "path": "windBearing",
10358
10379
  "valueType": "number"
@@ -10745,7 +10766,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
10745
10766
  return DeviceRole;
10746
10767
  }({});
10747
10768
  //#endregion
10748
- //#region ../types/dist/composition-kEl9mevu.mjs
10769
+ //#region ../types/dist/composition-OhpuespA.mjs
10749
10770
  /**
10750
10771
  * The ffmpeg build every CamStack node downloads, per platform and arch.
10751
10772
  *
@@ -11500,15 +11521,74 @@ function validateExpressionSource(src) {
11500
11521
  }
11501
11522
  return null;
11502
11523
  }
11503
- /** A composed device's stableId is this prefix plus the block id. */
11504
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11505
- /** Longest snippet a `code` source may carry (slice 4). */
11506
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11524
+ /**
11525
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11526
+ * id. Split out so helper schemas can name it without a cycle through
11527
+ * `composition.ts` (D659).
11528
+ */
11507
11529
  var CompositionSourceRefSchema = object({
11508
11530
  /** The owning addon; `stableId` is unique only within it. */
11509
11531
  addonId: string().min(1),
11510
11532
  stableId: string().min(1)
11511
11533
  });
11534
+ var GROUP_TEMPLATE_IDS = [
11535
+ "temperature",
11536
+ "humidity",
11537
+ "pressure",
11538
+ "illuminance",
11539
+ "power",
11540
+ "energy",
11541
+ "battery",
11542
+ "numeric",
11543
+ "motion",
11544
+ "contact",
11545
+ "leak",
11546
+ "smoke",
11547
+ "occupancy",
11548
+ "binary",
11549
+ "switch",
11550
+ "light"
11551
+ ];
11552
+ var NUMERIC_AGGREGATES = [
11553
+ "mean",
11554
+ "median",
11555
+ "min",
11556
+ "max",
11557
+ "sum"
11558
+ ];
11559
+ var BINARY_AGGREGATES = ["any", "all"];
11560
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11561
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11562
+ var AGGREGATE_REDUCERS = [
11563
+ "mean",
11564
+ "median",
11565
+ "min",
11566
+ "max",
11567
+ "sum",
11568
+ "any",
11569
+ "all"
11570
+ ];
11571
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11572
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11573
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11574
+ /** One member. The field is stored so occupancy can name motion or binary. */
11575
+ var HelperMemberSchema = object({
11576
+ source: CompositionSourceRefSchema,
11577
+ cap: string().min(1),
11578
+ fieldPath: string().min(1)
11579
+ });
11580
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
11581
+ kind: literal("group"),
11582
+ templateId: GroupTemplateIdSchema,
11583
+ aggregate: AggregateReducerSchema,
11584
+ members: array(HelperMemberSchema).min(1).max(64),
11585
+ /** Actuator groups only. Readings leave this unset. */
11586
+ active: ActiveCriterionSchema.optional()
11587
+ })]);
11588
+ /** A composed device's stableId is this prefix plus the block id. */
11589
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11590
+ /** Longest snippet a `code` source may carry (slice 4). */
11591
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11512
11592
  /** One field of one capability of one source device. */
11513
11593
  var CompositionFieldReadSchema = object({
11514
11594
  source: CompositionSourceRefSchema,
@@ -11552,7 +11632,33 @@ var CompositionCodeSourceSchema = object({
11552
11632
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11553
11633
  CompositionFromSourceSchema,
11554
11634
  CompositionExpressionSourceSchema,
11555
- CompositionCodeSourceSchema
11635
+ CompositionCodeSourceSchema,
11636
+ object({
11637
+ kind: literal("aggregate"),
11638
+ reducer: _enum([
11639
+ "mean",
11640
+ "median",
11641
+ "min",
11642
+ "max",
11643
+ "sum",
11644
+ "any",
11645
+ "all"
11646
+ ]),
11647
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
11648
+ /** `value` is the reading; the others are the member counts. */
11649
+ publish: _enum([
11650
+ "value",
11651
+ "included",
11652
+ "excluded",
11653
+ "total"
11654
+ ]),
11655
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
11656
+ empty: _enum([
11657
+ "null",
11658
+ "omit",
11659
+ "offline"
11660
+ ])
11661
+ })
11556
11662
  ]);
11557
11663
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11558
11664
  kind: literal("forward"),
@@ -11598,7 +11704,13 @@ var CompositionSchema = object({
11598
11704
  kind: literal("existing"),
11599
11705
  device: CompositionSourceRefSchema
11600
11706
  })]),
11601
- features: array(CompositionFeatureSchema).min(1).max(16)
11707
+ features: array(CompositionFeatureSchema).min(1).max(16),
11708
+ /**
11709
+ * Present when the block is a helper template. Absent on a free
11710
+ * composition (the room example, device 4603). The editor reads this,
11711
+ * never the compiled features.
11712
+ */
11713
+ helper: HelperSpecSchema.optional()
11602
11714
  }).superRefine((composition, ctx) => {
11603
11715
  const seen = /* @__PURE__ */ new Set();
11604
11716
  composition.features.forEach((feature, index) => {
@@ -17700,7 +17812,11 @@ var CompositionProblemSchema = object({
17700
17812
  "item-field-reserved",
17701
17813
  "target-missing",
17702
17814
  "target-unreadable",
17703
- "not-replaceable"
17815
+ "not-replaceable",
17816
+ "native-capability-replace",
17817
+ "mixed-quantity",
17818
+ "helper-mismatch",
17819
+ "member-constraint"
17704
17820
  ]),
17705
17821
  path: string(),
17706
17822
  message: string()
@@ -17721,7 +17837,8 @@ var CompositionUnavailableModeSchema = _enum([
17721
17837
  "null",
17722
17838
  "offline",
17723
17839
  "unknown",
17724
- "release"
17840
+ "release",
17841
+ "omit"
17725
17842
  ]);
17726
17843
  /**
17727
17844
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -17758,9 +17875,11 @@ var CompositionFieldPlanSchema = object({
17758
17875
  reads: array(CompositionFieldReadSchema)
17759
17876
  });
17760
17877
  /**
17761
- * What a feature does to its target (D663): `new` builds a capability on a new
17762
- * device; on an existing device it `add`s a capability the device lacks, or
17763
- * `replace`s named fields of one it has natively.
17878
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
17879
+ * new device; on an existing device it `add`s a capability the device lacks.
17880
+ * Replacing a capability the device already serves is refused
17881
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
17882
+ * wire for a claim the hub already holds.
17764
17883
  */
17765
17884
  var CompositionFeatureModeSchema = _enum([
17766
17885
  "new",
@@ -17933,6 +18052,275 @@ Object.freeze({
17933
18052
  result: ["number", "null"]
17934
18053
  }
17935
18054
  });
18055
+ /**
18056
+ * One catalog row per base reading a group helper can build (D944 §3).
18057
+ * Data, not a form default written into the block.
18058
+ */
18059
+ var NOW = {
18060
+ fieldPath: "lastFetchedAt",
18061
+ expr: "now"
18062
+ };
18063
+ var CHANGED = {
18064
+ fieldPath: "lastChangedAt",
18065
+ expr: "now"
18066
+ };
18067
+ function numeric(input) {
18068
+ return {
18069
+ ...input,
18070
+ family: "numeric",
18071
+ deviceType: DeviceType.Sensor,
18072
+ memberDeviceType: DeviceType.Sensor,
18073
+ commandCaps: [],
18074
+ baseUnit: baseUnitOf(input.quantity),
18075
+ aggregates: NUMERIC_AGGREGATES,
18076
+ accept: [{
18077
+ cap: input.outputCap,
18078
+ fieldPath: input.outputField
18079
+ }]
18080
+ };
18081
+ }
18082
+ function binary(input) {
18083
+ return {
18084
+ ...input,
18085
+ family: "binary",
18086
+ deviceType: DeviceType.Sensor,
18087
+ memberDeviceType: DeviceType.Sensor,
18088
+ commandCaps: [],
18089
+ quantity: "none",
18090
+ baseUnit: void 0,
18091
+ aggregates: BINARY_AGGREGATES,
18092
+ empty: "offline"
18093
+ };
18094
+ }
18095
+ var GROUP_TEMPLATES = [
18096
+ numeric({
18097
+ id: "temperature",
18098
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18099
+ role: DeviceRole.TemperatureSensor,
18100
+ outputCap: "temperature-sensor",
18101
+ outputField: "celsius",
18102
+ quantity: "temperature",
18103
+ defaultAggregate: "mean",
18104
+ empty: "null",
18105
+ companions: [NOW]
18106
+ }),
18107
+ numeric({
18108
+ id: "humidity",
18109
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18110
+ role: DeviceRole.HumiditySensor,
18111
+ outputCap: "humidity-sensor",
18112
+ outputField: "percent",
18113
+ quantity: "humidity",
18114
+ defaultAggregate: "mean",
18115
+ empty: "null",
18116
+ companions: [NOW]
18117
+ }),
18118
+ numeric({
18119
+ id: "pressure",
18120
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18121
+ role: DeviceRole.PressureSensor,
18122
+ outputCap: "pressure-sensor",
18123
+ outputField: "hpa",
18124
+ quantity: "pressure",
18125
+ defaultAggregate: "mean",
18126
+ empty: "null",
18127
+ companions: [NOW]
18128
+ }),
18129
+ numeric({
18130
+ id: "illuminance",
18131
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18132
+ role: DeviceRole.AmbientLightSensor,
18133
+ outputCap: "ambient-light-sensor",
18134
+ outputField: "lux",
18135
+ quantity: "illuminance",
18136
+ defaultAggregate: "mean",
18137
+ empty: "null",
18138
+ companions: [NOW]
18139
+ }),
18140
+ numeric({
18141
+ id: "power",
18142
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18143
+ role: DeviceRole.PowerSensor,
18144
+ outputCap: "power-meter",
18145
+ outputField: "watts",
18146
+ quantity: "power",
18147
+ defaultAggregate: "sum",
18148
+ empty: "omit",
18149
+ companions: [NOW]
18150
+ }),
18151
+ numeric({
18152
+ id: "energy",
18153
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18154
+ role: DeviceRole.EnergySensor,
18155
+ outputCap: "power-meter",
18156
+ outputField: "kwhTotal",
18157
+ quantity: "energy",
18158
+ defaultAggregate: "sum",
18159
+ empty: "omit",
18160
+ companions: [NOW]
18161
+ }),
18162
+ numeric({
18163
+ id: "battery",
18164
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18165
+ role: DeviceRole.BatterySensor,
18166
+ outputCap: "battery",
18167
+ outputField: "percentage",
18168
+ quantity: "percentage",
18169
+ defaultAggregate: "mean",
18170
+ empty: "null",
18171
+ companions: [
18172
+ {
18173
+ fieldPath: "charging",
18174
+ expr: "\"unknown\""
18175
+ },
18176
+ {
18177
+ fieldPath: "sleeping",
18178
+ expr: "false"
18179
+ },
18180
+ {
18181
+ fieldPath: "lastUpdated",
18182
+ expr: "now"
18183
+ }
18184
+ ]
18185
+ }),
18186
+ numeric({
18187
+ id: "numeric",
18188
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18189
+ role: DeviceRole.NumericSensor,
18190
+ outputCap: "numeric-sensor",
18191
+ outputField: "value",
18192
+ quantity: "device",
18193
+ defaultAggregate: "mean",
18194
+ empty: "null",
18195
+ companions: [NOW]
18196
+ }),
18197
+ binary({
18198
+ id: "motion",
18199
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18200
+ role: DeviceRole.MotionSensor,
18201
+ outputCap: "motion",
18202
+ outputField: "detected",
18203
+ defaultAggregate: "any",
18204
+ accept: [{
18205
+ cap: "motion",
18206
+ fieldPath: "detected"
18207
+ }],
18208
+ companions: [{
18209
+ fieldPath: "lastDetectedAt",
18210
+ expr: "null"
18211
+ }, {
18212
+ fieldPath: "autoClearAfterMs",
18213
+ expr: "null"
18214
+ }]
18215
+ }),
18216
+ binary({
18217
+ id: "contact",
18218
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18219
+ role: DeviceRole.ContactSensor,
18220
+ outputCap: "contact",
18221
+ outputField: "entryOpen",
18222
+ defaultAggregate: "any",
18223
+ accept: [{
18224
+ cap: "contact",
18225
+ fieldPath: "entryOpen"
18226
+ }],
18227
+ companions: [CHANGED]
18228
+ }),
18229
+ binary({
18230
+ id: "leak",
18231
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18232
+ role: DeviceRole.LeakSensor,
18233
+ outputCap: "flood",
18234
+ outputField: "flooded",
18235
+ defaultAggregate: "any",
18236
+ accept: [{
18237
+ cap: "flood",
18238
+ fieldPath: "flooded"
18239
+ }],
18240
+ companions: [CHANGED]
18241
+ }),
18242
+ binary({
18243
+ id: "smoke",
18244
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18245
+ role: DeviceRole.SmokeSensor,
18246
+ outputCap: "smoke",
18247
+ outputField: "detected",
18248
+ defaultAggregate: "any",
18249
+ accept: [{
18250
+ cap: "smoke",
18251
+ fieldPath: "detected"
18252
+ }],
18253
+ companions: [CHANGED]
18254
+ }),
18255
+ binary({
18256
+ id: "occupancy",
18257
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18258
+ role: DeviceRole.BinarySensor,
18259
+ outputCap: "binary",
18260
+ outputField: "on",
18261
+ defaultAggregate: "any",
18262
+ accept: [{
18263
+ cap: "motion",
18264
+ fieldPath: "detected"
18265
+ }, {
18266
+ cap: "binary",
18267
+ fieldPath: "on"
18268
+ }],
18269
+ companions: [CHANGED]
18270
+ }),
18271
+ binary({
18272
+ id: "binary",
18273
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18274
+ role: DeviceRole.BinarySensor,
18275
+ outputCap: "binary",
18276
+ outputField: "on",
18277
+ defaultAggregate: "any",
18278
+ accept: [{
18279
+ cap: "binary",
18280
+ fieldPath: "on"
18281
+ }],
18282
+ companions: [CHANGED]
18283
+ }),
18284
+ actuator({
18285
+ id: "switch",
18286
+ 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.",
18287
+ deviceType: DeviceType.Switch,
18288
+ outputCap: "switch",
18289
+ commandCaps: ["switch"]
18290
+ }),
18291
+ actuator({
18292
+ id: "light",
18293
+ 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.",
18294
+ deviceType: DeviceType.Light,
18295
+ outputCap: "switch",
18296
+ commandCaps: [
18297
+ "switch",
18298
+ "brightness",
18299
+ "color"
18300
+ ]
18301
+ })
18302
+ ];
18303
+ function actuator(input) {
18304
+ return {
18305
+ ...input,
18306
+ family: "actuator",
18307
+ role: void 0,
18308
+ memberDeviceType: input.deviceType,
18309
+ outputField: "on",
18310
+ quantity: "none",
18311
+ baseUnit: void 0,
18312
+ aggregates: BINARY_AGGREGATES,
18313
+ defaultAggregate: "any",
18314
+ empty: "offline",
18315
+ accept: [{
18316
+ cap: "switch",
18317
+ fieldPath: "on"
18318
+ }],
18319
+ companions: [CHANGED]
18320
+ };
18321
+ }
18322
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18323
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
17936
18324
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
17937
18325
  /**
17938
18326
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -19646,12 +20034,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
19646
20034
  * providers detect renames by matching `uniqueId` and patch `id`
19647
20035
  * in-place so dispatch keeps working.
19648
20036
  *
19649
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
19650
- * `precision`) live in the cap STATUS SLICE so they flow live with every
19651
- * state push and are the single source of truth. The `device_class` tag is
19652
- * an HA-specific input that is consumed at the discovery boundary
19653
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
19654
- * stored after that point.
20037
+ * **Display unit and precision are NOT stored here.** They are a read-time
20038
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20039
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20040
+ * neither is repeated on each sample. The `device_class` tag is an
20041
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20042
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
19655
20043
  *
19656
20044
  * The `raw` escape hatch carries provider-specific attributes that don't
19657
20045
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -19665,6 +20053,14 @@ var SourceInfoSchema = object({
19665
20053
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
19666
20054
  * Used to detect rename when `id` changes. */
19667
20055
  uniqueId: string().optional(),
20056
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20057
+ quantity: ReadingQuantitySchema.optional(),
20058
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20059
+ dateTimeShape: _enum([
20060
+ "date",
20061
+ "time",
20062
+ "datetime"
20063
+ ]).optional(),
19668
20064
  /** Provider-specific extras that don't fit the structured slots.
19669
20065
  * Whitelist what goes here per provider — do NOT dump entire HA
19670
20066
  * attribute blobs (would explode the meta JSON column). */
@@ -40577,7 +40973,8 @@ method(object({
40577
40973
  "enum"
40578
40974
  ]),
40579
40975
  enumValues: array(string()).optional(),
40580
- item: boolean().optional()
40976
+ item: boolean().optional(),
40977
+ quantity: ReadingQuantitySchema.optional()
40581
40978
  })).readonly(),
40582
40979
  itemArray: object({
40583
40980
  path: string(),
@@ -44377,8 +44774,8 @@ var MaskPolygonShapeSchema = object({
44377
44774
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44378
44775
  var MaskGridShapeSchema = object({
44379
44776
  kind: literal("grid"),
44380
- gridWidth: number(),
44381
- gridHeight: number(),
44777
+ gridWidth: number().meta(readingMeta("none")),
44778
+ gridHeight: number().meta(readingMeta("none")),
44382
44779
  cells: array(boolean())
44383
44780
  });
44384
44781
  discriminatedUnion("kind", [
@@ -44490,6 +44887,8 @@ var NcSystemEventKindSchema = _enum([
44490
44887
  "detection-blind",
44491
44888
  "motion-noisy",
44492
44889
  "motion-calm",
44890
+ "package-llm-silent",
44891
+ "package-llm-answered",
44493
44892
  "recording-stalled",
44494
44893
  "recording-healthy",
44495
44894
  "recording-degraded",
@@ -55517,6 +55916,28 @@ var PackageVersionInfoSchema = object({
55517
55916
  deprecated: string().optional(),
55518
55917
  distTags: array(string()).readonly()
55519
55918
  });
55919
+ /**
55920
+ * One published document. `ok` is markdown from the registry. `missing` means
55921
+ * the registry answered and the file was not there. `error` is a registry or
55922
+ * tarball failure — never an empty string standing in for either.
55923
+ */
55924
+ var PackageDocBodySchema = discriminatedUnion("status", [
55925
+ object({
55926
+ status: literal("ok"),
55927
+ markdown: string()
55928
+ }),
55929
+ object({ status: literal("missing") }),
55930
+ object({
55931
+ status: literal("error"),
55932
+ message: string()
55933
+ })
55934
+ ]);
55935
+ var PackageDocsSchema = object({
55936
+ name: string(),
55937
+ version: string(),
55938
+ readme: PackageDocBodySchema,
55939
+ changelog: PackageDocBodySchema
55940
+ });
55520
55941
  var SearchResultSchema = object({
55521
55942
  name: string(),
55522
55943
  version: string(),
@@ -55735,7 +56156,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
55735
56156
  }), object({ success: literal(true) }), {
55736
56157
  kind: "mutation",
55737
56158
  auth: "admin"
55738
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
56159
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
56160
+ name: string().min(1),
56161
+ version: string().min(1).optional()
56162
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
55739
56163
  kind: "mutation",
55740
56164
  auth: "admin"
55741
56165
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -55785,46 +56209,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
55785
56209
  }), LogStreamEntrySchema, { kind: "subscription" });
55786
56210
  object({
55787
56211
  /** Carbon dioxide concentration in ppm. */
55788
- co2Ppm: number().min(0).optional(),
56212
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
55789
56213
  /** Total volatile organic compounds in ppb. */
55790
- vocPpb: number().min(0).optional(),
56214
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
55791
56215
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
55792
- pm25: number().min(0).optional(),
56216
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
55793
56217
  /** Particulate matter ≤ 10 μm in µg/m³. */
55794
- pm10: number().min(0).optional(),
56218
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
55795
56219
  /** Composite AQI value (typically 0..500). */
55796
- aqi: number().optional(),
56220
+ aqi: number().optional().meta(readingMeta("index")),
55797
56221
  /** Ms epoch when the slice was last updated. */
55798
- lastFetchedAt: number(),
55799
- /** Live display unit of the single metric this slice carries (e.g. HA
55800
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
55801
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
55802
- * per slice is unambiguous. */
55803
- unit: string().optional(),
55804
- /** Suggested decimal places for numeric display.
55805
- * Populated live from the upstream source when provided (e.g. HA
55806
- * `attributes.suggested_display_precision`). Falls back to
55807
- * auto-formatting when absent. */
55808
- precision: number().int().min(0).max(10).optional()
56222
+ lastFetchedAt: number()
55809
56223
  });
55810
56224
  DeviceType.Sensor;
55811
56225
  object({
55812
- /** Current illuminance in lux (lx). */
55813
- lux: number().min(0).meta({
55814
- unit: "lx",
56226
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56227
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
56228
+ precision: 0,
55815
56229
  label: "Illuminance"
55816
- }),
56230
+ })),
55817
56231
  /** Ms epoch when the slice was last updated. */
55818
- lastFetchedAt: number(),
55819
- /** Live display unit from the upstream source (e.g. HA
55820
- * `attributes.unit_of_measurement`). The UI prefers this over the
55821
- * role's canonical unit. Absent → fall back to the canonical unit. */
55822
- unit: string().optional(),
55823
- /** Suggested decimal places for numeric display.
55824
- * Populated live from the upstream source when provided (e.g. HA
55825
- * `attributes.suggested_display_precision`). Falls back to
55826
- * auto-formatting when absent. */
55827
- precision: number().int().min(0).max(10).optional()
56232
+ lastFetchedAt: number()
55828
56233
  });
55829
56234
  DeviceType.Sensor;
55830
56235
  /**
@@ -55969,10 +56374,7 @@ var BatteryStatusSchema = object({
55969
56374
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
55970
56375
  * declines to band a non-finite reading, which is the correct shape.
55971
56376
  */
55972
- percentage: number().min(0).max(100).nullable().meta({
55973
- unit: "%",
55974
- label: "Charge"
55975
- }),
56377
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
55976
56378
  /**
55977
56379
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
55978
56380
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -55999,7 +56401,14 @@ var BatteryStatusSchema = object({
55999
56401
  */
56000
56402
  sleeping: boolean().meta({ label: "Sleeping" }),
56001
56403
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
56002
- lastUpdated: number(),
56404
+ lastUpdated: number().meta(readingMeta("none")),
56405
+ /**
56406
+ * Battery voltage in volts, for a cell the firmware reports as voltage
56407
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
56408
+ * that only report charge. A second reading on this cap, not a second
56409
+ * `numeric-sensor` on the same device.
56410
+ */
56411
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
56003
56412
  /**
56004
56413
  * Ms epoch of the last time the device PROVED it was reachable — a
56005
56414
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -56059,7 +56468,7 @@ object({
56059
56468
  DeviceType.Sensor;
56060
56469
  object({
56061
56470
  /** Current level as 0..100 inclusive. Firmware-reported. */
56062
- percentage: number().min(0).max(100),
56471
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
56063
56472
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
56064
56473
  lastChangedAt: number()
56065
56474
  });
@@ -56444,21 +56853,21 @@ object({
56444
56853
  /** Single setpoint in Celsius. Used by single-target modes
56445
56854
  * (heat / cool / dry). Null when the device is in a range mode
56446
56855
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
56447
- target: number().nullable(),
56856
+ target: number().nullable().meta(readingMeta("temperature")),
56448
56857
  /** Upper bound of the dual setpoint in Celsius. Populated only
56449
56858
  * in `heat_cool` mode. */
56450
- targetHigh: number().nullable(),
56859
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
56451
56860
  /** Lower bound of the dual setpoint in Celsius. Populated only
56452
56861
  * in `heat_cool` mode. */
56453
- targetLow: number().nullable(),
56862
+ targetLow: number().nullable().meta(readingMeta("temperature")),
56454
56863
  /** Target relative humidity (0..100). Null when the device has no
56455
56864
  * humidity control. */
56456
- targetHumidity: number().min(0).max(100).nullable(),
56865
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56457
56866
  /** Current measured temperature in Celsius. Read-only — pushed by
56458
56867
  * the device's internal sensor. */
56459
- currentTemp: number().nullable(),
56868
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
56460
56869
  /** Current measured relative humidity (0..100). Read-only. */
56461
- currentHumidity: number().min(0).max(100).nullable(),
56870
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56462
56871
  /** Vertical louver swing on/off. Null when the device has no vertical
56463
56872
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
56464
56873
  swingVertical: boolean().nullable(),
@@ -56466,11 +56875,11 @@ object({
56466
56875
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
56467
56876
  swingHorizontal: boolean().nullable(),
56468
56877
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
56469
- minTemp: number().nullable().optional(),
56878
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
56470
56879
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
56471
- maxTemp: number().nullable().optional(),
56880
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
56472
56881
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
56473
- tempStep: number().positive().nullable().optional(),
56882
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
56474
56883
  /** Ms epoch when the slice was last updated (push or command). */
56475
56884
  lastFetchedAt: number()
56476
56885
  });
@@ -56549,17 +56958,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
56549
56958
  * modes to expose in the picker without a slice fetch.
56550
56959
  */
56551
56960
  var RgbTripletSchema = object({
56552
- r: number().int().min(0).max(255),
56553
- g: number().int().min(0).max(255),
56554
- b: number().int().min(0).max(255)
56961
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
56962
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
56963
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
56555
56964
  });
56556
56965
  var HsvTripletSchema = object({
56557
56966
  /** Hue in degrees, 0..360. */
56558
- h: number().min(0).max(360),
56967
+ h: number().min(0).max(360).meta(readingMeta("none")),
56559
56968
  /** Saturation as 0..100 inclusive. */
56560
- s: number().min(0).max(100),
56969
+ s: number().min(0).max(100).meta(readingMeta("none")),
56561
56970
  /** Value/brightness as 0..100 inclusive. */
56562
- v: number().min(0).max(100)
56971
+ v: number().min(0).max(100).meta(readingMeta("none"))
56563
56972
  });
56564
56973
  var ColorInputSchema = discriminatedUnion("mode", [
56565
56974
  object({
@@ -56588,7 +56997,7 @@ object({
56588
56997
  /** Populated when `mode === 'hsv'`. */
56589
56998
  hsv: HsvTripletSchema.optional(),
56590
56999
  /** Populated when `mode === 'mired'`. */
56591
- mireds: number().int().optional(),
57000
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
56592
57001
  /** Ms epoch of the last operator-driven change. */
56593
57002
  lastChangedAt: number()
56594
57003
  });
@@ -56712,18 +57121,12 @@ _enum([
56712
57121
  "empty",
56713
57122
  "full"
56714
57123
  ]);
56715
- /** Units a remaining-life counter is reported in. Closed: the panel renders
56716
- * each. */
56717
- var ConsumableRemainingUnitSchema = _enum([
56718
- "days",
56719
- "hours",
56720
- "cycles"
56721
- ]);
56722
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
56723
- * days). `value` null = unknown (D393). */
57124
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
57125
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
57126
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
56724
57127
  var ConsumableRemainingSchema = object({
56725
- value: number().min(0).nullable(),
56726
- unit: ConsumableRemainingUnitSchema
57128
+ value: number().min(0).nullable().meta(readingMeta("device")),
57129
+ kind: ConsumableRemainingKindSchema
56727
57130
  });
56728
57131
  var ConsumablesStatusSchema = object({
56729
57132
  items: array(object({
@@ -56732,7 +57135,7 @@ var ConsumablesStatusSchema = object({
56732
57135
  /** Display name. */
56733
57136
  label: string().min(1),
56734
57137
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
56735
- level: number().min(0).max(100).nullable(),
57138
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
56736
57139
  /** The provider's discrete verdict when known (binary mode). */
56737
57140
  status: ConsumableStatusSchema.nullable(),
56738
57141
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -56749,7 +57152,7 @@ var ConsumablesStatusSchema = object({
56749
57152
  })),
56750
57153
  lastChangedAt: number()
56751
57154
  });
56752
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57155
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
56753
57156
  Object.values(DeviceType), method(object({
56754
57157
  deviceId: number().int().nonnegative(),
56755
57158
  key: string().min(1)
@@ -56774,39 +57177,17 @@ object({
56774
57177
  /** Current value. Numeric kind → number; select/text/datetime → string.
56775
57178
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
56776
57179
  * or time-only — HA accepts all three forms). */
56777
- value: union([number(), string()]),
57180
+ value: union([number().meta(readingMeta("device")), string()]),
56778
57181
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
56779
57182
  options: array(string()),
56780
57183
  /** Ms epoch when the slice was last updated. */
56781
57184
  lastChangedAt: number(),
56782
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
56783
- * Populated live from HA `attributes.unit_of_measurement` on each push.
56784
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
56785
- unit: string().optional(),
56786
- /** Minimum allowed value. Populated live from HA `attributes.min`.
56787
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
56788
- min: number().optional(),
56789
- /** Maximum allowed value. Populated live from HA `attributes.max`.
56790
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
56791
- max: number().optional(),
56792
- /** Step increment. Populated live from HA `attributes.step`.
56793
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
56794
- step: number().optional(),
56795
- /** Suggested decimal places for numeric display. Populated live from HA
56796
- * `attributes.suggested_display_precision`, or derived from `step` when the
56797
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
56798
- * other kinds. Falls back to auto-formatting when absent. */
56799
- precision: number().int().min(0).max(10).optional(),
56800
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
56801
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
56802
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
56803
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
56804
- * `has_date`/`has_time` attributes on each push. */
56805
- format: _enum([
56806
- "date",
56807
- "time",
56808
- "datetime"
56809
- ]).optional()
57185
+ /** Minimum allowed value, in the device quantity's base unit. */
57186
+ min: number().optional().meta(readingMeta("device")),
57187
+ /** Maximum allowed value, in the device quantity's base unit. */
57188
+ max: number().optional().meta(readingMeta("device")),
57189
+ /** Step increment, in the device quantity's base unit. */
57190
+ step: number().optional().meta(readingMeta("device"))
56810
57191
  });
56811
57192
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
56812
57193
  object({
@@ -56844,9 +57225,9 @@ object({
56844
57225
  ]),
56845
57226
  /** 0 = fully closed, 100 = fully open. Null when the device has
56846
57227
  * no intermediate position surface. */
56847
- position: number().min(0).max(100).nullable(),
57228
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
56848
57229
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
56849
- tiltPosition: number().min(0).max(100).nullable(),
57230
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
56850
57231
  /** Ms epoch when the slice was last updated. */
56851
57232
  lastChangedAt: number()
56852
57233
  });
@@ -56904,9 +57285,9 @@ var NormalizedRangeSchema$1 = object({
56904
57285
  object({
56905
57286
  mode: DayNightModeSchema,
56906
57287
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
56907
- sensitivity: number().optional(),
57288
+ sensitivity: number().optional().meta(readingMeta("none")),
56908
57289
  /** Delay before the IR-cut filter flips, in seconds. */
56909
- switchDelaySec: number().optional(),
57290
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
56910
57291
  lastFetchedAt: number()
56911
57292
  });
56912
57293
  /**
@@ -57061,11 +57442,11 @@ object({
57061
57442
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
57062
57443
  availablePrograms: array(string()),
57063
57444
  /** Seconds remaining on the active cycle, or null when not running / not known. */
57064
- remainingSeconds: number().int().nonnegative().nullable(),
57445
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57065
57446
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
57066
- delayedStartSeconds: number().int().nonnegative().nullable(),
57447
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57067
57448
  /** Cycle completion 0..100, or null when not applicable. */
57068
- progressPercent: number().min(0).max(100).nullable(),
57449
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57069
57450
  /** True when the door is reported open; null when the door state is not known. */
57070
57451
  doorOpen: boolean().nullable(),
57071
57452
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -57089,7 +57470,7 @@ object({
57089
57470
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
57090
57471
  machineCareDue: boolean().nullable(),
57091
57472
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
57092
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
57473
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
57093
57474
  /** Option states: null when the appliance does not expose the option or has not said. */
57094
57475
  halfLoad: boolean().nullable(),
57095
57476
  hygienePlus: boolean().nullable(),
@@ -57104,7 +57485,7 @@ object({
57104
57485
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
57105
57486
  activeEvents: array(string()),
57106
57487
  /** Numeric error code when the appliance reports one; null otherwise. */
57107
- errorCode: number().nullable(),
57488
+ errorCode: number().nullable().meta(readingMeta("none")),
57108
57489
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
57109
57490
  errorLabel: string().nullable(),
57110
57491
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -57250,24 +57631,14 @@ object({
57250
57631
  /** Ms epoch of the last press. null = never observed since this provider started. */
57251
57632
  lastPressedAt: number().nullable(),
57252
57633
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
57253
- pressCountSinceStart: number()
57634
+ pressCountSinceStart: number().meta(readingMeta("count"))
57254
57635
  });
57255
57636
  object({
57256
57637
  deviceId: number(),
57257
57638
  timestamp: number()
57258
57639
  });
57259
57640
  DeviceType.Button, DeviceType.Camera;
57260
- /**
57261
- * Enum-state sensor — a string value picked from a finite option set.
57262
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
57263
- * states, weather conditions, contact-source channels, mode strings).
57264
- * The option set is stable across observations; it is persisted in the
57265
- * device config blob (via the control slice at adoption) and is NOT
57266
- * stored in `sourceInfo`.
57267
- */
57268
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
57269
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
57270
- var EnumSensorDateTimeFormatSchema = _enum([
57641
+ _enum([
57271
57642
  "date",
57272
57643
  "time",
57273
57644
  "datetime"
@@ -57275,12 +57646,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
57275
57646
  object({
57276
57647
  value: string(),
57277
57648
  /**
57278
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
57279
- * as a locale date/time/datetime; absent for plain enum sensors. HA
57280
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
57281
- * a time-only sensor → `'time'`.
57649
+ * Set when `value` is a timestamp rather than an enum state, so history
57650
+ * does not learn ISO strings as states. The display shape (date / time /
57651
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
57282
57652
  */
57283
- format: EnumSensorDateTimeFormatSchema.optional(),
57653
+ timestamp: boolean().optional(),
57284
57654
  /** Ms epoch when the slice was last updated. */
57285
57655
  lastFetchedAt: number()
57286
57656
  });
@@ -57299,13 +57669,13 @@ var EventFireSchema = object({
57299
57669
  deviceId: number(),
57300
57670
  eventType: string(),
57301
57671
  data: record(string(), unknown()).nullable(),
57302
- timestamp: number(),
57303
- seq: number()
57672
+ timestamp: number().meta(readingMeta("none")),
57673
+ seq: number().meta(readingMeta("count"))
57304
57674
  });
57305
57675
  object({
57306
57676
  eventTypes: array(string()),
57307
57677
  lastEvent: EventFireSchema.nullable(),
57308
- eventCountSinceStart: number()
57678
+ eventCountSinceStart: number().meta(readingMeta("count"))
57309
57679
  });
57310
57680
  DeviceType.EventEmitter;
57311
57681
  var IdentitySchema = object({
@@ -57694,10 +58064,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
57694
58064
  object({
57695
58065
  /** Active speed as 0..100 inclusive. Null when the device has no
57696
58066
  * speed surface (single-speed fan). */
57697
- percentage: number().min(0).max(100).nullable(),
58067
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57698
58068
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
57699
58069
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
57700
- percentageStep: number().positive().optional(),
58070
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
57701
58071
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
57702
58072
  * — empty string when no preset is active or supported. */
57703
58073
  preset: string(),
@@ -57779,7 +58149,7 @@ var FeatureProbeStatusSchema = object({
57779
58149
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
57780
58150
  model: string().nullable(),
57781
58151
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
57782
- channelCount: number().nullable(),
58152
+ channelCount: number().nullable().meta(readingMeta("count")),
57783
58153
  /**
57784
58154
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
57785
58155
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -57811,9 +58181,39 @@ object({
57811
58181
  lastChangedAt: number()
57812
58182
  });
57813
58183
  DeviceType.Sensor;
58184
+ object({
58185
+ /** One row per member that has been commanded. A later command replaces that member's row. */
58186
+ results: array(object({
58187
+ deviceId: number().int().nonnegative(),
58188
+ cap: string().min(1),
58189
+ method: string().min(1),
58190
+ ok: boolean(),
58191
+ error: string().optional(),
58192
+ /** Ms epoch of the attempt. */
58193
+ at: number()
58194
+ })) });
58195
+ DeviceType.Switch, DeviceType.Light;
58196
+ object({
58197
+ /** Members whose current value entered the aggregate. */
58198
+ included: number().int().nonnegative().meta(readingMeta("count", {
58199
+ precision: 0,
58200
+ label: "Reporting"
58201
+ })),
58202
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
58203
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
58204
+ precision: 0,
58205
+ label: "Excluded"
58206
+ })),
58207
+ /** `included` + `excluded`. */
58208
+ total: number().int().nonnegative().meta(readingMeta("count", {
58209
+ precision: 0,
58210
+ label: "Members"
58211
+ }))
58212
+ });
58213
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
57814
58214
  object({
57815
58215
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
57816
- position: number().min(0).max(100).nullable(),
58216
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57817
58217
  /** Ms epoch when the slice was last updated (push or poll). */
57818
58218
  lastFetchedAt: number()
57819
58219
  });
@@ -57822,9 +58222,9 @@ object({
57822
58222
  /** Whether the humidifier is currently on. */
57823
58223
  on: boolean(),
57824
58224
  /** Current measured relative humidity (0..100). Null when not reported. */
57825
- currentHumidity: number().min(0).max(100).nullable(),
58225
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
57826
58226
  /** Target relative humidity (0..100). Null when no setpoint surface. */
57827
- targetHumidity: number().min(0).max(100).nullable(),
58227
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
57828
58228
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
57829
58229
  * device has no mode surface. */
57830
58230
  mode: string().nullable(),
@@ -57834,9 +58234,9 @@ object({
57834
58234
  * `idle` / `off`). Null when the device doesn't report it. */
57835
58235
  action: string().nullable(),
57836
58236
  /** HA `min_humidity` attribute. Null → UI uses 0. */
57837
- minHumidity: number().nullable(),
58237
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
57838
58238
  /** HA `max_humidity` attribute. Null → UI uses 100. */
57839
- maxHumidity: number().nullable(),
58239
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
57840
58240
  /** Ms epoch when the slice was last updated. */
57841
58241
  lastChangedAt: number()
57842
58242
  });
@@ -57860,22 +58260,13 @@ DeviceType.Humidifier, method(object({
57860
58260
  auth: "protected"
57861
58261
  });
57862
58262
  object({
57863
- /** Current relative humidity, 0..100. */
57864
- percent: number().min(0).max(100).meta({
57865
- unit: "%",
58263
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
58264
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
58265
+ precision: 0,
57866
58266
  label: "Humidity"
57867
- }),
58267
+ })),
57868
58268
  /** Ms epoch when the slice was last updated. */
57869
- lastFetchedAt: number(),
57870
- /** Live display unit from the upstream source (e.g. HA
57871
- * `attributes.unit_of_measurement`). The UI prefers this over the
57872
- * role's canonical unit. Absent → fall back to the canonical unit. */
57873
- unit: string().optional(),
57874
- /** Suggested decimal places for numeric display.
57875
- * Populated live from the upstream source when provided (e.g. HA
57876
- * `attributes.suggested_display_precision`). Falls back to
57877
- * auto-formatting when absent. */
57878
- precision: number().int().min(0).max(10).optional()
58269
+ lastFetchedAt: number()
57879
58270
  });
57880
58271
  DeviceType.Sensor, DeviceType.Thermostat;
57881
58272
  object({
@@ -57883,7 +58274,7 @@ object({
57883
58274
  * entity exposes no `entity_picture` (yet). */
57884
58275
  url: string().nullable(),
57885
58276
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
57886
- lastUpdated: number().nullable()
58277
+ lastUpdated: number().nullable().meta(readingMeta("none"))
57887
58278
  });
57888
58279
  DeviceType.Image;
57889
58280
  /**
@@ -57939,19 +58330,19 @@ var NormalizedRangeSchema = object({
57939
58330
  });
57940
58331
  object({
57941
58332
  /** Normalized 0–100. */
57942
- brightness: number().optional(),
58333
+ brightness: number().optional().meta(readingMeta("none")),
57943
58334
  /** Normalized 0–100. */
57944
- contrast: number().optional(),
58335
+ contrast: number().optional().meta(readingMeta("none")),
57945
58336
  /** Normalized 0–100. */
57946
- saturation: number().optional(),
58337
+ saturation: number().optional().meta(readingMeta("none")),
57947
58338
  /** Normalized 0–100. */
57948
- sharpness: number().optional(),
58339
+ sharpness: number().optional().meta(readingMeta("none")),
57949
58340
  mirror: boolean().optional(),
57950
58341
  flip: boolean().optional(),
57951
58342
  rotate: ImageRotateSchema.optional(),
57952
58343
  whiteBalance: WhiteBalanceModeSchema.optional(),
57953
58344
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
57954
- warmth: number().optional(),
58345
+ warmth: number().optional().meta(readingMeta("none")),
57955
58346
  exposureMode: ExposureModeSchema.optional(),
57956
58347
  backlightMode: BacklightModeSchema.optional(),
57957
58348
  lastFetchedAt: number()
@@ -58196,10 +58587,10 @@ var IntercomAbilitySchema = object({
58196
58587
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
58197
58588
  codecs: array(string()),
58198
58589
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
58199
- sampleRate: number(),
58590
+ sampleRate: number().meta(readingMeta("none")),
58200
58591
  duplex: _enum(["half", "full"]),
58201
58592
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
58202
- maxBacklogMs: number()
58593
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
58203
58594
  });
58204
58595
  var IntercomStatusSchema = object({
58205
58596
  /** True while a talk session is open. */
@@ -58310,13 +58701,13 @@ object({
58310
58701
  activity: LawnMowerActivitySchema,
58311
58702
  /** 0..100 battery percentage. Null when the device has no battery
58312
58703
  * reading. */
58313
- batteryLevel: number().min(0).max(100).nullable(),
58704
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58314
58705
  /** 0..100 mowing-completion percentage of the current task, or null when no
58315
58706
  * task is active / progress is unavailable. */
58316
- progressPercent: number().min(0).max(100).nullable(),
58707
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58317
58708
  /** Current device/event code (dynamic — mostly status, sometimes an error),
58318
58709
  * or null when unknown. */
58319
- currentCode: number().nullable(),
58710
+ currentCode: number().nullable().meta(readingMeta("none")),
58320
58711
  /** Human label for {@link currentCode}, or null when undecodable. */
58321
58712
  currentCodeLabel: string().nullable(),
58322
58713
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -58778,7 +59169,7 @@ object({
58778
59169
  state: MediaPlayerStateSchema,
58779
59170
  /** Volume as 0..100 inclusive. Null when the device has no volume
58780
59171
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
58781
- volumeLevel: number().min(0).max(100).nullable(),
59172
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58782
59173
  /** Mute toggle distinct from volume=0. Null when no mute surface.
58783
59174
  * Pair with `DeviceFeature.MediaPlayerMute`. */
58784
59175
  isMuted: boolean().nullable(),
@@ -58791,10 +59182,10 @@ object({
58791
59182
  currentMedia: MediaInfoSchema.nullable(),
58792
59183
  /** Current playback position in ms. Null when not seekable or
58793
59184
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
58794
- positionMs: number().int().nonnegative().nullable(),
59185
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
58795
59186
  /** Total duration of the current media in ms. Null when unknown
58796
59187
  * (live stream) or nothing is playing. */
58797
- durationMs: number().int().nonnegative().nullable(),
59188
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
58798
59189
  /** Shuffle toggle. Null when no shuffle surface.
58799
59190
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
58800
59191
  shuffle: boolean().nullable(),
@@ -59094,7 +59485,7 @@ object({
59094
59485
  * it, every provider seeded it at boot, and every session left it
59095
59486
  * `detected: true` after its detach.
59096
59487
  */
59097
- autoClearAfterMs: number().nullable()
59488
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
59098
59489
  });
59099
59490
  /**
59100
59491
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -59144,7 +59535,7 @@ var MotionZoneRegionSchema = object({
59144
59535
  });
59145
59536
  object({
59146
59537
  enabled: boolean(),
59147
- sensitivity: number(),
59538
+ sensitivity: number().meta(readingMeta("none")),
59148
59539
  /** Grid region(s). Today exactly one `grid` shape. */
59149
59540
  regions: array(MotionZoneRegionSchema),
59150
59541
  lastFetchedAt: number()
@@ -59448,7 +59839,7 @@ var NavigationStatusSchema = object({
59448
59839
  following: boolean(),
59449
59840
  flash: boolean(),
59450
59841
  lightMode: NavigationLightModeSchema,
59451
- lightLevel: number().min(40).max(100),
59842
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
59452
59843
  /** Ms epoch when the slice was last updated. */
59453
59844
  lastChangedAt: number()
59454
59845
  });
@@ -59494,13 +59885,13 @@ var NetworkLinkStatusSchema = object({
59494
59885
  * one whose reading has not landed must not be drawn at 0 %. Consumers
59495
59886
  * SKIP a null rather than coerce it.
59496
59887
  */
59497
- signalPercent: number().min(0).max(100).nullable(),
59888
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59498
59889
  /** Raw received signal strength in dBm, when the firmware reports one. */
59499
- rssiDbm: number().optional(),
59890
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
59500
59891
  /** Network name of a wireless link, when the firmware reports it. */
59501
59892
  ssid: string().optional(),
59502
59893
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
59503
- lastUpdated: number()
59894
+ lastUpdated: number().meta(readingMeta("none"))
59504
59895
  });
59505
59896
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
59506
59897
  deviceId: number(),
@@ -59819,7 +60210,7 @@ object({
59819
60210
  lastError: string().nullable(),
59820
60211
  /** Number of deliveries currently buffered server-side (rate-limited
59821
60212
  * notifiers may queue). 0 when the provider sends synchronously. */
59822
- queueDepth: number().int().nonnegative(),
60213
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
59823
60214
  /** Per-feature capability matrix — authoritative source for the
59824
60215
  * compose-form field gating. */
59825
60216
  supports: NotifierSupportsSchema
@@ -59869,15 +60260,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
59869
60260
  acceptedAt: number()
59870
60261
  });
59871
60262
  object({
59872
- value: number(),
59873
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
59874
- * Populated live from the upstream source on each state push.
59875
- * Absent for unitless sensors. */
59876
- unit: string().optional(),
59877
- /** Suggested decimal places for numeric display.
59878
- * Populated live from the upstream source when provided.
59879
- * Falls back to auto-formatting when absent. */
59880
- precision: number().int().min(0).max(10).optional(),
60263
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
60264
+ value: number().nullable().meta(readingMeta("device")),
59881
60265
  /** Ms epoch when the slice was last updated. */
59882
60266
  lastFetchedAt: number()
59883
60267
  });
@@ -60167,23 +60551,26 @@ object({
60167
60551
  /** Food currently in the bowl (grams). Null when the device has not
60168
60552
  * reported a reading yet. On dual-hopper models this is the combined
60169
60553
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
60170
- foodLevel: number().nullable(),
60554
+ foodLevel: number().nullable().meta(readingMeta("none")),
60171
60555
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
60172
60556
  * single-hopper models. */
60173
- food1: number().nullable(),
60557
+ food1: number().nullable().meta(readingMeta("none")),
60174
60558
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
60175
60559
  * single-hopper models. */
60176
- food2: number().nullable(),
60560
+ food2: number().nullable().meta(readingMeta("none")),
60177
60561
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
60178
60562
  * (`device_class: problem`, on = low). True when the bowl is empty /
60179
60563
  * below the feeder's low threshold. */
60180
60564
  lowFood: boolean(),
60181
60565
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
60182
60566
  * device has no battery reading. */
60183
- batteryPower: number().min(0).max(100).nullable(),
60184
- /** Days of desiccant life remaining. Null when the model has no
60185
- * desiccant sensor. */
60186
- desiccantLeftDays: number().nullable(),
60567
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60568
+ /**
60569
+ * Desiccant life remaining, stored in seconds (duration base, D944).
60570
+ * Providers convert from the days the feeder reports. Null when the
60571
+ * model has no desiccant sensor.
60572
+ */
60573
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
60187
60574
  /** True while a feed is in progress. */
60188
60575
  feeding: boolean(),
60189
60576
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -60194,7 +60581,7 @@ object({
60194
60581
  * with `errorCode` for consumers that want the raw integer. */
60195
60582
  error: string().nullable(),
60196
60583
  /** Raw device error code (0 / null = no error). */
60197
- errorCode: number().nullable(),
60584
+ errorCode: number().nullable().meta(readingMeta("none")),
60198
60585
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
60199
60586
  isDualHopper: boolean(),
60200
60587
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -60204,7 +60591,7 @@ object({
60204
60591
  /** Play a chime when dispensing. */
60205
60592
  feedSound: boolean(),
60206
60593
  /** Speaker / prompt volume level (device-scaled integer). */
60207
- volume: number(),
60594
+ volume: number().meta(readingMeta("none")),
60208
60595
  /** Ms epoch when the slice was last refreshed from the cloud. */
60209
60596
  lastFetchedAt: number()
60210
60597
  });
@@ -60641,36 +61028,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
60641
61028
  });
60642
61029
  object({
60643
61030
  /** Instantaneous power draw in watts. */
60644
- watts: number().optional().meta({
60645
- unit: "W",
60646
- label: "Power"
60647
- }),
61031
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
60648
61032
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
60649
- kwhTotal: number().optional().meta({
60650
- unit: "kWh",
60651
- label: "Energy"
60652
- }),
61033
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
60653
61034
  /** Voltage in volts. */
60654
- volts: number().optional(),
61035
+ volts: number().optional().meta(readingMeta("voltage")),
60655
61036
  /** Current in amperes. */
60656
- amps: number().optional(),
60657
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
60658
- va: number().optional(),
61037
+ amps: number().optional().meta(readingMeta("current")),
61038
+ /** Apparent power in volt-amperes. */
61039
+ va: number().optional().meta(readingMeta("apparent-power")),
60659
61040
  /** Power factor (0..1) if reported. */
60660
- powerFactor: number().min(0).max(1).optional(),
61041
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
60661
61042
  /** Ms epoch when the slice was last updated. */
60662
- lastFetchedAt: number(),
60663
- /** Live display unit of the single metric this slice carries (e.g. HA
60664
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
60665
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
60666
- * unit per slice is unambiguous. The UI prefers this over the role's
60667
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
60668
- unit: string().optional(),
60669
- /** Suggested decimal places for numeric display.
60670
- * Populated live from the upstream source when provided (e.g. HA
60671
- * `attributes.suggested_display_precision`). Falls back to
60672
- * auto-formatting when absent. */
60673
- precision: number().int().min(0).max(10).optional()
61043
+ lastFetchedAt: number()
60674
61044
  });
60675
61045
  DeviceType.Sensor;
60676
61046
  /**
@@ -60691,11 +61061,11 @@ DeviceType.Sensor;
60691
61061
  */
60692
61062
  var GpsLocationSchema = object({
60693
61063
  /** Latitude in decimal degrees, -90..90. */
60694
- latitude: number().min(-90).max(90),
61064
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
60695
61065
  /** Longitude in decimal degrees, -180..180. */
60696
- longitude: number().min(-180).max(180),
61066
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
60697
61067
  /** Reported accuracy in meters (lower = better). */
60698
- accuracyMeters: number().nonnegative()
61068
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
60699
61069
  });
60700
61070
  object({
60701
61071
  /** `home` / `not_home` / any user-defined zone name. */
@@ -60707,25 +61077,16 @@ object({
60707
61077
  gps: GpsLocationSchema.nullable(),
60708
61078
  /** Optional battery level of the tracking device (0..100). Null
60709
61079
  * when not reported. */
60710
- batteryPercent: number().min(0).max(100).nullable(),
61080
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60711
61081
  /** Ms epoch when the slice was last updated. */
60712
61082
  lastChangedAt: number()
60713
61083
  });
60714
61084
  DeviceType.Presence;
60715
61085
  object({
60716
- /** Current pressure in hPa. */
60717
- hpa: number(),
61086
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
61087
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
60718
61088
  /** Ms epoch when the slice was last updated. */
60719
- lastFetchedAt: number(),
60720
- /** Live display unit from the upstream source (e.g. HA
60721
- * `attributes.unit_of_measurement`). The UI prefers this over the
60722
- * role's canonical unit. Absent → fall back to the canonical unit. */
60723
- unit: string().optional(),
60724
- /** Suggested decimal places for numeric display.
60725
- * Populated live from the upstream source when provided (e.g. HA
60726
- * `attributes.suggested_display_precision`). Falls back to
60727
- * auto-formatting when absent. */
60728
- precision: number().int().min(0).max(10).optional()
61089
+ lastFetchedAt: number()
60729
61090
  });
60730
61091
  DeviceType.Sensor;
60731
61092
  /**
@@ -61058,8 +61419,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
61058
61419
  }), _void(), { kind: "mutation" });
61059
61420
  var PtzAutotrackSettingsSchema = object({
61060
61421
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
61061
- stopDelaySeconds: number().int().min(0).max(300),
61062
- disappearDelaySeconds: number().int().min(0).max(300)
61422
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
61423
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
61063
61424
  });
61064
61425
  /**
61065
61426
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -61116,7 +61477,7 @@ object({
61116
61477
  /** null = unknown — never coerced to false (D393). */
61117
61478
  running: boolean().nullable(),
61118
61479
  /** Epoch ms the current boolean began; null while `running` is null. */
61119
- since: number().nullable(),
61480
+ since: number().nullable().meta(readingMeta("none")),
61120
61481
  source: RunningStateSourceSchema.meta({ label: "Source" }),
61121
61482
  phase: string().nullable(),
61122
61483
  /** null until a reading has been published (D393). */
@@ -62314,9 +62675,9 @@ var RecordTriggerSchema = _enum([
62314
62675
  */
62315
62676
  var RecordWindowSchema = object({
62316
62677
  trigger: RecordTriggerSchema,
62317
- day: number().int().min(0).max(6),
62318
- startMinute: number().int().min(0).max(1439),
62319
- endMinute: number().int().min(1).max(1440)
62678
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
62679
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
62680
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
62320
62681
  });
62321
62682
  /** Status of one physical volume, as the camera itself describes it. */
62322
62683
  var OnboardStorageVolumeSchema = object({
@@ -62385,10 +62746,10 @@ object({
62385
62746
  /** Master "record to the card at all" switch. */
62386
62747
  enabled: boolean().nullable(),
62387
62748
  overwriteWhenFull: boolean().nullable(),
62388
- preRecordSec: number().nullable(),
62389
- postRecordSec: number().nullable(),
62749
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
62750
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
62390
62751
  /** Length of one recorded file, in minutes. */
62391
- segmentMinutes: number().nullable(),
62752
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
62392
62753
  /** The primary track's weekly windows, flattened. */
62393
62754
  windows: array(RecordWindowSchema),
62394
62755
  /**
@@ -62404,7 +62765,7 @@ object({
62404
62765
  * provider reporting a non-zero count also reports the schedule as not
62405
62766
  * writable.
62406
62767
  */
62407
- unreadableWindows: number(),
62768
+ unreadableWindows: number().meta(readingMeta("count")),
62408
62769
  /**
62409
62770
  * The camera is scheduled to record and has NO usable storage.
62410
62771
  *
@@ -63041,7 +63402,7 @@ var CamStreamDescriptorSchema = object({
63041
63402
  url: string().optional(),
63042
63403
  codec: string().optional(),
63043
63404
  resolution: CamStreamResolutionSchema.optional(),
63044
- fps: number().positive().optional(),
63405
+ fps: number().positive().optional().meta(readingMeta("rate")),
63045
63406
  label: string().optional(),
63046
63407
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
63047
63408
  deviceFeatures: array(string()).optional(),
@@ -63071,18 +63432,18 @@ var StreamProfileSchema = _enum([
63071
63432
  ]);
63072
63433
  /** The current encoding config of one stream profile. */
63073
63434
  var StreamProfileConfigSchema = object({
63074
- width: number(),
63075
- height: number(),
63435
+ width: number().meta(readingMeta("none")),
63436
+ height: number().meta(readingMeta("none")),
63076
63437
  codec: _enum(["h264", "h265"]),
63077
- framerate: number(),
63078
- bitrate: number(),
63438
+ framerate: number().meta(readingMeta("rate")),
63439
+ bitrate: number().meta(readingMeta("bitrate")),
63079
63440
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
63080
63441
  encoderProfile: _enum([
63081
63442
  "high",
63082
63443
  "main",
63083
63444
  "baseline"
63084
63445
  ]).optional(),
63085
- gop: number().optional(),
63446
+ gop: number().optional().meta(readingMeta("none")),
63086
63447
  /**
63087
63448
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
63088
63449
  *
@@ -63725,23 +64086,13 @@ object({
63725
64086
  });
63726
64087
  DeviceType.Sensor;
63727
64088
  object({
63728
- /** Current temperature in Celsius. */
63729
- celsius: number().meta({
63730
- unit: "°C",
64089
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64090
+ celsius: number().nullable().meta(readingMeta("temperature", {
64091
+ precision: 1,
63731
64092
  label: "Temperature"
63732
- }),
64093
+ })),
63733
64094
  /** Ms epoch when the slice was last updated (push or poll). */
63734
- lastFetchedAt: number(),
63735
- /** Live display unit from the upstream source (e.g. HA
63736
- * `attributes.unit_of_measurement`). The UI prefers this over the
63737
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
63738
- * Absent → the UI falls back to the role's canonical unit. */
63739
- unit: string().optional(),
63740
- /** Suggested decimal places for numeric display.
63741
- * Populated live from the upstream source when provided (e.g. HA
63742
- * `attributes.suggested_display_precision`). Falls back to
63743
- * auto-formatting when absent. */
63744
- precision: number().int().min(0).max(10).optional()
64095
+ lastFetchedAt: number()
63745
64096
  });
63746
64097
  DeviceType.Sensor, DeviceType.Thermostat;
63747
64098
  /**
@@ -63993,16 +64344,16 @@ object({
63993
64344
  ]),
63994
64345
  /** 0..100 battery percentage. Null when the device has no battery
63995
64346
  * reading. */
63996
- batteryLevel: number().min(0).max(100).nullable(),
64347
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63997
64348
  /** Current fan-speed token (provider-verbatim). Null when unknown or
63998
64349
  * the vacuum has no speed control. */
63999
64350
  fanSpeed: string().nullable(),
64000
64351
  /** Speed tokens the hardware accepts — drives the UI selector. */
64001
64352
  availableFanSpeeds: array(string()),
64002
64353
  /** 0..100 cleaning-completion percentage of the current task, or null. */
64003
- progressPercent: number().min(0).max(100).nullable(),
64354
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64004
64355
  /** Current error code (0 / null = no error). */
64005
- errorCode: number().nullable(),
64356
+ errorCode: number().nullable().meta(readingMeta("none")),
64006
64357
  /** Human label for {@link errorCode}, or null when none / undecodable. */
64007
64358
  errorLabel: string().nullable(),
64008
64359
  /** Ms epoch when the slice was last updated. */
@@ -64041,7 +64392,7 @@ object({
64041
64392
  ]),
64042
64393
  /** 0 = fully closed, 100 = fully open. Null when the device has no
64043
64394
  * intermediate position surface. */
64044
- position: number().min(0).max(100).nullable(),
64395
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64045
64396
  /** Ms epoch when the slice was last updated. */
64046
64397
  lastChangedAt: number()
64047
64398
  });
@@ -64069,9 +64420,9 @@ object({
64069
64420
  DeviceType.Sensor;
64070
64421
  object({
64071
64422
  /** Current measured temperature. Null when not reported. */
64072
- currentTemp: number().nullable(),
64423
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
64073
64424
  /** Target temperature setpoint. Null when no setpoint surface. */
64074
- targetTemp: number().nullable(),
64425
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
64075
64426
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
64076
64427
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
64077
64428
  * device reports an unknown state. */
@@ -64082,9 +64433,9 @@ object({
64082
64433
  * has no away surface. */
64083
64434
  away: boolean().nullable(),
64084
64435
  /** HA `min_temp` attribute. Null when not reported. */
64085
- minTemp: number().nullable(),
64436
+ minTemp: number().nullable().meta(readingMeta("temperature")),
64086
64437
  /** HA `max_temp` attribute. Null when not reported. */
64087
- maxTemp: number().nullable(),
64438
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
64088
64439
  /** Ms epoch when the slice was last updated. */
64089
64440
  lastChangedAt: number()
64090
64441
  });
@@ -64108,25 +64459,18 @@ DeviceType.WaterHeater, method(object({
64108
64459
  auth: "protected"
64109
64460
  });
64110
64461
  object({
64111
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
64112
- * when no condition has been reported yet. */
64462
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
64113
64463
  condition: string().nullable(),
64114
- /** Current temperature in the reported unit. Null when not provided. */
64115
- temperature: number().nullable(),
64116
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
64117
- temperatureUnit: string().nullable(),
64464
+ /** Current temperature in Celsius. Null when not provided or refused. */
64465
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
64118
64466
  /** Relative humidity (0..100). Null when not provided. */
64119
- humidity: number().min(0).max(100).nullable(),
64120
- /** Barometric pressure in the reported unit. Null when not provided. */
64121
- pressure: number().nullable(),
64122
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
64123
- pressureUnit: string().nullable(),
64124
- /** Wind speed in the reported unit. Null when not provided. */
64125
- windSpeed: number().nullable(),
64126
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
64127
- windSpeedUnit: string().nullable(),
64467
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
64468
+ /** Barometric pressure in hPa. Null when not provided or refused. */
64469
+ pressure: number().nullable().meta(readingMeta("pressure")),
64470
+ /** Wind speed in metres per second. Null when not provided or refused. */
64471
+ windSpeed: number().nullable().meta(readingMeta("speed")),
64128
64472
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
64129
- windBearing: number().nullable(),
64473
+ windBearing: number().nullable().meta(readingMeta("angle")),
64130
64474
  /** Ms epoch when the slice was last updated. */
64131
64475
  lastFetchedAt: number()
64132
64476
  });
@@ -65038,6 +65382,13 @@ Object.freeze({
65038
65382
  access: "view",
65039
65383
  role: "admin"
65040
65384
  },
65385
+ "addons.getPackageDocs": {
65386
+ capName: "addons",
65387
+ capScope: "system",
65388
+ addonId: null,
65389
+ access: "view",
65390
+ role: "admin"
65391
+ },
65041
65392
  "addons.getVersions": {
65042
65393
  capName: "addons",
65043
65394
  capScope: "system",
@@ -68146,6 +68497,20 @@ Object.freeze({
68146
68497
  access: "view",
68147
68498
  role: "viewer"
68148
68499
  },
68500
+ "groupCommands.getStatus": {
68501
+ capName: "group-commands",
68502
+ capScope: "device",
68503
+ addonId: null,
68504
+ access: "view",
68505
+ role: "viewer"
68506
+ },
68507
+ "groupStatus.getStatus": {
68508
+ capName: "group-status",
68509
+ capScope: "device",
68510
+ addonId: null,
68511
+ access: "view",
68512
+ role: "viewer"
68513
+ },
68149
68514
  "heatingValve.getStatus": {
68150
68515
  capName: "heating-valve",
68151
68516
  capScope: "device",
@@ -77968,6 +78333,37 @@ var SENSOR_CAP_VOCABULARY = {
77968
78333
  icon: "flame"
77969
78334
  }]
77970
78335
  },
78336
+ "group-commands": {
78337
+ scope: "device",
78338
+ noun: t("Group command", "Comando di gruppo")
78339
+ },
78340
+ "group-status": {
78341
+ scope: "device",
78342
+ noun: t("Group", "Gruppo"),
78343
+ state: [
78344
+ {
78345
+ kind: "numeric",
78346
+ field: "included",
78347
+ quantity: t("the members reporting", "i membri che rispondono"),
78348
+ label: t("Reporting", "Rispondono"),
78349
+ icon: "users"
78350
+ },
78351
+ {
78352
+ kind: "numeric",
78353
+ field: "excluded",
78354
+ quantity: t("the members left out", "i membri esclusi"),
78355
+ label: t("Excluded", "Esclusi"),
78356
+ icon: "users"
78357
+ },
78358
+ {
78359
+ kind: "numeric",
78360
+ field: "total",
78361
+ quantity: t("the member count", "il numero di membri"),
78362
+ label: t("Members", "Membri"),
78363
+ icon: "sigma"
78364
+ }
78365
+ ]
78366
+ },
77971
78367
  "humidity-sensor": {
77972
78368
  scope: "device",
77973
78369
  noun: t("Humidity", "Umidità"),
@@ -78001,7 +78397,6 @@ var SENSOR_CAP_VOCABULARY = {
78001
78397
  field: "value",
78002
78398
  quantity: t("the reading", "il valore"),
78003
78399
  label: t("Reading", "Valore"),
78004
- unitField: "unit",
78005
78400
  icon: "gauge"
78006
78401
  }]
78007
78402
  },
@@ -78137,7 +78532,7 @@ var SENSOR_CAP_VOCABULARY = {
78137
78532
  field: "temperature",
78138
78533
  quantity: t("the outdoor temperature", "la temperatura esterna"),
78139
78534
  label: t("Temperature", "Temperatura"),
78140
- unitField: "temperatureUnit",
78535
+ unit: "°C",
78141
78536
  icon: "thermometer"
78142
78537
  },
78143
78538
  {
@@ -78153,7 +78548,7 @@ var SENSOR_CAP_VOCABULARY = {
78153
78548
  field: "windSpeed",
78154
78549
  quantity: t("the wind speed", "la velocità del vento"),
78155
78550
  label: t("Wind", "Vento"),
78156
- unitField: "windSpeedUnit",
78551
+ unit: "m/s",
78157
78552
  icon: "wind"
78158
78553
  },
78159
78554
  {
@@ -78161,7 +78556,7 @@ var SENSOR_CAP_VOCABULARY = {
78161
78556
  field: "pressure",
78162
78557
  quantity: t("the air pressure", "la pressione atmosferica"),
78163
78558
  label: t("Pressure", "Pressione"),
78164
- unitField: "pressureUnit",
78559
+ unit: "hPa",
78165
78560
  icon: "gauge"
78166
78561
  }
78167
78562
  ]
@@ -79434,6 +79829,16 @@ Object.freeze({
79434
79829
  form: "single",
79435
79830
  optional: false
79436
79831
  }],
79832
+ "groupCommands.getStatus": [{
79833
+ name: "deviceId",
79834
+ form: "single",
79835
+ optional: false
79836
+ }],
79837
+ "groupStatus.getStatus": [{
79838
+ name: "deviceId",
79839
+ form: "single",
79840
+ optional: false
79841
+ }],
79437
79842
  "heatingValve.getStatus": [{
79438
79843
  name: "deviceId",
79439
79844
  form: "single",
@@ -81418,6 +81823,8 @@ new Map(Object.entries({
81418
81823
  "detection-blind": "device-events",
81419
81824
  "motion-noisy": "device-events",
81420
81825
  "motion-calm": "device-events",
81826
+ "package-llm-silent": "device-events",
81827
+ "package-llm-answered": "device-events",
81421
81828
  "recording-stalled": "device-events",
81422
81829
  "recording-healthy": "device-events",
81423
81830
  "recording-degraded": "device-events",
@@ -81501,6 +81908,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
81501
81908
  label: "Motion without a subject",
81502
81909
  kinds: ["motion-noisy", "motion-calm"]
81503
81910
  },
81911
+ {
81912
+ id: "package-llm",
81913
+ family: "device-events",
81914
+ label: "Package confirm",
81915
+ kinds: ["package-llm-silent", "package-llm-answered"]
81916
+ },
81504
81917
  {
81505
81918
  id: "recording",
81506
81919
  family: "device-events",
@@ -81682,6 +82095,8 @@ var DEVICE_EVENTS = [
81682
82095
  "detection-blind",
81683
82096
  "motion-noisy",
81684
82097
  "motion-calm",
82098
+ "package-llm-silent",
82099
+ "package-llm-answered",
81685
82100
  "recording-stalled",
81686
82101
  "recording-healthy",
81687
82102
  "recording-degraded",
@@ -81950,6 +82365,15 @@ new Map([
81950
82365
  snapshotLabel: "Cameras with constant motion now",
81951
82366
  snapshotSection: null
81952
82367
  },
82368
+ {
82369
+ id: "package-llm-confirm",
82370
+ family: "device-events",
82371
+ label: "Package confirm (not answering / answering again)",
82372
+ bad: ["package-llm-silent"],
82373
+ recovery: ["package-llm-answered"],
82374
+ snapshotLabel: "Cameras whose package confirm is not answering",
82375
+ snapshotSection: null
82376
+ },
81953
82377
  {
81954
82378
  id: "recording-health",
81955
82379
  family: "device-events",