@camstack/addon-auth 1.2.168 → 1.2.169

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.
@@ -1,4 +1,4 @@
1
- //#region ../types/dist/event-category-DHXxB8t9.mjs
1
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
2
2
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
3
3
  EventCategory["SystemBoot"] = "system.boot";
4
4
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -324,6 +324,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
324
324
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
325
325
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
326
326
  /**
327
+ * A camera's package LLM confirm has failed closed three times in a row
328
+ * (D931, 2026-10-26). One edge per camera until
329
+ * {@link PipelinePackageLlmAnswered}.
330
+ */
331
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
332
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
333
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
334
+ /**
327
335
  * Per-camera pipeline config was mutated by the orchestrator
328
336
  * (a settings change via `setAgentInferenceDevices` /
329
337
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5566,7 +5574,7 @@ var ZodIssueCode = {
5566
5574
  var ZodFirstPartyTypeKind;
5567
5575
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5568
5576
  //#endregion
5569
- //#region ../types/dist/sleep-BErAxhul.mjs
5577
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5570
5578
  /**
5571
5579
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5572
5580
  * window to float samples (D455).
@@ -8294,6 +8302,99 @@ function defineEntryKind(name, spec) {
8294
8302
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8295
8303
  return defined;
8296
8304
  }
8305
+ var READING_QUANTITIES = [
8306
+ "temperature",
8307
+ "pressure",
8308
+ "speed",
8309
+ "precipitation",
8310
+ "precipitation-rate",
8311
+ "length",
8312
+ "area",
8313
+ "illuminance",
8314
+ "irradiance",
8315
+ "power",
8316
+ "energy",
8317
+ "voltage",
8318
+ "current",
8319
+ "apparent-power",
8320
+ "humidity",
8321
+ "percentage",
8322
+ "concentration-volume",
8323
+ "concentration-mass",
8324
+ "concentration-ppb",
8325
+ "volume",
8326
+ "duration",
8327
+ "duration-ms",
8328
+ "information",
8329
+ "sound-level",
8330
+ "signal-level",
8331
+ "colour-temperature",
8332
+ "angle",
8333
+ "bitrate",
8334
+ "rate",
8335
+ "mass",
8336
+ "index",
8337
+ "ratio",
8338
+ "count",
8339
+ "none",
8340
+ "device"
8341
+ ];
8342
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8343
+ new Set(READING_QUANTITIES);
8344
+ /**
8345
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8346
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8347
+ */
8348
+ var QUANTITY_BASE = {
8349
+ temperature: "°C",
8350
+ pressure: "hPa",
8351
+ speed: "m/s",
8352
+ precipitation: "mm",
8353
+ "precipitation-rate": "mm/h",
8354
+ length: "m",
8355
+ area: "m²",
8356
+ illuminance: "lx",
8357
+ irradiance: "W/m²",
8358
+ power: "W",
8359
+ energy: "kWh",
8360
+ voltage: "V",
8361
+ current: "A",
8362
+ "apparent-power": "VA",
8363
+ humidity: "%",
8364
+ percentage: "%",
8365
+ "concentration-volume": "ppm",
8366
+ "concentration-mass": "µg/m³",
8367
+ "concentration-ppb": "ppb",
8368
+ volume: "L",
8369
+ duration: "s",
8370
+ "duration-ms": "ms",
8371
+ information: "B",
8372
+ "sound-level": "dBFS",
8373
+ "signal-level": "dBm",
8374
+ "colour-temperature": "mired",
8375
+ angle: "°",
8376
+ bitrate: "kbps",
8377
+ rate: "fps",
8378
+ mass: "kg",
8379
+ index: "none",
8380
+ ratio: "none",
8381
+ count: "none",
8382
+ none: "none",
8383
+ device: "device"
8384
+ };
8385
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8386
+ function baseUnitOf(quantity) {
8387
+ const symbol = QUANTITY_BASE[quantity];
8388
+ if (symbol === "none" || symbol === "device") return void 0;
8389
+ return symbol;
8390
+ }
8391
+ function readingMeta(quantity, extras) {
8392
+ return {
8393
+ quantity,
8394
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8395
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8396
+ };
8397
+ }
8297
8398
  /** Shared Zod schemas used across streaming capabilities. */
8298
8399
  var CamProfileSchema = _enum([
8299
8400
  "high",
@@ -8321,8 +8422,8 @@ var CamStreamKindSchema = _enum([
8321
8422
  "derived"
8322
8423
  ]);
8323
8424
  var CamStreamResolutionSchema = object({
8324
- width: number().int().positive(),
8325
- height: number().int().positive()
8425
+ width: number().int().positive().meta(readingMeta("none")),
8426
+ height: number().int().positive().meta(readingMeta("none"))
8326
8427
  });
8327
8428
  var CameraStreamSchema = object({
8328
8429
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8836,6 +8937,8 @@ var CAP_NAMES = Object.freeze([
8836
8937
  "filesystem-browse",
8837
8938
  "flood",
8838
8939
  "gas",
8940
+ "group-commands",
8941
+ "group-status",
8839
8942
  "heating-valve",
8840
8943
  "humidifier",
8841
8944
  "humidity-sensor",
@@ -8965,14 +9068,6 @@ Object.freeze({
8965
9068
  {
8966
9069
  "path": "aqi",
8967
9070
  "valueType": "number"
8968
- },
8969
- {
8970
- "path": "unit",
8971
- "valueType": "string"
8972
- },
8973
- {
8974
- "path": "precision",
8975
- "valueType": "number"
8976
9071
  }
8977
9072
  ],
8978
9073
  "alarm-panel": [
@@ -9008,20 +9103,10 @@ Object.freeze({
9008
9103
  "valueType": "boolean"
9009
9104
  }
9010
9105
  ],
9011
- "ambient-light-sensor": [
9012
- {
9013
- "path": "lux",
9014
- "valueType": "number"
9015
- },
9016
- {
9017
- "path": "unit",
9018
- "valueType": "string"
9019
- },
9020
- {
9021
- "path": "precision",
9022
- "valueType": "number"
9023
- }
9024
- ],
9106
+ "ambient-light-sensor": [{
9107
+ "path": "lux",
9108
+ "valueType": "number"
9109
+ }],
9025
9110
  "audio-metrics": [
9026
9111
  {
9027
9112
  "path": "ts",
@@ -9101,6 +9186,10 @@ Object.freeze({
9101
9186
  "path": "lastUpdated",
9102
9187
  "valueType": "number"
9103
9188
  },
9189
+ {
9190
+ "path": "volts",
9191
+ "valueType": "number"
9192
+ },
9104
9193
  {
9105
9194
  "path": "lastContactAt",
9106
9195
  "valueType": "number"
@@ -9343,10 +9432,6 @@ Object.freeze({
9343
9432
  "datetime"
9344
9433
  ]
9345
9434
  },
9346
- {
9347
- "path": "unit",
9348
- "valueType": "string"
9349
- },
9350
9435
  {
9351
9436
  "path": "min",
9352
9437
  "valueType": "number"
@@ -9358,19 +9443,6 @@ Object.freeze({
9358
9443
  {
9359
9444
  "path": "step",
9360
9445
  "valueType": "number"
9361
- },
9362
- {
9363
- "path": "precision",
9364
- "valueType": "number"
9365
- },
9366
- {
9367
- "path": "format",
9368
- "valueType": "enum",
9369
- "enumValues": [
9370
- "date",
9371
- "time",
9372
- "datetime"
9373
- ]
9374
9446
  }
9375
9447
  ],
9376
9448
  "cover": [
@@ -9611,13 +9683,8 @@ Object.freeze({
9611
9683
  "path": "value",
9612
9684
  "valueType": "string"
9613
9685
  }, {
9614
- "path": "format",
9615
- "valueType": "enum",
9616
- "enumValues": [
9617
- "date",
9618
- "time",
9619
- "datetime"
9620
- ]
9686
+ "path": "timestamp",
9687
+ "valueType": "boolean"
9621
9688
  }],
9622
9689
  "event-emitter": [
9623
9690
  {
@@ -9690,6 +9757,20 @@ Object.freeze({
9690
9757
  "path": "detected",
9691
9758
  "valueType": "boolean"
9692
9759
  }],
9760
+ "group-status": [
9761
+ {
9762
+ "path": "included",
9763
+ "valueType": "number"
9764
+ },
9765
+ {
9766
+ "path": "excluded",
9767
+ "valueType": "number"
9768
+ },
9769
+ {
9770
+ "path": "total",
9771
+ "valueType": "number"
9772
+ }
9773
+ ],
9693
9774
  "heating-valve": [{
9694
9775
  "path": "position",
9695
9776
  "valueType": "number"
@@ -9724,20 +9805,10 @@ Object.freeze({
9724
9805
  "valueType": "number"
9725
9806
  }
9726
9807
  ],
9727
- "humidity-sensor": [
9728
- {
9729
- "path": "percent",
9730
- "valueType": "number"
9731
- },
9732
- {
9733
- "path": "unit",
9734
- "valueType": "string"
9735
- },
9736
- {
9737
- "path": "precision",
9738
- "valueType": "number"
9739
- }
9740
- ],
9808
+ "humidity-sensor": [{
9809
+ "path": "percent",
9810
+ "valueType": "number"
9811
+ }],
9741
9812
  "image": [{
9742
9813
  "path": "url",
9743
9814
  "valueType": "string"
@@ -10076,20 +10147,10 @@ Object.freeze({
10076
10147
  "valueType": "boolean"
10077
10148
  }
10078
10149
  ],
10079
- "numeric-sensor": [
10080
- {
10081
- "path": "value",
10082
- "valueType": "number"
10083
- },
10084
- {
10085
- "path": "unit",
10086
- "valueType": "string"
10087
- },
10088
- {
10089
- "path": "precision",
10090
- "valueType": "number"
10091
- }
10092
- ],
10150
+ "numeric-sensor": [{
10151
+ "path": "value",
10152
+ "valueType": "number"
10153
+ }],
10093
10154
  "pet-feeder": [
10094
10155
  {
10095
10156
  "path": "foodLevel",
@@ -10181,14 +10242,6 @@ Object.freeze({
10181
10242
  {
10182
10243
  "path": "powerFactor",
10183
10244
  "valueType": "number"
10184
- },
10185
- {
10186
- "path": "unit",
10187
- "valueType": "string"
10188
- },
10189
- {
10190
- "path": "precision",
10191
- "valueType": "number"
10192
10245
  }
10193
10246
  ],
10194
10247
  "presence": [
@@ -10217,20 +10270,10 @@ Object.freeze({
10217
10270
  "valueType": "number"
10218
10271
  }
10219
10272
  ],
10220
- "pressure-sensor": [
10221
- {
10222
- "path": "hpa",
10223
- "valueType": "number"
10224
- },
10225
- {
10226
- "path": "unit",
10227
- "valueType": "string"
10228
- },
10229
- {
10230
- "path": "precision",
10231
- "valueType": "number"
10232
- }
10233
- ],
10273
+ "pressure-sensor": [{
10274
+ "path": "hpa",
10275
+ "valueType": "number"
10276
+ }],
10234
10277
  "privacy-mask": [{
10235
10278
  "path": "enabled",
10236
10279
  "valueType": "boolean"
@@ -10541,20 +10584,10 @@ Object.freeze({
10541
10584
  "path": "tampered",
10542
10585
  "valueType": "boolean"
10543
10586
  }],
10544
- "temperature-sensor": [
10545
- {
10546
- "path": "celsius",
10547
- "valueType": "number"
10548
- },
10549
- {
10550
- "path": "unit",
10551
- "valueType": "string"
10552
- },
10553
- {
10554
- "path": "precision",
10555
- "valueType": "number"
10556
- }
10557
- ],
10587
+ "temperature-sensor": [{
10588
+ "path": "celsius",
10589
+ "valueType": "number"
10590
+ }],
10558
10591
  "update": [
10559
10592
  {
10560
10593
  "path": "currentVersion",
@@ -10665,10 +10698,6 @@ Object.freeze({
10665
10698
  "path": "temperature",
10666
10699
  "valueType": "number"
10667
10700
  },
10668
- {
10669
- "path": "temperatureUnit",
10670
- "valueType": "string"
10671
- },
10672
10701
  {
10673
10702
  "path": "humidity",
10674
10703
  "valueType": "number"
@@ -10677,18 +10706,10 @@ Object.freeze({
10677
10706
  "path": "pressure",
10678
10707
  "valueType": "number"
10679
10708
  },
10680
- {
10681
- "path": "pressureUnit",
10682
- "valueType": "string"
10683
- },
10684
10709
  {
10685
10710
  "path": "windSpeed",
10686
10711
  "valueType": "number"
10687
10712
  },
10688
- {
10689
- "path": "windSpeedUnit",
10690
- "valueType": "string"
10691
- },
10692
10713
  {
10693
10714
  "path": "windBearing",
10694
10715
  "valueType": "number"
@@ -11091,7 +11112,7 @@ function errMsg(err) {
11091
11112
  return String(err);
11092
11113
  }
11093
11114
  //#endregion
11094
- //#region ../types/dist/composition-kEl9mevu.mjs
11115
+ //#region ../types/dist/composition-OhpuespA.mjs
11095
11116
  /**
11096
11117
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11097
11118
  *
@@ -11846,15 +11867,74 @@ function validateExpressionSource(src) {
11846
11867
  }
11847
11868
  return null;
11848
11869
  }
11849
- /** A composed device's stableId is this prefix plus the block id. */
11850
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11851
- /** Longest snippet a `code` source may carry (slice 4). */
11852
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11870
+ /**
11871
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11872
+ * id. Split out so helper schemas can name it without a cycle through
11873
+ * `composition.ts` (D659).
11874
+ */
11853
11875
  var CompositionSourceRefSchema = object({
11854
11876
  /** The owning addon; `stableId` is unique only within it. */
11855
11877
  addonId: string().min(1),
11856
11878
  stableId: string().min(1)
11857
11879
  });
11880
+ var GROUP_TEMPLATE_IDS = [
11881
+ "temperature",
11882
+ "humidity",
11883
+ "pressure",
11884
+ "illuminance",
11885
+ "power",
11886
+ "energy",
11887
+ "battery",
11888
+ "numeric",
11889
+ "motion",
11890
+ "contact",
11891
+ "leak",
11892
+ "smoke",
11893
+ "occupancy",
11894
+ "binary",
11895
+ "switch",
11896
+ "light"
11897
+ ];
11898
+ var NUMERIC_AGGREGATES = [
11899
+ "mean",
11900
+ "median",
11901
+ "min",
11902
+ "max",
11903
+ "sum"
11904
+ ];
11905
+ var BINARY_AGGREGATES = ["any", "all"];
11906
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11907
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11908
+ var AGGREGATE_REDUCERS = [
11909
+ "mean",
11910
+ "median",
11911
+ "min",
11912
+ "max",
11913
+ "sum",
11914
+ "any",
11915
+ "all"
11916
+ ];
11917
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11918
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11919
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11920
+ /** One member. The field is stored so occupancy can name motion or binary. */
11921
+ var HelperMemberSchema = object({
11922
+ source: CompositionSourceRefSchema,
11923
+ cap: string().min(1),
11924
+ fieldPath: string().min(1)
11925
+ });
11926
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
11927
+ kind: literal("group"),
11928
+ templateId: GroupTemplateIdSchema,
11929
+ aggregate: AggregateReducerSchema,
11930
+ members: array(HelperMemberSchema).min(1).max(64),
11931
+ /** Actuator groups only. Readings leave this unset. */
11932
+ active: ActiveCriterionSchema.optional()
11933
+ })]);
11934
+ /** A composed device's stableId is this prefix plus the block id. */
11935
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11936
+ /** Longest snippet a `code` source may carry (slice 4). */
11937
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11858
11938
  /** One field of one capability of one source device. */
11859
11939
  var CompositionFieldReadSchema = object({
11860
11940
  source: CompositionSourceRefSchema,
@@ -11898,7 +11978,33 @@ var CompositionCodeSourceSchema = object({
11898
11978
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11899
11979
  CompositionFromSourceSchema,
11900
11980
  CompositionExpressionSourceSchema,
11901
- CompositionCodeSourceSchema
11981
+ CompositionCodeSourceSchema,
11982
+ object({
11983
+ kind: literal("aggregate"),
11984
+ reducer: _enum([
11985
+ "mean",
11986
+ "median",
11987
+ "min",
11988
+ "max",
11989
+ "sum",
11990
+ "any",
11991
+ "all"
11992
+ ]),
11993
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
11994
+ /** `value` is the reading; the others are the member counts. */
11995
+ publish: _enum([
11996
+ "value",
11997
+ "included",
11998
+ "excluded",
11999
+ "total"
12000
+ ]),
12001
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
12002
+ empty: _enum([
12003
+ "null",
12004
+ "omit",
12005
+ "offline"
12006
+ ])
12007
+ })
11902
12008
  ]);
11903
12009
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11904
12010
  kind: literal("forward"),
@@ -11944,7 +12050,13 @@ var CompositionSchema = object({
11944
12050
  kind: literal("existing"),
11945
12051
  device: CompositionSourceRefSchema
11946
12052
  })]),
11947
- features: array(CompositionFeatureSchema).min(1).max(16)
12053
+ features: array(CompositionFeatureSchema).min(1).max(16),
12054
+ /**
12055
+ * Present when the block is a helper template. Absent on a free
12056
+ * composition (the room example, device 4603). The editor reads this,
12057
+ * never the compiled features.
12058
+ */
12059
+ helper: HelperSpecSchema.optional()
11948
12060
  }).superRefine((composition, ctx) => {
11949
12061
  const seen = /* @__PURE__ */ new Set();
11950
12062
  composition.features.forEach((feature, index) => {
@@ -18422,7 +18534,11 @@ var CompositionProblemSchema = object({
18422
18534
  "item-field-reserved",
18423
18535
  "target-missing",
18424
18536
  "target-unreadable",
18425
- "not-replaceable"
18537
+ "not-replaceable",
18538
+ "native-capability-replace",
18539
+ "mixed-quantity",
18540
+ "helper-mismatch",
18541
+ "member-constraint"
18426
18542
  ]),
18427
18543
  path: string(),
18428
18544
  message: string()
@@ -18443,7 +18559,8 @@ var CompositionUnavailableModeSchema = _enum([
18443
18559
  "null",
18444
18560
  "offline",
18445
18561
  "unknown",
18446
- "release"
18562
+ "release",
18563
+ "omit"
18447
18564
  ]);
18448
18565
  /**
18449
18566
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -18480,9 +18597,11 @@ var CompositionFieldPlanSchema = object({
18480
18597
  reads: array(CompositionFieldReadSchema)
18481
18598
  });
18482
18599
  /**
18483
- * What a feature does to its target (D663): `new` builds a capability on a new
18484
- * device; on an existing device it `add`s a capability the device lacks, or
18485
- * `replace`s named fields of one it has natively.
18600
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
18601
+ * new device; on an existing device it `add`s a capability the device lacks.
18602
+ * Replacing a capability the device already serves is refused
18603
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
18604
+ * wire for a claim the hub already holds.
18486
18605
  */
18487
18606
  var CompositionFeatureModeSchema = _enum([
18488
18607
  "new",
@@ -18655,6 +18774,275 @@ Object.freeze({
18655
18774
  result: ["number", "null"]
18656
18775
  }
18657
18776
  });
18777
+ /**
18778
+ * One catalog row per base reading a group helper can build (D944 §3).
18779
+ * Data, not a form default written into the block.
18780
+ */
18781
+ var NOW = {
18782
+ fieldPath: "lastFetchedAt",
18783
+ expr: "now"
18784
+ };
18785
+ var CHANGED = {
18786
+ fieldPath: "lastChangedAt",
18787
+ expr: "now"
18788
+ };
18789
+ function numeric(input) {
18790
+ return {
18791
+ ...input,
18792
+ family: "numeric",
18793
+ deviceType: DeviceType.Sensor,
18794
+ memberDeviceType: DeviceType.Sensor,
18795
+ commandCaps: [],
18796
+ baseUnit: baseUnitOf(input.quantity),
18797
+ aggregates: NUMERIC_AGGREGATES,
18798
+ accept: [{
18799
+ cap: input.outputCap,
18800
+ fieldPath: input.outputField
18801
+ }]
18802
+ };
18803
+ }
18804
+ function binary(input) {
18805
+ return {
18806
+ ...input,
18807
+ family: "binary",
18808
+ deviceType: DeviceType.Sensor,
18809
+ memberDeviceType: DeviceType.Sensor,
18810
+ commandCaps: [],
18811
+ quantity: "none",
18812
+ baseUnit: void 0,
18813
+ aggregates: BINARY_AGGREGATES,
18814
+ empty: "offline"
18815
+ };
18816
+ }
18817
+ var GROUP_TEMPLATES = [
18818
+ numeric({
18819
+ id: "temperature",
18820
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18821
+ role: DeviceRole.TemperatureSensor,
18822
+ outputCap: "temperature-sensor",
18823
+ outputField: "celsius",
18824
+ quantity: "temperature",
18825
+ defaultAggregate: "mean",
18826
+ empty: "null",
18827
+ companions: [NOW]
18828
+ }),
18829
+ numeric({
18830
+ id: "humidity",
18831
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18832
+ role: DeviceRole.HumiditySensor,
18833
+ outputCap: "humidity-sensor",
18834
+ outputField: "percent",
18835
+ quantity: "humidity",
18836
+ defaultAggregate: "mean",
18837
+ empty: "null",
18838
+ companions: [NOW]
18839
+ }),
18840
+ numeric({
18841
+ id: "pressure",
18842
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18843
+ role: DeviceRole.PressureSensor,
18844
+ outputCap: "pressure-sensor",
18845
+ outputField: "hpa",
18846
+ quantity: "pressure",
18847
+ defaultAggregate: "mean",
18848
+ empty: "null",
18849
+ companions: [NOW]
18850
+ }),
18851
+ numeric({
18852
+ id: "illuminance",
18853
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18854
+ role: DeviceRole.AmbientLightSensor,
18855
+ outputCap: "ambient-light-sensor",
18856
+ outputField: "lux",
18857
+ quantity: "illuminance",
18858
+ defaultAggregate: "mean",
18859
+ empty: "null",
18860
+ companions: [NOW]
18861
+ }),
18862
+ numeric({
18863
+ id: "power",
18864
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18865
+ role: DeviceRole.PowerSensor,
18866
+ outputCap: "power-meter",
18867
+ outputField: "watts",
18868
+ quantity: "power",
18869
+ defaultAggregate: "sum",
18870
+ empty: "omit",
18871
+ companions: [NOW]
18872
+ }),
18873
+ numeric({
18874
+ id: "energy",
18875
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18876
+ role: DeviceRole.EnergySensor,
18877
+ outputCap: "power-meter",
18878
+ outputField: "kwhTotal",
18879
+ quantity: "energy",
18880
+ defaultAggregate: "sum",
18881
+ empty: "omit",
18882
+ companions: [NOW]
18883
+ }),
18884
+ numeric({
18885
+ id: "battery",
18886
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18887
+ role: DeviceRole.BatterySensor,
18888
+ outputCap: "battery",
18889
+ outputField: "percentage",
18890
+ quantity: "percentage",
18891
+ defaultAggregate: "mean",
18892
+ empty: "null",
18893
+ companions: [
18894
+ {
18895
+ fieldPath: "charging",
18896
+ expr: "\"unknown\""
18897
+ },
18898
+ {
18899
+ fieldPath: "sleeping",
18900
+ expr: "false"
18901
+ },
18902
+ {
18903
+ fieldPath: "lastUpdated",
18904
+ expr: "now"
18905
+ }
18906
+ ]
18907
+ }),
18908
+ numeric({
18909
+ id: "numeric",
18910
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18911
+ role: DeviceRole.NumericSensor,
18912
+ outputCap: "numeric-sensor",
18913
+ outputField: "value",
18914
+ quantity: "device",
18915
+ defaultAggregate: "mean",
18916
+ empty: "null",
18917
+ companions: [NOW]
18918
+ }),
18919
+ binary({
18920
+ id: "motion",
18921
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18922
+ role: DeviceRole.MotionSensor,
18923
+ outputCap: "motion",
18924
+ outputField: "detected",
18925
+ defaultAggregate: "any",
18926
+ accept: [{
18927
+ cap: "motion",
18928
+ fieldPath: "detected"
18929
+ }],
18930
+ companions: [{
18931
+ fieldPath: "lastDetectedAt",
18932
+ expr: "null"
18933
+ }, {
18934
+ fieldPath: "autoClearAfterMs",
18935
+ expr: "null"
18936
+ }]
18937
+ }),
18938
+ binary({
18939
+ id: "contact",
18940
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18941
+ role: DeviceRole.ContactSensor,
18942
+ outputCap: "contact",
18943
+ outputField: "entryOpen",
18944
+ defaultAggregate: "any",
18945
+ accept: [{
18946
+ cap: "contact",
18947
+ fieldPath: "entryOpen"
18948
+ }],
18949
+ companions: [CHANGED]
18950
+ }),
18951
+ binary({
18952
+ id: "leak",
18953
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18954
+ role: DeviceRole.LeakSensor,
18955
+ outputCap: "flood",
18956
+ outputField: "flooded",
18957
+ defaultAggregate: "any",
18958
+ accept: [{
18959
+ cap: "flood",
18960
+ fieldPath: "flooded"
18961
+ }],
18962
+ companions: [CHANGED]
18963
+ }),
18964
+ binary({
18965
+ id: "smoke",
18966
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18967
+ role: DeviceRole.SmokeSensor,
18968
+ outputCap: "smoke",
18969
+ outputField: "detected",
18970
+ defaultAggregate: "any",
18971
+ accept: [{
18972
+ cap: "smoke",
18973
+ fieldPath: "detected"
18974
+ }],
18975
+ companions: [CHANGED]
18976
+ }),
18977
+ binary({
18978
+ id: "occupancy",
18979
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18980
+ role: DeviceRole.BinarySensor,
18981
+ outputCap: "binary",
18982
+ outputField: "on",
18983
+ defaultAggregate: "any",
18984
+ accept: [{
18985
+ cap: "motion",
18986
+ fieldPath: "detected"
18987
+ }, {
18988
+ cap: "binary",
18989
+ fieldPath: "on"
18990
+ }],
18991
+ companions: [CHANGED]
18992
+ }),
18993
+ binary({
18994
+ id: "binary",
18995
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18996
+ role: DeviceRole.BinarySensor,
18997
+ outputCap: "binary",
18998
+ outputField: "on",
18999
+ defaultAggregate: "any",
19000
+ accept: [{
19001
+ cap: "binary",
19002
+ fieldPath: "on"
19003
+ }],
19004
+ companions: [CHANGED]
19005
+ }),
19006
+ actuator({
19007
+ id: "switch",
19008
+ 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.",
19009
+ deviceType: DeviceType.Switch,
19010
+ outputCap: "switch",
19011
+ commandCaps: ["switch"]
19012
+ }),
19013
+ actuator({
19014
+ id: "light",
19015
+ 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.",
19016
+ deviceType: DeviceType.Light,
19017
+ outputCap: "switch",
19018
+ commandCaps: [
19019
+ "switch",
19020
+ "brightness",
19021
+ "color"
19022
+ ]
19023
+ })
19024
+ ];
19025
+ function actuator(input) {
19026
+ return {
19027
+ ...input,
19028
+ family: "actuator",
19029
+ role: void 0,
19030
+ memberDeviceType: input.deviceType,
19031
+ outputField: "on",
19032
+ quantity: "none",
19033
+ baseUnit: void 0,
19034
+ aggregates: BINARY_AGGREGATES,
19035
+ defaultAggregate: "any",
19036
+ empty: "offline",
19037
+ accept: [{
19038
+ cap: "switch",
19039
+ fieldPath: "on"
19040
+ }],
19041
+ companions: [CHANGED]
19042
+ };
19043
+ }
19044
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
19045
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18658
19046
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18659
19047
  /**
18660
19048
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -20368,12 +20756,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
20368
20756
  * providers detect renames by matching `uniqueId` and patch `id`
20369
20757
  * in-place so dispatch keeps working.
20370
20758
  *
20371
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
20372
- * `precision`) live in the cap STATUS SLICE so they flow live with every
20373
- * state push and are the single source of truth. The `device_class` tag is
20374
- * an HA-specific input that is consumed at the discovery boundary
20375
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
20376
- * stored after that point.
20759
+ * **Display unit and precision are NOT stored here.** They are a read-time
20760
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20761
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20762
+ * neither is repeated on each sample. The `device_class` tag is an
20763
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20764
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
20377
20765
  *
20378
20766
  * The `raw` escape hatch carries provider-specific attributes that don't
20379
20767
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -20387,6 +20775,14 @@ var SourceInfoSchema = object({
20387
20775
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
20388
20776
  * Used to detect rename when `id` changes. */
20389
20777
  uniqueId: string().optional(),
20778
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20779
+ quantity: ReadingQuantitySchema.optional(),
20780
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20781
+ dateTimeShape: _enum([
20782
+ "date",
20783
+ "time",
20784
+ "datetime"
20785
+ ]).optional(),
20390
20786
  /** Provider-specific extras that don't fit the structured slots.
20391
20787
  * Whitelist what goes here per provider — do NOT dump entire HA
20392
20788
  * attribute blobs (would explode the meta JSON column). */
@@ -41317,7 +41713,8 @@ method(object({
41317
41713
  "enum"
41318
41714
  ]),
41319
41715
  enumValues: array(string()).optional(),
41320
- item: boolean().optional()
41716
+ item: boolean().optional(),
41717
+ quantity: ReadingQuantitySchema.optional()
41321
41718
  })).readonly(),
41322
41719
  itemArray: object({
41323
41720
  path: string(),
@@ -45125,8 +45522,8 @@ var MaskPolygonShapeSchema = object({
45125
45522
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
45126
45523
  var MaskGridShapeSchema = object({
45127
45524
  kind: literal("grid"),
45128
- gridWidth: number(),
45129
- gridHeight: number(),
45525
+ gridWidth: number().meta(readingMeta("none")),
45526
+ gridHeight: number().meta(readingMeta("none")),
45130
45527
  cells: array(boolean())
45131
45528
  });
45132
45529
  discriminatedUnion("kind", [
@@ -45238,6 +45635,8 @@ var NcSystemEventKindSchema = _enum([
45238
45635
  "detection-blind",
45239
45636
  "motion-noisy",
45240
45637
  "motion-calm",
45638
+ "package-llm-silent",
45639
+ "package-llm-answered",
45241
45640
  "recording-stalled",
45242
45641
  "recording-healthy",
45243
45642
  "recording-degraded",
@@ -56303,6 +56702,28 @@ var PackageVersionInfoSchema = object({
56303
56702
  deprecated: string().optional(),
56304
56703
  distTags: array(string()).readonly()
56305
56704
  });
56705
+ /**
56706
+ * One published document. `ok` is markdown from the registry. `missing` means
56707
+ * the registry answered and the file was not there. `error` is a registry or
56708
+ * tarball failure — never an empty string standing in for either.
56709
+ */
56710
+ var PackageDocBodySchema = discriminatedUnion("status", [
56711
+ object({
56712
+ status: literal("ok"),
56713
+ markdown: string()
56714
+ }),
56715
+ object({ status: literal("missing") }),
56716
+ object({
56717
+ status: literal("error"),
56718
+ message: string()
56719
+ })
56720
+ ]);
56721
+ var PackageDocsSchema = object({
56722
+ name: string(),
56723
+ version: string(),
56724
+ readme: PackageDocBodySchema,
56725
+ changelog: PackageDocBodySchema
56726
+ });
56306
56727
  var SearchResultSchema = object({
56307
56728
  name: string(),
56308
56729
  version: string(),
@@ -56521,7 +56942,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56521
56942
  }), object({ success: literal(true) }), {
56522
56943
  kind: "mutation",
56523
56944
  auth: "admin"
56524
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
56945
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
56946
+ name: string().min(1),
56947
+ version: string().min(1).optional()
56948
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
56525
56949
  kind: "mutation",
56526
56950
  auth: "admin"
56527
56951
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -56571,46 +56995,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56571
56995
  }), LogStreamEntrySchema, { kind: "subscription" });
56572
56996
  object({
56573
56997
  /** Carbon dioxide concentration in ppm. */
56574
- co2Ppm: number().min(0).optional(),
56998
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
56575
56999
  /** Total volatile organic compounds in ppb. */
56576
- vocPpb: number().min(0).optional(),
57000
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56577
57001
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56578
- pm25: number().min(0).optional(),
57002
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56579
57003
  /** Particulate matter ≤ 10 μm in µg/m³. */
56580
- pm10: number().min(0).optional(),
57004
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
56581
57005
  /** Composite AQI value (typically 0..500). */
56582
- aqi: number().optional(),
57006
+ aqi: number().optional().meta(readingMeta("index")),
56583
57007
  /** Ms epoch when the slice was last updated. */
56584
- lastFetchedAt: number(),
56585
- /** Live display unit of the single metric this slice carries (e.g. HA
56586
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
56587
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
56588
- * per slice is unambiguous. */
56589
- unit: string().optional(),
56590
- /** Suggested decimal places for numeric display.
56591
- * Populated live from the upstream source when provided (e.g. HA
56592
- * `attributes.suggested_display_precision`). Falls back to
56593
- * auto-formatting when absent. */
56594
- precision: number().int().min(0).max(10).optional()
57008
+ lastFetchedAt: number()
56595
57009
  });
56596
57010
  DeviceType.Sensor;
56597
57011
  object({
56598
- /** Current illuminance in lux (lx). */
56599
- lux: number().min(0).meta({
56600
- unit: "lx",
57012
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
57013
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
57014
+ precision: 0,
56601
57015
  label: "Illuminance"
56602
- }),
57016
+ })),
56603
57017
  /** Ms epoch when the slice was last updated. */
56604
- lastFetchedAt: number(),
56605
- /** Live display unit from the upstream source (e.g. HA
56606
- * `attributes.unit_of_measurement`). The UI prefers this over the
56607
- * role's canonical unit. Absent → fall back to the canonical unit. */
56608
- unit: string().optional(),
56609
- /** Suggested decimal places for numeric display.
56610
- * Populated live from the upstream source when provided (e.g. HA
56611
- * `attributes.suggested_display_precision`). Falls back to
56612
- * auto-formatting when absent. */
56613
- precision: number().int().min(0).max(10).optional()
57018
+ lastFetchedAt: number()
56614
57019
  });
56615
57020
  DeviceType.Sensor;
56616
57021
  /**
@@ -56755,10 +57160,7 @@ var BatteryStatusSchema = object({
56755
57160
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
56756
57161
  * declines to band a non-finite reading, which is the correct shape.
56757
57162
  */
56758
- percentage: number().min(0).max(100).nullable().meta({
56759
- unit: "%",
56760
- label: "Charge"
56761
- }),
57163
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
56762
57164
  /**
56763
57165
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
56764
57166
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -56785,7 +57187,14 @@ var BatteryStatusSchema = object({
56785
57187
  */
56786
57188
  sleeping: boolean().meta({ label: "Sleeping" }),
56787
57189
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
56788
- lastUpdated: number(),
57190
+ lastUpdated: number().meta(readingMeta("none")),
57191
+ /**
57192
+ * Battery voltage in volts, for a cell the firmware reports as voltage
57193
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
57194
+ * that only report charge. A second reading on this cap, not a second
57195
+ * `numeric-sensor` on the same device.
57196
+ */
57197
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
56789
57198
  /**
56790
57199
  * Ms epoch of the last time the device PROVED it was reachable — a
56791
57200
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -56845,7 +57254,7 @@ object({
56845
57254
  DeviceType.Sensor;
56846
57255
  object({
56847
57256
  /** Current level as 0..100 inclusive. Firmware-reported. */
56848
- percentage: number().min(0).max(100),
57257
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
56849
57258
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
56850
57259
  lastChangedAt: number()
56851
57260
  });
@@ -57230,21 +57639,21 @@ object({
57230
57639
  /** Single setpoint in Celsius. Used by single-target modes
57231
57640
  * (heat / cool / dry). Null when the device is in a range mode
57232
57641
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
57233
- target: number().nullable(),
57642
+ target: number().nullable().meta(readingMeta("temperature")),
57234
57643
  /** Upper bound of the dual setpoint in Celsius. Populated only
57235
57644
  * in `heat_cool` mode. */
57236
- targetHigh: number().nullable(),
57645
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
57237
57646
  /** Lower bound of the dual setpoint in Celsius. Populated only
57238
57647
  * in `heat_cool` mode. */
57239
- targetLow: number().nullable(),
57648
+ targetLow: number().nullable().meta(readingMeta("temperature")),
57240
57649
  /** Target relative humidity (0..100). Null when the device has no
57241
57650
  * humidity control. */
57242
- targetHumidity: number().min(0).max(100).nullable(),
57651
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
57243
57652
  /** Current measured temperature in Celsius. Read-only — pushed by
57244
57653
  * the device's internal sensor. */
57245
- currentTemp: number().nullable(),
57654
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
57246
57655
  /** Current measured relative humidity (0..100). Read-only. */
57247
- currentHumidity: number().min(0).max(100).nullable(),
57656
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
57248
57657
  /** Vertical louver swing on/off. Null when the device has no vertical
57249
57658
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
57250
57659
  swingVertical: boolean().nullable(),
@@ -57252,11 +57661,11 @@ object({
57252
57661
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
57253
57662
  swingHorizontal: boolean().nullable(),
57254
57663
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
57255
- minTemp: number().nullable().optional(),
57664
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
57256
57665
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
57257
- maxTemp: number().nullable().optional(),
57666
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
57258
57667
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
57259
- tempStep: number().positive().nullable().optional(),
57668
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
57260
57669
  /** Ms epoch when the slice was last updated (push or command). */
57261
57670
  lastFetchedAt: number()
57262
57671
  });
@@ -57335,17 +57744,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
57335
57744
  * modes to expose in the picker without a slice fetch.
57336
57745
  */
57337
57746
  var RgbTripletSchema = object({
57338
- r: number().int().min(0).max(255),
57339
- g: number().int().min(0).max(255),
57340
- b: number().int().min(0).max(255)
57747
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
57748
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
57749
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
57341
57750
  });
57342
57751
  var HsvTripletSchema = object({
57343
57752
  /** Hue in degrees, 0..360. */
57344
- h: number().min(0).max(360),
57753
+ h: number().min(0).max(360).meta(readingMeta("none")),
57345
57754
  /** Saturation as 0..100 inclusive. */
57346
- s: number().min(0).max(100),
57755
+ s: number().min(0).max(100).meta(readingMeta("none")),
57347
57756
  /** Value/brightness as 0..100 inclusive. */
57348
- v: number().min(0).max(100)
57757
+ v: number().min(0).max(100).meta(readingMeta("none"))
57349
57758
  });
57350
57759
  var ColorInputSchema = discriminatedUnion("mode", [
57351
57760
  object({
@@ -57374,7 +57783,7 @@ object({
57374
57783
  /** Populated when `mode === 'hsv'`. */
57375
57784
  hsv: HsvTripletSchema.optional(),
57376
57785
  /** Populated when `mode === 'mired'`. */
57377
- mireds: number().int().optional(),
57786
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
57378
57787
  /** Ms epoch of the last operator-driven change. */
57379
57788
  lastChangedAt: number()
57380
57789
  });
@@ -57498,18 +57907,12 @@ _enum([
57498
57907
  "empty",
57499
57908
  "full"
57500
57909
  ]);
57501
- /** Units a remaining-life counter is reported in. Closed: the panel renders
57502
- * each. */
57503
- var ConsumableRemainingUnitSchema = _enum([
57504
- "days",
57505
- "hours",
57506
- "cycles"
57507
- ]);
57508
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
57509
- * days). `value` null = unknown (D393). */
57910
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
57911
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
57912
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
57510
57913
  var ConsumableRemainingSchema = object({
57511
- value: number().min(0).nullable(),
57512
- unit: ConsumableRemainingUnitSchema
57914
+ value: number().min(0).nullable().meta(readingMeta("device")),
57915
+ kind: ConsumableRemainingKindSchema
57513
57916
  });
57514
57917
  var ConsumablesStatusSchema = object({
57515
57918
  items: array(object({
@@ -57518,7 +57921,7 @@ var ConsumablesStatusSchema = object({
57518
57921
  /** Display name. */
57519
57922
  label: string().min(1),
57520
57923
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
57521
- level: number().min(0).max(100).nullable(),
57924
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57522
57925
  /** The provider's discrete verdict when known (binary mode). */
57523
57926
  status: ConsumableStatusSchema.nullable(),
57524
57927
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -57535,7 +57938,7 @@ var ConsumablesStatusSchema = object({
57535
57938
  })),
57536
57939
  lastChangedAt: number()
57537
57940
  });
57538
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57941
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57539
57942
  Object.values(DeviceType), method(object({
57540
57943
  deviceId: number().int().nonnegative(),
57541
57944
  key: string().min(1)
@@ -57560,39 +57963,17 @@ object({
57560
57963
  /** Current value. Numeric kind → number; select/text/datetime → string.
57561
57964
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
57562
57965
  * or time-only — HA accepts all three forms). */
57563
- value: union([number(), string()]),
57966
+ value: union([number().meta(readingMeta("device")), string()]),
57564
57967
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
57565
57968
  options: array(string()),
57566
57969
  /** Ms epoch when the slice was last updated. */
57567
57970
  lastChangedAt: number(),
57568
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
57569
- * Populated live from HA `attributes.unit_of_measurement` on each push.
57570
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57571
- unit: string().optional(),
57572
- /** Minimum allowed value. Populated live from HA `attributes.min`.
57573
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57574
- min: number().optional(),
57575
- /** Maximum allowed value. Populated live from HA `attributes.max`.
57576
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57577
- max: number().optional(),
57578
- /** Step increment. Populated live from HA `attributes.step`.
57579
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57580
- step: number().optional(),
57581
- /** Suggested decimal places for numeric display. Populated live from HA
57582
- * `attributes.suggested_display_precision`, or derived from `step` when the
57583
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
57584
- * other kinds. Falls back to auto-formatting when absent. */
57585
- precision: number().int().min(0).max(10).optional(),
57586
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
57587
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
57588
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
57589
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
57590
- * `has_date`/`has_time` attributes on each push. */
57591
- format: _enum([
57592
- "date",
57593
- "time",
57594
- "datetime"
57595
- ]).optional()
57971
+ /** Minimum allowed value, in the device quantity's base unit. */
57972
+ min: number().optional().meta(readingMeta("device")),
57973
+ /** Maximum allowed value, in the device quantity's base unit. */
57974
+ max: number().optional().meta(readingMeta("device")),
57975
+ /** Step increment, in the device quantity's base unit. */
57976
+ step: number().optional().meta(readingMeta("device"))
57596
57977
  });
57597
57978
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
57598
57979
  object({
@@ -57630,9 +58011,9 @@ object({
57630
58011
  ]),
57631
58012
  /** 0 = fully closed, 100 = fully open. Null when the device has
57632
58013
  * no intermediate position surface. */
57633
- position: number().min(0).max(100).nullable(),
58014
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57634
58015
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
57635
- tiltPosition: number().min(0).max(100).nullable(),
58016
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57636
58017
  /** Ms epoch when the slice was last updated. */
57637
58018
  lastChangedAt: number()
57638
58019
  });
@@ -57690,9 +58071,9 @@ var NormalizedRangeSchema$1 = object({
57690
58071
  object({
57691
58072
  mode: DayNightModeSchema,
57692
58073
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
57693
- sensitivity: number().optional(),
58074
+ sensitivity: number().optional().meta(readingMeta("none")),
57694
58075
  /** Delay before the IR-cut filter flips, in seconds. */
57695
- switchDelaySec: number().optional(),
58076
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
57696
58077
  lastFetchedAt: number()
57697
58078
  });
57698
58079
  /**
@@ -57847,11 +58228,11 @@ object({
57847
58228
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
57848
58229
  availablePrograms: array(string()),
57849
58230
  /** Seconds remaining on the active cycle, or null when not running / not known. */
57850
- remainingSeconds: number().int().nonnegative().nullable(),
58231
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57851
58232
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
57852
- delayedStartSeconds: number().int().nonnegative().nullable(),
58233
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57853
58234
  /** Cycle completion 0..100, or null when not applicable. */
57854
- progressPercent: number().min(0).max(100).nullable(),
58235
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57855
58236
  /** True when the door is reported open; null when the door state is not known. */
57856
58237
  doorOpen: boolean().nullable(),
57857
58238
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -57875,7 +58256,7 @@ object({
57875
58256
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
57876
58257
  machineCareDue: boolean().nullable(),
57877
58258
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
57878
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
58259
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
57879
58260
  /** Option states: null when the appliance does not expose the option or has not said. */
57880
58261
  halfLoad: boolean().nullable(),
57881
58262
  hygienePlus: boolean().nullable(),
@@ -57890,7 +58271,7 @@ object({
57890
58271
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
57891
58272
  activeEvents: array(string()),
57892
58273
  /** Numeric error code when the appliance reports one; null otherwise. */
57893
- errorCode: number().nullable(),
58274
+ errorCode: number().nullable().meta(readingMeta("none")),
57894
58275
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
57895
58276
  errorLabel: string().nullable(),
57896
58277
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -58036,24 +58417,14 @@ object({
58036
58417
  /** Ms epoch of the last press. null = never observed since this provider started. */
58037
58418
  lastPressedAt: number().nullable(),
58038
58419
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
58039
- pressCountSinceStart: number()
58420
+ pressCountSinceStart: number().meta(readingMeta("count"))
58040
58421
  });
58041
58422
  object({
58042
58423
  deviceId: number(),
58043
58424
  timestamp: number()
58044
58425
  });
58045
58426
  DeviceType.Button, DeviceType.Camera;
58046
- /**
58047
- * Enum-state sensor — a string value picked from a finite option set.
58048
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
58049
- * states, weather conditions, contact-source channels, mode strings).
58050
- * The option set is stable across observations; it is persisted in the
58051
- * device config blob (via the control slice at adoption) and is NOT
58052
- * stored in `sourceInfo`.
58053
- */
58054
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
58055
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
58056
- var EnumSensorDateTimeFormatSchema = _enum([
58427
+ _enum([
58057
58428
  "date",
58058
58429
  "time",
58059
58430
  "datetime"
@@ -58061,12 +58432,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
58061
58432
  object({
58062
58433
  value: string(),
58063
58434
  /**
58064
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
58065
- * as a locale date/time/datetime; absent for plain enum sensors. HA
58066
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
58067
- * a time-only sensor → `'time'`.
58435
+ * Set when `value` is a timestamp rather than an enum state, so history
58436
+ * does not learn ISO strings as states. The display shape (date / time /
58437
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
58068
58438
  */
58069
- format: EnumSensorDateTimeFormatSchema.optional(),
58439
+ timestamp: boolean().optional(),
58070
58440
  /** Ms epoch when the slice was last updated. */
58071
58441
  lastFetchedAt: number()
58072
58442
  });
@@ -58085,13 +58455,13 @@ var EventFireSchema = object({
58085
58455
  deviceId: number(),
58086
58456
  eventType: string(),
58087
58457
  data: record(string(), unknown()).nullable(),
58088
- timestamp: number(),
58089
- seq: number()
58458
+ timestamp: number().meta(readingMeta("none")),
58459
+ seq: number().meta(readingMeta("count"))
58090
58460
  });
58091
58461
  object({
58092
58462
  eventTypes: array(string()),
58093
58463
  lastEvent: EventFireSchema.nullable(),
58094
- eventCountSinceStart: number()
58464
+ eventCountSinceStart: number().meta(readingMeta("count"))
58095
58465
  });
58096
58466
  DeviceType.EventEmitter;
58097
58467
  var IdentitySchema = object({
@@ -58480,10 +58850,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
58480
58850
  object({
58481
58851
  /** Active speed as 0..100 inclusive. Null when the device has no
58482
58852
  * speed surface (single-speed fan). */
58483
- percentage: number().min(0).max(100).nullable(),
58853
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58484
58854
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
58485
58855
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
58486
- percentageStep: number().positive().optional(),
58856
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
58487
58857
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
58488
58858
  * — empty string when no preset is active or supported. */
58489
58859
  preset: string(),
@@ -58565,7 +58935,7 @@ var FeatureProbeStatusSchema = object({
58565
58935
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
58566
58936
  model: string().nullable(),
58567
58937
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
58568
- channelCount: number().nullable(),
58938
+ channelCount: number().nullable().meta(readingMeta("count")),
58569
58939
  /**
58570
58940
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
58571
58941
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -58597,9 +58967,39 @@ object({
58597
58967
  lastChangedAt: number()
58598
58968
  });
58599
58969
  DeviceType.Sensor;
58970
+ object({
58971
+ /** One row per member that has been commanded. A later command replaces that member's row. */
58972
+ results: array(object({
58973
+ deviceId: number().int().nonnegative(),
58974
+ cap: string().min(1),
58975
+ method: string().min(1),
58976
+ ok: boolean(),
58977
+ error: string().optional(),
58978
+ /** Ms epoch of the attempt. */
58979
+ at: number()
58980
+ })) });
58981
+ DeviceType.Switch, DeviceType.Light;
58982
+ object({
58983
+ /** Members whose current value entered the aggregate. */
58984
+ included: number().int().nonnegative().meta(readingMeta("count", {
58985
+ precision: 0,
58986
+ label: "Reporting"
58987
+ })),
58988
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
58989
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
58990
+ precision: 0,
58991
+ label: "Excluded"
58992
+ })),
58993
+ /** `included` + `excluded`. */
58994
+ total: number().int().nonnegative().meta(readingMeta("count", {
58995
+ precision: 0,
58996
+ label: "Members"
58997
+ }))
58998
+ });
58999
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
58600
59000
  object({
58601
59001
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
58602
- position: number().min(0).max(100).nullable(),
59002
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58603
59003
  /** Ms epoch when the slice was last updated (push or poll). */
58604
59004
  lastFetchedAt: number()
58605
59005
  });
@@ -58608,9 +59008,9 @@ object({
58608
59008
  /** Whether the humidifier is currently on. */
58609
59009
  on: boolean(),
58610
59010
  /** Current measured relative humidity (0..100). Null when not reported. */
58611
- currentHumidity: number().min(0).max(100).nullable(),
59011
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58612
59012
  /** Target relative humidity (0..100). Null when no setpoint surface. */
58613
- targetHumidity: number().min(0).max(100).nullable(),
59013
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58614
59014
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
58615
59015
  * device has no mode surface. */
58616
59016
  mode: string().nullable(),
@@ -58620,9 +59020,9 @@ object({
58620
59020
  * `idle` / `off`). Null when the device doesn't report it. */
58621
59021
  action: string().nullable(),
58622
59022
  /** HA `min_humidity` attribute. Null → UI uses 0. */
58623
- minHumidity: number().nullable(),
59023
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
58624
59024
  /** HA `max_humidity` attribute. Null → UI uses 100. */
58625
- maxHumidity: number().nullable(),
59025
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
58626
59026
  /** Ms epoch when the slice was last updated. */
58627
59027
  lastChangedAt: number()
58628
59028
  });
@@ -58646,22 +59046,13 @@ DeviceType.Humidifier, method(object({
58646
59046
  auth: "protected"
58647
59047
  });
58648
59048
  object({
58649
- /** Current relative humidity, 0..100. */
58650
- percent: number().min(0).max(100).meta({
58651
- unit: "%",
59049
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
59050
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
59051
+ precision: 0,
58652
59052
  label: "Humidity"
58653
- }),
59053
+ })),
58654
59054
  /** Ms epoch when the slice was last updated. */
58655
- lastFetchedAt: number(),
58656
- /** Live display unit from the upstream source (e.g. HA
58657
- * `attributes.unit_of_measurement`). The UI prefers this over the
58658
- * role's canonical unit. Absent → fall back to the canonical unit. */
58659
- unit: string().optional(),
58660
- /** Suggested decimal places for numeric display.
58661
- * Populated live from the upstream source when provided (e.g. HA
58662
- * `attributes.suggested_display_precision`). Falls back to
58663
- * auto-formatting when absent. */
58664
- precision: number().int().min(0).max(10).optional()
59055
+ lastFetchedAt: number()
58665
59056
  });
58666
59057
  DeviceType.Sensor, DeviceType.Thermostat;
58667
59058
  object({
@@ -58669,7 +59060,7 @@ object({
58669
59060
  * entity exposes no `entity_picture` (yet). */
58670
59061
  url: string().nullable(),
58671
59062
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
58672
- lastUpdated: number().nullable()
59063
+ lastUpdated: number().nullable().meta(readingMeta("none"))
58673
59064
  });
58674
59065
  DeviceType.Image;
58675
59066
  /**
@@ -58725,19 +59116,19 @@ var NormalizedRangeSchema = object({
58725
59116
  });
58726
59117
  object({
58727
59118
  /** Normalized 0–100. */
58728
- brightness: number().optional(),
59119
+ brightness: number().optional().meta(readingMeta("none")),
58729
59120
  /** Normalized 0–100. */
58730
- contrast: number().optional(),
59121
+ contrast: number().optional().meta(readingMeta("none")),
58731
59122
  /** Normalized 0–100. */
58732
- saturation: number().optional(),
59123
+ saturation: number().optional().meta(readingMeta("none")),
58733
59124
  /** Normalized 0–100. */
58734
- sharpness: number().optional(),
59125
+ sharpness: number().optional().meta(readingMeta("none")),
58735
59126
  mirror: boolean().optional(),
58736
59127
  flip: boolean().optional(),
58737
59128
  rotate: ImageRotateSchema.optional(),
58738
59129
  whiteBalance: WhiteBalanceModeSchema.optional(),
58739
59130
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
58740
- warmth: number().optional(),
59131
+ warmth: number().optional().meta(readingMeta("none")),
58741
59132
  exposureMode: ExposureModeSchema.optional(),
58742
59133
  backlightMode: BacklightModeSchema.optional(),
58743
59134
  lastFetchedAt: number()
@@ -58982,10 +59373,10 @@ var IntercomAbilitySchema = object({
58982
59373
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
58983
59374
  codecs: array(string()),
58984
59375
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
58985
- sampleRate: number(),
59376
+ sampleRate: number().meta(readingMeta("none")),
58986
59377
  duplex: _enum(["half", "full"]),
58987
59378
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
58988
- maxBacklogMs: number()
59379
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
58989
59380
  });
58990
59381
  var IntercomStatusSchema = object({
58991
59382
  /** True while a talk session is open. */
@@ -59096,13 +59487,13 @@ object({
59096
59487
  activity: LawnMowerActivitySchema,
59097
59488
  /** 0..100 battery percentage. Null when the device has no battery
59098
59489
  * reading. */
59099
- batteryLevel: number().min(0).max(100).nullable(),
59490
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59100
59491
  /** 0..100 mowing-completion percentage of the current task, or null when no
59101
59492
  * task is active / progress is unavailable. */
59102
- progressPercent: number().min(0).max(100).nullable(),
59493
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59103
59494
  /** Current device/event code (dynamic — mostly status, sometimes an error),
59104
59495
  * or null when unknown. */
59105
- currentCode: number().nullable(),
59496
+ currentCode: number().nullable().meta(readingMeta("none")),
59106
59497
  /** Human label for {@link currentCode}, or null when undecodable. */
59107
59498
  currentCodeLabel: string().nullable(),
59108
59499
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -59564,7 +59955,7 @@ object({
59564
59955
  state: MediaPlayerStateSchema,
59565
59956
  /** Volume as 0..100 inclusive. Null when the device has no volume
59566
59957
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59567
- volumeLevel: number().min(0).max(100).nullable(),
59958
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59568
59959
  /** Mute toggle distinct from volume=0. Null when no mute surface.
59569
59960
  * Pair with `DeviceFeature.MediaPlayerMute`. */
59570
59961
  isMuted: boolean().nullable(),
@@ -59577,10 +59968,10 @@ object({
59577
59968
  currentMedia: MediaInfoSchema.nullable(),
59578
59969
  /** Current playback position in ms. Null when not seekable or
59579
59970
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59580
- positionMs: number().int().nonnegative().nullable(),
59971
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59581
59972
  /** Total duration of the current media in ms. Null when unknown
59582
59973
  * (live stream) or nothing is playing. */
59583
- durationMs: number().int().nonnegative().nullable(),
59974
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59584
59975
  /** Shuffle toggle. Null when no shuffle surface.
59585
59976
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59586
59977
  shuffle: boolean().nullable(),
@@ -59880,7 +60271,7 @@ object({
59880
60271
  * it, every provider seeded it at boot, and every session left it
59881
60272
  * `detected: true` after its detach.
59882
60273
  */
59883
- autoClearAfterMs: number().nullable()
60274
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
59884
60275
  });
59885
60276
  /**
59886
60277
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -59930,7 +60321,7 @@ var MotionZoneRegionSchema = object({
59930
60321
  });
59931
60322
  object({
59932
60323
  enabled: boolean(),
59933
- sensitivity: number(),
60324
+ sensitivity: number().meta(readingMeta("none")),
59934
60325
  /** Grid region(s). Today exactly one `grid` shape. */
59935
60326
  regions: array(MotionZoneRegionSchema),
59936
60327
  lastFetchedAt: number()
@@ -60234,7 +60625,7 @@ var NavigationStatusSchema = object({
60234
60625
  following: boolean(),
60235
60626
  flash: boolean(),
60236
60627
  lightMode: NavigationLightModeSchema,
60237
- lightLevel: number().min(40).max(100),
60628
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
60238
60629
  /** Ms epoch when the slice was last updated. */
60239
60630
  lastChangedAt: number()
60240
60631
  });
@@ -60280,13 +60671,13 @@ var NetworkLinkStatusSchema = object({
60280
60671
  * one whose reading has not landed must not be drawn at 0 %. Consumers
60281
60672
  * SKIP a null rather than coerce it.
60282
60673
  */
60283
- signalPercent: number().min(0).max(100).nullable(),
60674
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60284
60675
  /** Raw received signal strength in dBm, when the firmware reports one. */
60285
- rssiDbm: number().optional(),
60676
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
60286
60677
  /** Network name of a wireless link, when the firmware reports it. */
60287
60678
  ssid: string().optional(),
60288
60679
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60289
- lastUpdated: number()
60680
+ lastUpdated: number().meta(readingMeta("none"))
60290
60681
  });
60291
60682
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
60292
60683
  deviceId: number(),
@@ -60605,7 +60996,7 @@ object({
60605
60996
  lastError: string().nullable(),
60606
60997
  /** Number of deliveries currently buffered server-side (rate-limited
60607
60998
  * notifiers may queue). 0 when the provider sends synchronously. */
60608
- queueDepth: number().int().nonnegative(),
60999
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
60609
61000
  /** Per-feature capability matrix — authoritative source for the
60610
61001
  * compose-form field gating. */
60611
61002
  supports: NotifierSupportsSchema
@@ -60655,15 +61046,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
60655
61046
  acceptedAt: number()
60656
61047
  });
60657
61048
  object({
60658
- value: number(),
60659
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
60660
- * Populated live from the upstream source on each state push.
60661
- * Absent for unitless sensors. */
60662
- unit: string().optional(),
60663
- /** Suggested decimal places for numeric display.
60664
- * Populated live from the upstream source when provided.
60665
- * Falls back to auto-formatting when absent. */
60666
- precision: number().int().min(0).max(10).optional(),
61049
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
61050
+ value: number().nullable().meta(readingMeta("device")),
60667
61051
  /** Ms epoch when the slice was last updated. */
60668
61052
  lastFetchedAt: number()
60669
61053
  });
@@ -60953,23 +61337,26 @@ object({
60953
61337
  /** Food currently in the bowl (grams). Null when the device has not
60954
61338
  * reported a reading yet. On dual-hopper models this is the combined
60955
61339
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
60956
- foodLevel: number().nullable(),
61340
+ foodLevel: number().nullable().meta(readingMeta("none")),
60957
61341
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
60958
61342
  * single-hopper models. */
60959
- food1: number().nullable(),
61343
+ food1: number().nullable().meta(readingMeta("none")),
60960
61344
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
60961
61345
  * single-hopper models. */
60962
- food2: number().nullable(),
61346
+ food2: number().nullable().meta(readingMeta("none")),
60963
61347
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
60964
61348
  * (`device_class: problem`, on = low). True when the bowl is empty /
60965
61349
  * below the feeder's low threshold. */
60966
61350
  lowFood: boolean(),
60967
61351
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
60968
61352
  * device has no battery reading. */
60969
- batteryPower: number().min(0).max(100).nullable(),
60970
- /** Days of desiccant life remaining. Null when the model has no
60971
- * desiccant sensor. */
60972
- desiccantLeftDays: number().nullable(),
61353
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61354
+ /**
61355
+ * Desiccant life remaining, stored in seconds (duration base, D944).
61356
+ * Providers convert from the days the feeder reports. Null when the
61357
+ * model has no desiccant sensor.
61358
+ */
61359
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
60973
61360
  /** True while a feed is in progress. */
60974
61361
  feeding: boolean(),
60975
61362
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -60980,7 +61367,7 @@ object({
60980
61367
  * with `errorCode` for consumers that want the raw integer. */
60981
61368
  error: string().nullable(),
60982
61369
  /** Raw device error code (0 / null = no error). */
60983
- errorCode: number().nullable(),
61370
+ errorCode: number().nullable().meta(readingMeta("none")),
60984
61371
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
60985
61372
  isDualHopper: boolean(),
60986
61373
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -60990,7 +61377,7 @@ object({
60990
61377
  /** Play a chime when dispensing. */
60991
61378
  feedSound: boolean(),
60992
61379
  /** Speaker / prompt volume level (device-scaled integer). */
60993
- volume: number(),
61380
+ volume: number().meta(readingMeta("none")),
60994
61381
  /** Ms epoch when the slice was last refreshed from the cloud. */
60995
61382
  lastFetchedAt: number()
60996
61383
  });
@@ -61427,36 +61814,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
61427
61814
  });
61428
61815
  object({
61429
61816
  /** Instantaneous power draw in watts. */
61430
- watts: number().optional().meta({
61431
- unit: "W",
61432
- label: "Power"
61433
- }),
61817
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
61434
61818
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61435
- kwhTotal: number().optional().meta({
61436
- unit: "kWh",
61437
- label: "Energy"
61438
- }),
61819
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61439
61820
  /** Voltage in volts. */
61440
- volts: number().optional(),
61821
+ volts: number().optional().meta(readingMeta("voltage")),
61441
61822
  /** Current in amperes. */
61442
- amps: number().optional(),
61443
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
61444
- va: number().optional(),
61823
+ amps: number().optional().meta(readingMeta("current")),
61824
+ /** Apparent power in volt-amperes. */
61825
+ va: number().optional().meta(readingMeta("apparent-power")),
61445
61826
  /** Power factor (0..1) if reported. */
61446
- powerFactor: number().min(0).max(1).optional(),
61827
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
61447
61828
  /** Ms epoch when the slice was last updated. */
61448
- lastFetchedAt: number(),
61449
- /** Live display unit of the single metric this slice carries (e.g. HA
61450
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
61451
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
61452
- * unit per slice is unambiguous. The UI prefers this over the role's
61453
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
61454
- unit: string().optional(),
61455
- /** Suggested decimal places for numeric display.
61456
- * Populated live from the upstream source when provided (e.g. HA
61457
- * `attributes.suggested_display_precision`). Falls back to
61458
- * auto-formatting when absent. */
61459
- precision: number().int().min(0).max(10).optional()
61829
+ lastFetchedAt: number()
61460
61830
  });
61461
61831
  DeviceType.Sensor;
61462
61832
  /**
@@ -61477,11 +61847,11 @@ DeviceType.Sensor;
61477
61847
  */
61478
61848
  var GpsLocationSchema = object({
61479
61849
  /** Latitude in decimal degrees, -90..90. */
61480
- latitude: number().min(-90).max(90),
61850
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
61481
61851
  /** Longitude in decimal degrees, -180..180. */
61482
- longitude: number().min(-180).max(180),
61852
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
61483
61853
  /** Reported accuracy in meters (lower = better). */
61484
- accuracyMeters: number().nonnegative()
61854
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
61485
61855
  });
61486
61856
  object({
61487
61857
  /** `home` / `not_home` / any user-defined zone name. */
@@ -61493,25 +61863,16 @@ object({
61493
61863
  gps: GpsLocationSchema.nullable(),
61494
61864
  /** Optional battery level of the tracking device (0..100). Null
61495
61865
  * when not reported. */
61496
- batteryPercent: number().min(0).max(100).nullable(),
61866
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61497
61867
  /** Ms epoch when the slice was last updated. */
61498
61868
  lastChangedAt: number()
61499
61869
  });
61500
61870
  DeviceType.Presence;
61501
61871
  object({
61502
- /** Current pressure in hPa. */
61503
- hpa: number(),
61872
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
61873
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
61504
61874
  /** Ms epoch when the slice was last updated. */
61505
- lastFetchedAt: number(),
61506
- /** Live display unit from the upstream source (e.g. HA
61507
- * `attributes.unit_of_measurement`). The UI prefers this over the
61508
- * role's canonical unit. Absent → fall back to the canonical unit. */
61509
- unit: string().optional(),
61510
- /** Suggested decimal places for numeric display.
61511
- * Populated live from the upstream source when provided (e.g. HA
61512
- * `attributes.suggested_display_precision`). Falls back to
61513
- * auto-formatting when absent. */
61514
- precision: number().int().min(0).max(10).optional()
61875
+ lastFetchedAt: number()
61515
61876
  });
61516
61877
  DeviceType.Sensor;
61517
61878
  /**
@@ -61844,8 +62205,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
61844
62205
  }), _void(), { kind: "mutation" });
61845
62206
  var PtzAutotrackSettingsSchema = object({
61846
62207
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
61847
- stopDelaySeconds: number().int().min(0).max(300),
61848
- disappearDelaySeconds: number().int().min(0).max(300)
62208
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
62209
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
61849
62210
  });
61850
62211
  /**
61851
62212
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -61902,7 +62263,7 @@ object({
61902
62263
  /** null = unknown — never coerced to false (D393). */
61903
62264
  running: boolean().nullable(),
61904
62265
  /** Epoch ms the current boolean began; null while `running` is null. */
61905
- since: number().nullable(),
62266
+ since: number().nullable().meta(readingMeta("none")),
61906
62267
  source: RunningStateSourceSchema.meta({ label: "Source" }),
61907
62268
  phase: string().nullable(),
61908
62269
  /** null until a reading has been published (D393). */
@@ -63100,9 +63461,9 @@ var RecordTriggerSchema = _enum([
63100
63461
  */
63101
63462
  var RecordWindowSchema = object({
63102
63463
  trigger: RecordTriggerSchema,
63103
- day: number().int().min(0).max(6),
63104
- startMinute: number().int().min(0).max(1439),
63105
- endMinute: number().int().min(1).max(1440)
63464
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
63465
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
63466
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
63106
63467
  });
63107
63468
  /** Status of one physical volume, as the camera itself describes it. */
63108
63469
  var OnboardStorageVolumeSchema = object({
@@ -63171,10 +63532,10 @@ object({
63171
63532
  /** Master "record to the card at all" switch. */
63172
63533
  enabled: boolean().nullable(),
63173
63534
  overwriteWhenFull: boolean().nullable(),
63174
- preRecordSec: number().nullable(),
63175
- postRecordSec: number().nullable(),
63535
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
63536
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
63176
63537
  /** Length of one recorded file, in minutes. */
63177
- segmentMinutes: number().nullable(),
63538
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
63178
63539
  /** The primary track's weekly windows, flattened. */
63179
63540
  windows: array(RecordWindowSchema),
63180
63541
  /**
@@ -63190,7 +63551,7 @@ object({
63190
63551
  * provider reporting a non-zero count also reports the schedule as not
63191
63552
  * writable.
63192
63553
  */
63193
- unreadableWindows: number(),
63554
+ unreadableWindows: number().meta(readingMeta("count")),
63194
63555
  /**
63195
63556
  * The camera is scheduled to record and has NO usable storage.
63196
63557
  *
@@ -63827,7 +64188,7 @@ var CamStreamDescriptorSchema = object({
63827
64188
  url: string().optional(),
63828
64189
  codec: string().optional(),
63829
64190
  resolution: CamStreamResolutionSchema.optional(),
63830
- fps: number().positive().optional(),
64191
+ fps: number().positive().optional().meta(readingMeta("rate")),
63831
64192
  label: string().optional(),
63832
64193
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
63833
64194
  deviceFeatures: array(string()).optional(),
@@ -63857,18 +64218,18 @@ var StreamProfileSchema = _enum([
63857
64218
  ]);
63858
64219
  /** The current encoding config of one stream profile. */
63859
64220
  var StreamProfileConfigSchema = object({
63860
- width: number(),
63861
- height: number(),
64221
+ width: number().meta(readingMeta("none")),
64222
+ height: number().meta(readingMeta("none")),
63862
64223
  codec: _enum(["h264", "h265"]),
63863
- framerate: number(),
63864
- bitrate: number(),
64224
+ framerate: number().meta(readingMeta("rate")),
64225
+ bitrate: number().meta(readingMeta("bitrate")),
63865
64226
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
63866
64227
  encoderProfile: _enum([
63867
64228
  "high",
63868
64229
  "main",
63869
64230
  "baseline"
63870
64231
  ]).optional(),
63871
- gop: number().optional(),
64232
+ gop: number().optional().meta(readingMeta("none")),
63872
64233
  /**
63873
64234
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
63874
64235
  *
@@ -64511,23 +64872,13 @@ object({
64511
64872
  });
64512
64873
  DeviceType.Sensor;
64513
64874
  object({
64514
- /** Current temperature in Celsius. */
64515
- celsius: number().meta({
64516
- unit: "°C",
64875
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64876
+ celsius: number().nullable().meta(readingMeta("temperature", {
64877
+ precision: 1,
64517
64878
  label: "Temperature"
64518
- }),
64879
+ })),
64519
64880
  /** Ms epoch when the slice was last updated (push or poll). */
64520
- lastFetchedAt: number(),
64521
- /** Live display unit from the upstream source (e.g. HA
64522
- * `attributes.unit_of_measurement`). The UI prefers this over the
64523
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
64524
- * Absent → the UI falls back to the role's canonical unit. */
64525
- unit: string().optional(),
64526
- /** Suggested decimal places for numeric display.
64527
- * Populated live from the upstream source when provided (e.g. HA
64528
- * `attributes.suggested_display_precision`). Falls back to
64529
- * auto-formatting when absent. */
64530
- precision: number().int().min(0).max(10).optional()
64881
+ lastFetchedAt: number()
64531
64882
  });
64532
64883
  DeviceType.Sensor, DeviceType.Thermostat;
64533
64884
  /**
@@ -64779,16 +65130,16 @@ object({
64779
65130
  ]),
64780
65131
  /** 0..100 battery percentage. Null when the device has no battery
64781
65132
  * reading. */
64782
- batteryLevel: number().min(0).max(100).nullable(),
65133
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64783
65134
  /** Current fan-speed token (provider-verbatim). Null when unknown or
64784
65135
  * the vacuum has no speed control. */
64785
65136
  fanSpeed: string().nullable(),
64786
65137
  /** Speed tokens the hardware accepts — drives the UI selector. */
64787
65138
  availableFanSpeeds: array(string()),
64788
65139
  /** 0..100 cleaning-completion percentage of the current task, or null. */
64789
- progressPercent: number().min(0).max(100).nullable(),
65140
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64790
65141
  /** Current error code (0 / null = no error). */
64791
- errorCode: number().nullable(),
65142
+ errorCode: number().nullable().meta(readingMeta("none")),
64792
65143
  /** Human label for {@link errorCode}, or null when none / undecodable. */
64793
65144
  errorLabel: string().nullable(),
64794
65145
  /** Ms epoch when the slice was last updated. */
@@ -64827,7 +65178,7 @@ object({
64827
65178
  ]),
64828
65179
  /** 0 = fully closed, 100 = fully open. Null when the device has no
64829
65180
  * intermediate position surface. */
64830
- position: number().min(0).max(100).nullable(),
65181
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64831
65182
  /** Ms epoch when the slice was last updated. */
64832
65183
  lastChangedAt: number()
64833
65184
  });
@@ -64855,9 +65206,9 @@ object({
64855
65206
  DeviceType.Sensor;
64856
65207
  object({
64857
65208
  /** Current measured temperature. Null when not reported. */
64858
- currentTemp: number().nullable(),
65209
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
64859
65210
  /** Target temperature setpoint. Null when no setpoint surface. */
64860
- targetTemp: number().nullable(),
65211
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
64861
65212
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
64862
65213
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
64863
65214
  * device reports an unknown state. */
@@ -64868,9 +65219,9 @@ object({
64868
65219
  * has no away surface. */
64869
65220
  away: boolean().nullable(),
64870
65221
  /** HA `min_temp` attribute. Null when not reported. */
64871
- minTemp: number().nullable(),
65222
+ minTemp: number().nullable().meta(readingMeta("temperature")),
64872
65223
  /** HA `max_temp` attribute. Null when not reported. */
64873
- maxTemp: number().nullable(),
65224
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
64874
65225
  /** Ms epoch when the slice was last updated. */
64875
65226
  lastChangedAt: number()
64876
65227
  });
@@ -64894,25 +65245,18 @@ DeviceType.WaterHeater, method(object({
64894
65245
  auth: "protected"
64895
65246
  });
64896
65247
  object({
64897
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
64898
- * when no condition has been reported yet. */
65248
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
64899
65249
  condition: string().nullable(),
64900
- /** Current temperature in the reported unit. Null when not provided. */
64901
- temperature: number().nullable(),
64902
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
64903
- temperatureUnit: string().nullable(),
65250
+ /** Current temperature in Celsius. Null when not provided or refused. */
65251
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
64904
65252
  /** Relative humidity (0..100). Null when not provided. */
64905
- humidity: number().min(0).max(100).nullable(),
64906
- /** Barometric pressure in the reported unit. Null when not provided. */
64907
- pressure: number().nullable(),
64908
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
64909
- pressureUnit: string().nullable(),
64910
- /** Wind speed in the reported unit. Null when not provided. */
64911
- windSpeed: number().nullable(),
64912
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
64913
- windSpeedUnit: string().nullable(),
65253
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
65254
+ /** Barometric pressure in hPa. Null when not provided or refused. */
65255
+ pressure: number().nullable().meta(readingMeta("pressure")),
65256
+ /** Wind speed in metres per second. Null when not provided or refused. */
65257
+ windSpeed: number().nullable().meta(readingMeta("speed")),
64914
65258
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
64915
- windBearing: number().nullable(),
65259
+ windBearing: number().nullable().meta(readingMeta("angle")),
64916
65260
  /** Ms epoch when the slice was last updated. */
64917
65261
  lastFetchedAt: number()
64918
65262
  });
@@ -65824,6 +66168,13 @@ Object.freeze({
65824
66168
  access: "view",
65825
66169
  role: "admin"
65826
66170
  },
66171
+ "addons.getPackageDocs": {
66172
+ capName: "addons",
66173
+ capScope: "system",
66174
+ addonId: null,
66175
+ access: "view",
66176
+ role: "admin"
66177
+ },
65827
66178
  "addons.getVersions": {
65828
66179
  capName: "addons",
65829
66180
  capScope: "system",
@@ -68932,6 +69283,20 @@ Object.freeze({
68932
69283
  access: "view",
68933
69284
  role: "viewer"
68934
69285
  },
69286
+ "groupCommands.getStatus": {
69287
+ capName: "group-commands",
69288
+ capScope: "device",
69289
+ addonId: null,
69290
+ access: "view",
69291
+ role: "viewer"
69292
+ },
69293
+ "groupStatus.getStatus": {
69294
+ capName: "group-status",
69295
+ capScope: "device",
69296
+ addonId: null,
69297
+ access: "view",
69298
+ role: "viewer"
69299
+ },
68935
69300
  "heatingValve.getStatus": {
68936
69301
  capName: "heating-valve",
68937
69302
  capScope: "device",
@@ -78754,6 +79119,37 @@ var SENSOR_CAP_VOCABULARY = {
78754
79119
  icon: "flame"
78755
79120
  }]
78756
79121
  },
79122
+ "group-commands": {
79123
+ scope: "device",
79124
+ noun: t("Group command", "Comando di gruppo")
79125
+ },
79126
+ "group-status": {
79127
+ scope: "device",
79128
+ noun: t("Group", "Gruppo"),
79129
+ state: [
79130
+ {
79131
+ kind: "numeric",
79132
+ field: "included",
79133
+ quantity: t("the members reporting", "i membri che rispondono"),
79134
+ label: t("Reporting", "Rispondono"),
79135
+ icon: "users"
79136
+ },
79137
+ {
79138
+ kind: "numeric",
79139
+ field: "excluded",
79140
+ quantity: t("the members left out", "i membri esclusi"),
79141
+ label: t("Excluded", "Esclusi"),
79142
+ icon: "users"
79143
+ },
79144
+ {
79145
+ kind: "numeric",
79146
+ field: "total",
79147
+ quantity: t("the member count", "il numero di membri"),
79148
+ label: t("Members", "Membri"),
79149
+ icon: "sigma"
79150
+ }
79151
+ ]
79152
+ },
78757
79153
  "humidity-sensor": {
78758
79154
  scope: "device",
78759
79155
  noun: t("Humidity", "Umidità"),
@@ -78787,7 +79183,6 @@ var SENSOR_CAP_VOCABULARY = {
78787
79183
  field: "value",
78788
79184
  quantity: t("the reading", "il valore"),
78789
79185
  label: t("Reading", "Valore"),
78790
- unitField: "unit",
78791
79186
  icon: "gauge"
78792
79187
  }]
78793
79188
  },
@@ -78923,7 +79318,7 @@ var SENSOR_CAP_VOCABULARY = {
78923
79318
  field: "temperature",
78924
79319
  quantity: t("the outdoor temperature", "la temperatura esterna"),
78925
79320
  label: t("Temperature", "Temperatura"),
78926
- unitField: "temperatureUnit",
79321
+ unit: "°C",
78927
79322
  icon: "thermometer"
78928
79323
  },
78929
79324
  {
@@ -78939,7 +79334,7 @@ var SENSOR_CAP_VOCABULARY = {
78939
79334
  field: "windSpeed",
78940
79335
  quantity: t("the wind speed", "la velocità del vento"),
78941
79336
  label: t("Wind", "Vento"),
78942
- unitField: "windSpeedUnit",
79337
+ unit: "m/s",
78943
79338
  icon: "wind"
78944
79339
  },
78945
79340
  {
@@ -78947,7 +79342,7 @@ var SENSOR_CAP_VOCABULARY = {
78947
79342
  field: "pressure",
78948
79343
  quantity: t("the air pressure", "la pressione atmosferica"),
78949
79344
  label: t("Pressure", "Pressione"),
78950
- unitField: "pressureUnit",
79345
+ unit: "hPa",
78951
79346
  icon: "gauge"
78952
79347
  }
78953
79348
  ]
@@ -80220,6 +80615,16 @@ Object.freeze({
80220
80615
  form: "single",
80221
80616
  optional: false
80222
80617
  }],
80618
+ "groupCommands.getStatus": [{
80619
+ name: "deviceId",
80620
+ form: "single",
80621
+ optional: false
80622
+ }],
80623
+ "groupStatus.getStatus": [{
80624
+ name: "deviceId",
80625
+ form: "single",
80626
+ optional: false
80627
+ }],
80223
80628
  "heatingValve.getStatus": [{
80224
80629
  name: "deviceId",
80225
80630
  form: "single",
@@ -82204,6 +82609,8 @@ new Map(Object.entries({
82204
82609
  "detection-blind": "device-events",
82205
82610
  "motion-noisy": "device-events",
82206
82611
  "motion-calm": "device-events",
82612
+ "package-llm-silent": "device-events",
82613
+ "package-llm-answered": "device-events",
82207
82614
  "recording-stalled": "device-events",
82208
82615
  "recording-healthy": "device-events",
82209
82616
  "recording-degraded": "device-events",
@@ -82287,6 +82694,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
82287
82694
  label: "Motion without a subject",
82288
82695
  kinds: ["motion-noisy", "motion-calm"]
82289
82696
  },
82697
+ {
82698
+ id: "package-llm",
82699
+ family: "device-events",
82700
+ label: "Package confirm",
82701
+ kinds: ["package-llm-silent", "package-llm-answered"]
82702
+ },
82290
82703
  {
82291
82704
  id: "recording",
82292
82705
  family: "device-events",
@@ -82468,6 +82881,8 @@ var DEVICE_EVENTS = [
82468
82881
  "detection-blind",
82469
82882
  "motion-noisy",
82470
82883
  "motion-calm",
82884
+ "package-llm-silent",
82885
+ "package-llm-answered",
82471
82886
  "recording-stalled",
82472
82887
  "recording-healthy",
82473
82888
  "recording-degraded",
@@ -82736,6 +83151,15 @@ new Map([
82736
83151
  snapshotLabel: "Cameras with constant motion now",
82737
83152
  snapshotSection: null
82738
83153
  },
83154
+ {
83155
+ id: "package-llm-confirm",
83156
+ family: "device-events",
83157
+ label: "Package confirm (not answering / answering again)",
83158
+ bad: ["package-llm-silent"],
83159
+ recovery: ["package-llm-answered"],
83160
+ snapshotLabel: "Cameras whose package confirm is not answering",
83161
+ snapshotSection: null
83162
+ },
82739
83163
  {
82740
83164
  id: "recording-health",
82741
83165
  family: "device-events",