@camstack/addon-notifiers 1.2.620 → 1.2.622

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -29,7 +29,7 @@ let node_fs = require("node:fs");
29
29
  let node_path = require("node:path");
30
30
  node_path = __toESM(node_path);
31
31
  let node_url = require("node:url");
32
- //#region ../types/dist/event-category-DHXxB8t9.mjs
32
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
33
33
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
34
34
  EventCategory["SystemBoot"] = "system.boot";
35
35
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -355,6 +355,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
355
355
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
356
356
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
357
357
  /**
358
+ * A camera's package LLM confirm has failed closed three times in a row
359
+ * (D931, 2026-10-26). One edge per camera until
360
+ * {@link PipelinePackageLlmAnswered}.
361
+ */
362
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
363
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
364
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
365
+ /**
358
366
  * Per-camera pipeline config was mutated by the orchestrator
359
367
  * (a settings change via `setAgentInferenceDevices` /
360
368
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5597,7 +5605,7 @@ var ZodIssueCode = {
5597
5605
  var ZodFirstPartyTypeKind;
5598
5606
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5599
5607
  //#endregion
5600
- //#region ../types/dist/sleep-BErAxhul.mjs
5608
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5601
5609
  /**
5602
5610
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5603
5611
  * window to float samples (D455).
@@ -8297,6 +8305,99 @@ function defineRecordKinds(specs) {
8297
8305
  }
8298
8306
  return out;
8299
8307
  }
8308
+ var READING_QUANTITIES = [
8309
+ "temperature",
8310
+ "pressure",
8311
+ "speed",
8312
+ "precipitation",
8313
+ "precipitation-rate",
8314
+ "length",
8315
+ "area",
8316
+ "illuminance",
8317
+ "irradiance",
8318
+ "power",
8319
+ "energy",
8320
+ "voltage",
8321
+ "current",
8322
+ "apparent-power",
8323
+ "humidity",
8324
+ "percentage",
8325
+ "concentration-volume",
8326
+ "concentration-mass",
8327
+ "concentration-ppb",
8328
+ "volume",
8329
+ "duration",
8330
+ "duration-ms",
8331
+ "information",
8332
+ "sound-level",
8333
+ "signal-level",
8334
+ "colour-temperature",
8335
+ "angle",
8336
+ "bitrate",
8337
+ "rate",
8338
+ "mass",
8339
+ "index",
8340
+ "ratio",
8341
+ "count",
8342
+ "none",
8343
+ "device"
8344
+ ];
8345
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8346
+ new Set(READING_QUANTITIES);
8347
+ /**
8348
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8349
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8350
+ */
8351
+ var QUANTITY_BASE = {
8352
+ temperature: "°C",
8353
+ pressure: "hPa",
8354
+ speed: "m/s",
8355
+ precipitation: "mm",
8356
+ "precipitation-rate": "mm/h",
8357
+ length: "m",
8358
+ area: "m²",
8359
+ illuminance: "lx",
8360
+ irradiance: "W/m²",
8361
+ power: "W",
8362
+ energy: "kWh",
8363
+ voltage: "V",
8364
+ current: "A",
8365
+ "apparent-power": "VA",
8366
+ humidity: "%",
8367
+ percentage: "%",
8368
+ "concentration-volume": "ppm",
8369
+ "concentration-mass": "µg/m³",
8370
+ "concentration-ppb": "ppb",
8371
+ volume: "L",
8372
+ duration: "s",
8373
+ "duration-ms": "ms",
8374
+ information: "B",
8375
+ "sound-level": "dBFS",
8376
+ "signal-level": "dBm",
8377
+ "colour-temperature": "mired",
8378
+ angle: "°",
8379
+ bitrate: "kbps",
8380
+ rate: "fps",
8381
+ mass: "kg",
8382
+ index: "none",
8383
+ ratio: "none",
8384
+ count: "none",
8385
+ none: "none",
8386
+ device: "device"
8387
+ };
8388
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8389
+ function baseUnitOf(quantity) {
8390
+ const symbol = QUANTITY_BASE[quantity];
8391
+ if (symbol === "none" || symbol === "device") return void 0;
8392
+ return symbol;
8393
+ }
8394
+ function readingMeta(quantity, extras) {
8395
+ return {
8396
+ quantity,
8397
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8398
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8399
+ };
8400
+ }
8300
8401
  /** Shared Zod schemas used across streaming capabilities. */
8301
8402
  var CamProfileSchema = _enum([
8302
8403
  "high",
@@ -8324,8 +8425,8 @@ var CamStreamKindSchema = _enum([
8324
8425
  "derived"
8325
8426
  ]);
8326
8427
  var CamStreamResolutionSchema = object({
8327
- width: number().int().positive(),
8328
- height: number().int().positive()
8428
+ width: number().int().positive().meta(readingMeta("none")),
8429
+ height: number().int().positive().meta(readingMeta("none"))
8329
8430
  });
8330
8431
  var CameraStreamSchema = object({
8331
8432
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8861,6 +8962,8 @@ var CAP_NAMES = Object.freeze([
8861
8962
  "filesystem-browse",
8862
8963
  "flood",
8863
8964
  "gas",
8965
+ "group-commands",
8966
+ "group-status",
8864
8967
  "heating-valve",
8865
8968
  "humidifier",
8866
8969
  "humidity-sensor",
@@ -8990,14 +9093,6 @@ Object.freeze({
8990
9093
  {
8991
9094
  "path": "aqi",
8992
9095
  "valueType": "number"
8993
- },
8994
- {
8995
- "path": "unit",
8996
- "valueType": "string"
8997
- },
8998
- {
8999
- "path": "precision",
9000
- "valueType": "number"
9001
9096
  }
9002
9097
  ],
9003
9098
  "alarm-panel": [
@@ -9033,20 +9128,10 @@ Object.freeze({
9033
9128
  "valueType": "boolean"
9034
9129
  }
9035
9130
  ],
9036
- "ambient-light-sensor": [
9037
- {
9038
- "path": "lux",
9039
- "valueType": "number"
9040
- },
9041
- {
9042
- "path": "unit",
9043
- "valueType": "string"
9044
- },
9045
- {
9046
- "path": "precision",
9047
- "valueType": "number"
9048
- }
9049
- ],
9131
+ "ambient-light-sensor": [{
9132
+ "path": "lux",
9133
+ "valueType": "number"
9134
+ }],
9050
9135
  "audio-metrics": [
9051
9136
  {
9052
9137
  "path": "ts",
@@ -9126,6 +9211,10 @@ Object.freeze({
9126
9211
  "path": "lastUpdated",
9127
9212
  "valueType": "number"
9128
9213
  },
9214
+ {
9215
+ "path": "volts",
9216
+ "valueType": "number"
9217
+ },
9129
9218
  {
9130
9219
  "path": "lastContactAt",
9131
9220
  "valueType": "number"
@@ -9368,10 +9457,6 @@ Object.freeze({
9368
9457
  "datetime"
9369
9458
  ]
9370
9459
  },
9371
- {
9372
- "path": "unit",
9373
- "valueType": "string"
9374
- },
9375
9460
  {
9376
9461
  "path": "min",
9377
9462
  "valueType": "number"
@@ -9383,19 +9468,6 @@ Object.freeze({
9383
9468
  {
9384
9469
  "path": "step",
9385
9470
  "valueType": "number"
9386
- },
9387
- {
9388
- "path": "precision",
9389
- "valueType": "number"
9390
- },
9391
- {
9392
- "path": "format",
9393
- "valueType": "enum",
9394
- "enumValues": [
9395
- "date",
9396
- "time",
9397
- "datetime"
9398
- ]
9399
9471
  }
9400
9472
  ],
9401
9473
  "cover": [
@@ -9636,13 +9708,8 @@ Object.freeze({
9636
9708
  "path": "value",
9637
9709
  "valueType": "string"
9638
9710
  }, {
9639
- "path": "format",
9640
- "valueType": "enum",
9641
- "enumValues": [
9642
- "date",
9643
- "time",
9644
- "datetime"
9645
- ]
9711
+ "path": "timestamp",
9712
+ "valueType": "boolean"
9646
9713
  }],
9647
9714
  "event-emitter": [
9648
9715
  {
@@ -9715,6 +9782,20 @@ Object.freeze({
9715
9782
  "path": "detected",
9716
9783
  "valueType": "boolean"
9717
9784
  }],
9785
+ "group-status": [
9786
+ {
9787
+ "path": "included",
9788
+ "valueType": "number"
9789
+ },
9790
+ {
9791
+ "path": "excluded",
9792
+ "valueType": "number"
9793
+ },
9794
+ {
9795
+ "path": "total",
9796
+ "valueType": "number"
9797
+ }
9798
+ ],
9718
9799
  "heating-valve": [{
9719
9800
  "path": "position",
9720
9801
  "valueType": "number"
@@ -9749,20 +9830,10 @@ Object.freeze({
9749
9830
  "valueType": "number"
9750
9831
  }
9751
9832
  ],
9752
- "humidity-sensor": [
9753
- {
9754
- "path": "percent",
9755
- "valueType": "number"
9756
- },
9757
- {
9758
- "path": "unit",
9759
- "valueType": "string"
9760
- },
9761
- {
9762
- "path": "precision",
9763
- "valueType": "number"
9764
- }
9765
- ],
9833
+ "humidity-sensor": [{
9834
+ "path": "percent",
9835
+ "valueType": "number"
9836
+ }],
9766
9837
  "image": [{
9767
9838
  "path": "url",
9768
9839
  "valueType": "string"
@@ -10101,20 +10172,10 @@ Object.freeze({
10101
10172
  "valueType": "boolean"
10102
10173
  }
10103
10174
  ],
10104
- "numeric-sensor": [
10105
- {
10106
- "path": "value",
10107
- "valueType": "number"
10108
- },
10109
- {
10110
- "path": "unit",
10111
- "valueType": "string"
10112
- },
10113
- {
10114
- "path": "precision",
10115
- "valueType": "number"
10116
- }
10117
- ],
10175
+ "numeric-sensor": [{
10176
+ "path": "value",
10177
+ "valueType": "number"
10178
+ }],
10118
10179
  "pet-feeder": [
10119
10180
  {
10120
10181
  "path": "foodLevel",
@@ -10206,14 +10267,6 @@ Object.freeze({
10206
10267
  {
10207
10268
  "path": "powerFactor",
10208
10269
  "valueType": "number"
10209
- },
10210
- {
10211
- "path": "unit",
10212
- "valueType": "string"
10213
- },
10214
- {
10215
- "path": "precision",
10216
- "valueType": "number"
10217
10270
  }
10218
10271
  ],
10219
10272
  "presence": [
@@ -10242,20 +10295,10 @@ Object.freeze({
10242
10295
  "valueType": "number"
10243
10296
  }
10244
10297
  ],
10245
- "pressure-sensor": [
10246
- {
10247
- "path": "hpa",
10248
- "valueType": "number"
10249
- },
10250
- {
10251
- "path": "unit",
10252
- "valueType": "string"
10253
- },
10254
- {
10255
- "path": "precision",
10256
- "valueType": "number"
10257
- }
10258
- ],
10298
+ "pressure-sensor": [{
10299
+ "path": "hpa",
10300
+ "valueType": "number"
10301
+ }],
10259
10302
  "privacy-mask": [{
10260
10303
  "path": "enabled",
10261
10304
  "valueType": "boolean"
@@ -10566,20 +10609,10 @@ Object.freeze({
10566
10609
  "path": "tampered",
10567
10610
  "valueType": "boolean"
10568
10611
  }],
10569
- "temperature-sensor": [
10570
- {
10571
- "path": "celsius",
10572
- "valueType": "number"
10573
- },
10574
- {
10575
- "path": "unit",
10576
- "valueType": "string"
10577
- },
10578
- {
10579
- "path": "precision",
10580
- "valueType": "number"
10581
- }
10582
- ],
10612
+ "temperature-sensor": [{
10613
+ "path": "celsius",
10614
+ "valueType": "number"
10615
+ }],
10583
10616
  "update": [
10584
10617
  {
10585
10618
  "path": "currentVersion",
@@ -10690,10 +10723,6 @@ Object.freeze({
10690
10723
  "path": "temperature",
10691
10724
  "valueType": "number"
10692
10725
  },
10693
- {
10694
- "path": "temperatureUnit",
10695
- "valueType": "string"
10696
- },
10697
10726
  {
10698
10727
  "path": "humidity",
10699
10728
  "valueType": "number"
@@ -10702,18 +10731,10 @@ Object.freeze({
10702
10731
  "path": "pressure",
10703
10732
  "valueType": "number"
10704
10733
  },
10705
- {
10706
- "path": "pressureUnit",
10707
- "valueType": "string"
10708
- },
10709
10734
  {
10710
10735
  "path": "windSpeed",
10711
10736
  "valueType": "number"
10712
10737
  },
10713
- {
10714
- "path": "windSpeedUnit",
10715
- "valueType": "string"
10716
- },
10717
10738
  {
10718
10739
  "path": "windBearing",
10719
10740
  "valueType": "number"
@@ -11120,7 +11141,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11120
11141
  return DeviceRole;
11121
11142
  }({});
11122
11143
  //#endregion
11123
- //#region ../types/dist/composition-kEl9mevu.mjs
11144
+ //#region ../types/dist/composition-OhpuespA.mjs
11124
11145
  /**
11125
11146
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11126
11147
  *
@@ -11875,15 +11896,74 @@ function validateExpressionSource(src) {
11875
11896
  }
11876
11897
  return null;
11877
11898
  }
11878
- /** A composed device's stableId is this prefix plus the block id. */
11879
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11880
- /** Longest snippet a `code` source may carry (slice 4). */
11881
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11899
+ /**
11900
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11901
+ * id. Split out so helper schemas can name it without a cycle through
11902
+ * `composition.ts` (D659).
11903
+ */
11882
11904
  var CompositionSourceRefSchema = object({
11883
11905
  /** The owning addon; `stableId` is unique only within it. */
11884
11906
  addonId: string().min(1),
11885
11907
  stableId: string().min(1)
11886
11908
  });
11909
+ var GROUP_TEMPLATE_IDS = [
11910
+ "temperature",
11911
+ "humidity",
11912
+ "pressure",
11913
+ "illuminance",
11914
+ "power",
11915
+ "energy",
11916
+ "battery",
11917
+ "numeric",
11918
+ "motion",
11919
+ "contact",
11920
+ "leak",
11921
+ "smoke",
11922
+ "occupancy",
11923
+ "binary",
11924
+ "switch",
11925
+ "light"
11926
+ ];
11927
+ var NUMERIC_AGGREGATES = [
11928
+ "mean",
11929
+ "median",
11930
+ "min",
11931
+ "max",
11932
+ "sum"
11933
+ ];
11934
+ var BINARY_AGGREGATES = ["any", "all"];
11935
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11936
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11937
+ var AGGREGATE_REDUCERS = [
11938
+ "mean",
11939
+ "median",
11940
+ "min",
11941
+ "max",
11942
+ "sum",
11943
+ "any",
11944
+ "all"
11945
+ ];
11946
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11947
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11948
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11949
+ /** One member. The field is stored so occupancy can name motion or binary. */
11950
+ var HelperMemberSchema = object({
11951
+ source: CompositionSourceRefSchema,
11952
+ cap: string().min(1),
11953
+ fieldPath: string().min(1)
11954
+ });
11955
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
11956
+ kind: literal("group"),
11957
+ templateId: GroupTemplateIdSchema,
11958
+ aggregate: AggregateReducerSchema,
11959
+ members: array(HelperMemberSchema).min(1).max(64),
11960
+ /** Actuator groups only. Readings leave this unset. */
11961
+ active: ActiveCriterionSchema.optional()
11962
+ })]);
11963
+ /** A composed device's stableId is this prefix plus the block id. */
11964
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11965
+ /** Longest snippet a `code` source may carry (slice 4). */
11966
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11887
11967
  /** One field of one capability of one source device. */
11888
11968
  var CompositionFieldReadSchema = object({
11889
11969
  source: CompositionSourceRefSchema,
@@ -11927,7 +12007,33 @@ var CompositionCodeSourceSchema = object({
11927
12007
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11928
12008
  CompositionFromSourceSchema,
11929
12009
  CompositionExpressionSourceSchema,
11930
- CompositionCodeSourceSchema
12010
+ CompositionCodeSourceSchema,
12011
+ object({
12012
+ kind: literal("aggregate"),
12013
+ reducer: _enum([
12014
+ "mean",
12015
+ "median",
12016
+ "min",
12017
+ "max",
12018
+ "sum",
12019
+ "any",
12020
+ "all"
12021
+ ]),
12022
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
12023
+ /** `value` is the reading; the others are the member counts. */
12024
+ publish: _enum([
12025
+ "value",
12026
+ "included",
12027
+ "excluded",
12028
+ "total"
12029
+ ]),
12030
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
12031
+ empty: _enum([
12032
+ "null",
12033
+ "omit",
12034
+ "offline"
12035
+ ])
12036
+ })
11931
12037
  ]);
11932
12038
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11933
12039
  kind: literal("forward"),
@@ -11973,7 +12079,13 @@ var CompositionSchema = object({
11973
12079
  kind: literal("existing"),
11974
12080
  device: CompositionSourceRefSchema
11975
12081
  })]),
11976
- features: array(CompositionFeatureSchema).min(1).max(16)
12082
+ features: array(CompositionFeatureSchema).min(1).max(16),
12083
+ /**
12084
+ * Present when the block is a helper template. Absent on a free
12085
+ * composition (the room example, device 4603). The editor reads this,
12086
+ * never the compiled features.
12087
+ */
12088
+ helper: HelperSpecSchema.optional()
11977
12089
  }).superRefine((composition, ctx) => {
11978
12090
  const seen = /* @__PURE__ */ new Set();
11979
12091
  composition.features.forEach((feature, index) => {
@@ -18211,7 +18323,11 @@ var CompositionProblemSchema = object({
18211
18323
  "item-field-reserved",
18212
18324
  "target-missing",
18213
18325
  "target-unreadable",
18214
- "not-replaceable"
18326
+ "not-replaceable",
18327
+ "native-capability-replace",
18328
+ "mixed-quantity",
18329
+ "helper-mismatch",
18330
+ "member-constraint"
18215
18331
  ]),
18216
18332
  path: string(),
18217
18333
  message: string()
@@ -18232,7 +18348,8 @@ var CompositionUnavailableModeSchema = _enum([
18232
18348
  "null",
18233
18349
  "offline",
18234
18350
  "unknown",
18235
- "release"
18351
+ "release",
18352
+ "omit"
18236
18353
  ]);
18237
18354
  /**
18238
18355
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -18269,9 +18386,11 @@ var CompositionFieldPlanSchema = object({
18269
18386
  reads: array(CompositionFieldReadSchema)
18270
18387
  });
18271
18388
  /**
18272
- * What a feature does to its target (D663): `new` builds a capability on a new
18273
- * device; on an existing device it `add`s a capability the device lacks, or
18274
- * `replace`s named fields of one it has natively.
18389
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
18390
+ * new device; on an existing device it `add`s a capability the device lacks.
18391
+ * Replacing a capability the device already serves is refused
18392
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
18393
+ * wire for a claim the hub already holds.
18275
18394
  */
18276
18395
  var CompositionFeatureModeSchema = _enum([
18277
18396
  "new",
@@ -18444,6 +18563,275 @@ Object.freeze({
18444
18563
  result: ["number", "null"]
18445
18564
  }
18446
18565
  });
18566
+ /**
18567
+ * One catalog row per base reading a group helper can build (D944 §3).
18568
+ * Data, not a form default written into the block.
18569
+ */
18570
+ var NOW = {
18571
+ fieldPath: "lastFetchedAt",
18572
+ expr: "now"
18573
+ };
18574
+ var CHANGED = {
18575
+ fieldPath: "lastChangedAt",
18576
+ expr: "now"
18577
+ };
18578
+ function numeric(input) {
18579
+ return {
18580
+ ...input,
18581
+ family: "numeric",
18582
+ deviceType: DeviceType.Sensor,
18583
+ memberDeviceType: DeviceType.Sensor,
18584
+ commandCaps: [],
18585
+ baseUnit: baseUnitOf(input.quantity),
18586
+ aggregates: NUMERIC_AGGREGATES,
18587
+ accept: [{
18588
+ cap: input.outputCap,
18589
+ fieldPath: input.outputField
18590
+ }]
18591
+ };
18592
+ }
18593
+ function binary(input) {
18594
+ return {
18595
+ ...input,
18596
+ family: "binary",
18597
+ deviceType: DeviceType.Sensor,
18598
+ memberDeviceType: DeviceType.Sensor,
18599
+ commandCaps: [],
18600
+ quantity: "none",
18601
+ baseUnit: void 0,
18602
+ aggregates: BINARY_AGGREGATES,
18603
+ empty: "offline"
18604
+ };
18605
+ }
18606
+ var GROUP_TEMPLATES = [
18607
+ numeric({
18608
+ id: "temperature",
18609
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18610
+ role: DeviceRole.TemperatureSensor,
18611
+ outputCap: "temperature-sensor",
18612
+ outputField: "celsius",
18613
+ quantity: "temperature",
18614
+ defaultAggregate: "mean",
18615
+ empty: "null",
18616
+ companions: [NOW]
18617
+ }),
18618
+ numeric({
18619
+ id: "humidity",
18620
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18621
+ role: DeviceRole.HumiditySensor,
18622
+ outputCap: "humidity-sensor",
18623
+ outputField: "percent",
18624
+ quantity: "humidity",
18625
+ defaultAggregate: "mean",
18626
+ empty: "null",
18627
+ companions: [NOW]
18628
+ }),
18629
+ numeric({
18630
+ id: "pressure",
18631
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18632
+ role: DeviceRole.PressureSensor,
18633
+ outputCap: "pressure-sensor",
18634
+ outputField: "hpa",
18635
+ quantity: "pressure",
18636
+ defaultAggregate: "mean",
18637
+ empty: "null",
18638
+ companions: [NOW]
18639
+ }),
18640
+ numeric({
18641
+ id: "illuminance",
18642
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18643
+ role: DeviceRole.AmbientLightSensor,
18644
+ outputCap: "ambient-light-sensor",
18645
+ outputField: "lux",
18646
+ quantity: "illuminance",
18647
+ defaultAggregate: "mean",
18648
+ empty: "null",
18649
+ companions: [NOW]
18650
+ }),
18651
+ numeric({
18652
+ id: "power",
18653
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18654
+ role: DeviceRole.PowerSensor,
18655
+ outputCap: "power-meter",
18656
+ outputField: "watts",
18657
+ quantity: "power",
18658
+ defaultAggregate: "sum",
18659
+ empty: "omit",
18660
+ companions: [NOW]
18661
+ }),
18662
+ numeric({
18663
+ id: "energy",
18664
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18665
+ role: DeviceRole.EnergySensor,
18666
+ outputCap: "power-meter",
18667
+ outputField: "kwhTotal",
18668
+ quantity: "energy",
18669
+ defaultAggregate: "sum",
18670
+ empty: "omit",
18671
+ companions: [NOW]
18672
+ }),
18673
+ numeric({
18674
+ id: "battery",
18675
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18676
+ role: DeviceRole.BatterySensor,
18677
+ outputCap: "battery",
18678
+ outputField: "percentage",
18679
+ quantity: "percentage",
18680
+ defaultAggregate: "mean",
18681
+ empty: "null",
18682
+ companions: [
18683
+ {
18684
+ fieldPath: "charging",
18685
+ expr: "\"unknown\""
18686
+ },
18687
+ {
18688
+ fieldPath: "sleeping",
18689
+ expr: "false"
18690
+ },
18691
+ {
18692
+ fieldPath: "lastUpdated",
18693
+ expr: "now"
18694
+ }
18695
+ ]
18696
+ }),
18697
+ numeric({
18698
+ id: "numeric",
18699
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18700
+ role: DeviceRole.NumericSensor,
18701
+ outputCap: "numeric-sensor",
18702
+ outputField: "value",
18703
+ quantity: "device",
18704
+ defaultAggregate: "mean",
18705
+ empty: "null",
18706
+ companions: [NOW]
18707
+ }),
18708
+ binary({
18709
+ id: "motion",
18710
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18711
+ role: DeviceRole.MotionSensor,
18712
+ outputCap: "motion",
18713
+ outputField: "detected",
18714
+ defaultAggregate: "any",
18715
+ accept: [{
18716
+ cap: "motion",
18717
+ fieldPath: "detected"
18718
+ }],
18719
+ companions: [{
18720
+ fieldPath: "lastDetectedAt",
18721
+ expr: "null"
18722
+ }, {
18723
+ fieldPath: "autoClearAfterMs",
18724
+ expr: "null"
18725
+ }]
18726
+ }),
18727
+ binary({
18728
+ id: "contact",
18729
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18730
+ role: DeviceRole.ContactSensor,
18731
+ outputCap: "contact",
18732
+ outputField: "entryOpen",
18733
+ defaultAggregate: "any",
18734
+ accept: [{
18735
+ cap: "contact",
18736
+ fieldPath: "entryOpen"
18737
+ }],
18738
+ companions: [CHANGED]
18739
+ }),
18740
+ binary({
18741
+ id: "leak",
18742
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18743
+ role: DeviceRole.LeakSensor,
18744
+ outputCap: "flood",
18745
+ outputField: "flooded",
18746
+ defaultAggregate: "any",
18747
+ accept: [{
18748
+ cap: "flood",
18749
+ fieldPath: "flooded"
18750
+ }],
18751
+ companions: [CHANGED]
18752
+ }),
18753
+ binary({
18754
+ id: "smoke",
18755
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18756
+ role: DeviceRole.SmokeSensor,
18757
+ outputCap: "smoke",
18758
+ outputField: "detected",
18759
+ defaultAggregate: "any",
18760
+ accept: [{
18761
+ cap: "smoke",
18762
+ fieldPath: "detected"
18763
+ }],
18764
+ companions: [CHANGED]
18765
+ }),
18766
+ binary({
18767
+ id: "occupancy",
18768
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18769
+ role: DeviceRole.BinarySensor,
18770
+ outputCap: "binary",
18771
+ outputField: "on",
18772
+ defaultAggregate: "any",
18773
+ accept: [{
18774
+ cap: "motion",
18775
+ fieldPath: "detected"
18776
+ }, {
18777
+ cap: "binary",
18778
+ fieldPath: "on"
18779
+ }],
18780
+ companions: [CHANGED]
18781
+ }),
18782
+ binary({
18783
+ id: "binary",
18784
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18785
+ role: DeviceRole.BinarySensor,
18786
+ outputCap: "binary",
18787
+ outputField: "on",
18788
+ defaultAggregate: "any",
18789
+ accept: [{
18790
+ cap: "binary",
18791
+ fieldPath: "on"
18792
+ }],
18793
+ companions: [CHANGED]
18794
+ }),
18795
+ actuator({
18796
+ id: "switch",
18797
+ 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.",
18798
+ deviceType: DeviceType.Switch,
18799
+ outputCap: "switch",
18800
+ commandCaps: ["switch"]
18801
+ }),
18802
+ actuator({
18803
+ id: "light",
18804
+ 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.",
18805
+ deviceType: DeviceType.Light,
18806
+ outputCap: "switch",
18807
+ commandCaps: [
18808
+ "switch",
18809
+ "brightness",
18810
+ "color"
18811
+ ]
18812
+ })
18813
+ ];
18814
+ function actuator(input) {
18815
+ return {
18816
+ ...input,
18817
+ family: "actuator",
18818
+ role: void 0,
18819
+ memberDeviceType: input.deviceType,
18820
+ outputField: "on",
18821
+ quantity: "none",
18822
+ baseUnit: void 0,
18823
+ aggregates: BINARY_AGGREGATES,
18824
+ defaultAggregate: "any",
18825
+ empty: "offline",
18826
+ accept: [{
18827
+ cap: "switch",
18828
+ fieldPath: "on"
18829
+ }],
18830
+ companions: [CHANGED]
18831
+ };
18832
+ }
18833
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18834
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18447
18835
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18448
18836
  /**
18449
18837
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -20157,12 +20545,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
20157
20545
  * providers detect renames by matching `uniqueId` and patch `id`
20158
20546
  * in-place so dispatch keeps working.
20159
20547
  *
20160
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
20161
- * `precision`) live in the cap STATUS SLICE so they flow live with every
20162
- * state push and are the single source of truth. The `device_class` tag is
20163
- * an HA-specific input that is consumed at the discovery boundary
20164
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
20165
- * stored after that point.
20548
+ * **Display unit and precision are NOT stored here.** They are a read-time
20549
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20550
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20551
+ * neither is repeated on each sample. The `device_class` tag is an
20552
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20553
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
20166
20554
  *
20167
20555
  * The `raw` escape hatch carries provider-specific attributes that don't
20168
20556
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -20176,6 +20564,14 @@ var SourceInfoSchema = object({
20176
20564
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
20177
20565
  * Used to detect rename when `id` changes. */
20178
20566
  uniqueId: string().optional(),
20567
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20568
+ quantity: ReadingQuantitySchema.optional(),
20569
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20570
+ dateTimeShape: _enum([
20571
+ "date",
20572
+ "time",
20573
+ "datetime"
20574
+ ]).optional(),
20179
20575
  /** Provider-specific extras that don't fit the structured slots.
20180
20576
  * Whitelist what goes here per provider — do NOT dump entire HA
20181
20577
  * attribute blobs (would explode the meta JSON column). */
@@ -41088,7 +41484,8 @@ method(object({
41088
41484
  "enum"
41089
41485
  ]),
41090
41486
  enumValues: array(string()).optional(),
41091
- item: boolean().optional()
41487
+ item: boolean().optional(),
41488
+ quantity: ReadingQuantitySchema.optional()
41092
41489
  })).readonly(),
41093
41490
  itemArray: object({
41094
41491
  path: string(),
@@ -44910,8 +45307,8 @@ var MaskPolygonShapeSchema = object({
44910
45307
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44911
45308
  var MaskGridShapeSchema = object({
44912
45309
  kind: literal("grid"),
44913
- gridWidth: number(),
44914
- gridHeight: number(),
45310
+ gridWidth: number().meta(readingMeta("none")),
45311
+ gridHeight: number().meta(readingMeta("none")),
44915
45312
  cells: array(boolean())
44916
45313
  });
44917
45314
  discriminatedUnion("kind", [
@@ -45023,6 +45420,8 @@ var NcSystemEventKindSchema = _enum([
45023
45420
  "detection-blind",
45024
45421
  "motion-noisy",
45025
45422
  "motion-calm",
45423
+ "package-llm-silent",
45424
+ "package-llm-answered",
45026
45425
  "recording-stalled",
45027
45426
  "recording-healthy",
45028
45427
  "recording-degraded",
@@ -56037,6 +56436,28 @@ var PackageVersionInfoSchema = object({
56037
56436
  deprecated: string().optional(),
56038
56437
  distTags: array(string()).readonly()
56039
56438
  });
56439
+ /**
56440
+ * One published document. `ok` is markdown from the registry. `missing` means
56441
+ * the registry answered and the file was not there. `error` is a registry or
56442
+ * tarball failure — never an empty string standing in for either.
56443
+ */
56444
+ var PackageDocBodySchema = discriminatedUnion("status", [
56445
+ object({
56446
+ status: literal("ok"),
56447
+ markdown: string()
56448
+ }),
56449
+ object({ status: literal("missing") }),
56450
+ object({
56451
+ status: literal("error"),
56452
+ message: string()
56453
+ })
56454
+ ]);
56455
+ var PackageDocsSchema = object({
56456
+ name: string(),
56457
+ version: string(),
56458
+ readme: PackageDocBodySchema,
56459
+ changelog: PackageDocBodySchema
56460
+ });
56040
56461
  var SearchResultSchema = object({
56041
56462
  name: string(),
56042
56463
  version: string(),
@@ -56255,7 +56676,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56255
56676
  }), object({ success: literal(true) }), {
56256
56677
  kind: "mutation",
56257
56678
  auth: "admin"
56258
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
56679
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
56680
+ name: string().min(1),
56681
+ version: string().min(1).optional()
56682
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
56259
56683
  kind: "mutation",
56260
56684
  auth: "admin"
56261
56685
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -56305,46 +56729,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56305
56729
  }), LogStreamEntrySchema, { kind: "subscription" });
56306
56730
  object({
56307
56731
  /** Carbon dioxide concentration in ppm. */
56308
- co2Ppm: number().min(0).optional(),
56732
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
56309
56733
  /** Total volatile organic compounds in ppb. */
56310
- vocPpb: number().min(0).optional(),
56734
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56311
56735
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56312
- pm25: number().min(0).optional(),
56736
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56313
56737
  /** Particulate matter ≤ 10 μm in µg/m³. */
56314
- pm10: number().min(0).optional(),
56738
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
56315
56739
  /** Composite AQI value (typically 0..500). */
56316
- aqi: number().optional(),
56740
+ aqi: number().optional().meta(readingMeta("index")),
56317
56741
  /** Ms epoch when the slice was last updated. */
56318
- lastFetchedAt: number(),
56319
- /** Live display unit of the single metric this slice carries (e.g. HA
56320
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
56321
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
56322
- * per slice is unambiguous. */
56323
- unit: string().optional(),
56324
- /** Suggested decimal places for numeric display.
56325
- * Populated live from the upstream source when provided (e.g. HA
56326
- * `attributes.suggested_display_precision`). Falls back to
56327
- * auto-formatting when absent. */
56328
- precision: number().int().min(0).max(10).optional()
56742
+ lastFetchedAt: number()
56329
56743
  });
56330
56744
  DeviceType.Sensor;
56331
56745
  object({
56332
- /** Current illuminance in lux (lx). */
56333
- lux: number().min(0).meta({
56334
- unit: "lx",
56746
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56747
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
56748
+ precision: 0,
56335
56749
  label: "Illuminance"
56336
- }),
56750
+ })),
56337
56751
  /** Ms epoch when the slice was last updated. */
56338
- lastFetchedAt: number(),
56339
- /** Live display unit from the upstream source (e.g. HA
56340
- * `attributes.unit_of_measurement`). The UI prefers this over the
56341
- * role's canonical unit. Absent → fall back to the canonical unit. */
56342
- unit: string().optional(),
56343
- /** Suggested decimal places for numeric display.
56344
- * Populated live from the upstream source when provided (e.g. HA
56345
- * `attributes.suggested_display_precision`). Falls back to
56346
- * auto-formatting when absent. */
56347
- precision: number().int().min(0).max(10).optional()
56752
+ lastFetchedAt: number()
56348
56753
  });
56349
56754
  DeviceType.Sensor;
56350
56755
  /**
@@ -56489,10 +56894,7 @@ var BatteryStatusSchema = object({
56489
56894
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
56490
56895
  * declines to band a non-finite reading, which is the correct shape.
56491
56896
  */
56492
- percentage: number().min(0).max(100).nullable().meta({
56493
- unit: "%",
56494
- label: "Charge"
56495
- }),
56897
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
56496
56898
  /**
56497
56899
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
56498
56900
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -56519,7 +56921,14 @@ var BatteryStatusSchema = object({
56519
56921
  */
56520
56922
  sleeping: boolean().meta({ label: "Sleeping" }),
56521
56923
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
56522
- lastUpdated: number(),
56924
+ lastUpdated: number().meta(readingMeta("none")),
56925
+ /**
56926
+ * Battery voltage in volts, for a cell the firmware reports as voltage
56927
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
56928
+ * that only report charge. A second reading on this cap, not a second
56929
+ * `numeric-sensor` on the same device.
56930
+ */
56931
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
56523
56932
  /**
56524
56933
  * Ms epoch of the last time the device PROVED it was reachable — a
56525
56934
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -56579,7 +56988,7 @@ object({
56579
56988
  DeviceType.Sensor;
56580
56989
  object({
56581
56990
  /** Current level as 0..100 inclusive. Firmware-reported. */
56582
- percentage: number().min(0).max(100),
56991
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
56583
56992
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
56584
56993
  lastChangedAt: number()
56585
56994
  });
@@ -56964,21 +57373,21 @@ object({
56964
57373
  /** Single setpoint in Celsius. Used by single-target modes
56965
57374
  * (heat / cool / dry). Null when the device is in a range mode
56966
57375
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
56967
- target: number().nullable(),
57376
+ target: number().nullable().meta(readingMeta("temperature")),
56968
57377
  /** Upper bound of the dual setpoint in Celsius. Populated only
56969
57378
  * in `heat_cool` mode. */
56970
- targetHigh: number().nullable(),
57379
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
56971
57380
  /** Lower bound of the dual setpoint in Celsius. Populated only
56972
57381
  * in `heat_cool` mode. */
56973
- targetLow: number().nullable(),
57382
+ targetLow: number().nullable().meta(readingMeta("temperature")),
56974
57383
  /** Target relative humidity (0..100). Null when the device has no
56975
57384
  * humidity control. */
56976
- targetHumidity: number().min(0).max(100).nullable(),
57385
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56977
57386
  /** Current measured temperature in Celsius. Read-only — pushed by
56978
57387
  * the device's internal sensor. */
56979
- currentTemp: number().nullable(),
57388
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
56980
57389
  /** Current measured relative humidity (0..100). Read-only. */
56981
- currentHumidity: number().min(0).max(100).nullable(),
57390
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56982
57391
  /** Vertical louver swing on/off. Null when the device has no vertical
56983
57392
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
56984
57393
  swingVertical: boolean().nullable(),
@@ -56986,11 +57395,11 @@ object({
56986
57395
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
56987
57396
  swingHorizontal: boolean().nullable(),
56988
57397
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
56989
- minTemp: number().nullable().optional(),
57398
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
56990
57399
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
56991
- maxTemp: number().nullable().optional(),
57400
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
56992
57401
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
56993
- tempStep: number().positive().nullable().optional(),
57402
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
56994
57403
  /** Ms epoch when the slice was last updated (push or command). */
56995
57404
  lastFetchedAt: number()
56996
57405
  });
@@ -57069,17 +57478,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
57069
57478
  * modes to expose in the picker without a slice fetch.
57070
57479
  */
57071
57480
  var RgbTripletSchema = object({
57072
- r: number().int().min(0).max(255),
57073
- g: number().int().min(0).max(255),
57074
- b: number().int().min(0).max(255)
57481
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
57482
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
57483
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
57075
57484
  });
57076
57485
  var HsvTripletSchema = object({
57077
57486
  /** Hue in degrees, 0..360. */
57078
- h: number().min(0).max(360),
57487
+ h: number().min(0).max(360).meta(readingMeta("none")),
57079
57488
  /** Saturation as 0..100 inclusive. */
57080
- s: number().min(0).max(100),
57489
+ s: number().min(0).max(100).meta(readingMeta("none")),
57081
57490
  /** Value/brightness as 0..100 inclusive. */
57082
- v: number().min(0).max(100)
57491
+ v: number().min(0).max(100).meta(readingMeta("none"))
57083
57492
  });
57084
57493
  var ColorInputSchema = discriminatedUnion("mode", [
57085
57494
  object({
@@ -57108,7 +57517,7 @@ object({
57108
57517
  /** Populated when `mode === 'hsv'`. */
57109
57518
  hsv: HsvTripletSchema.optional(),
57110
57519
  /** Populated when `mode === 'mired'`. */
57111
- mireds: number().int().optional(),
57520
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
57112
57521
  /** Ms epoch of the last operator-driven change. */
57113
57522
  lastChangedAt: number()
57114
57523
  });
@@ -57232,18 +57641,12 @@ _enum([
57232
57641
  "empty",
57233
57642
  "full"
57234
57643
  ]);
57235
- /** Units a remaining-life counter is reported in. Closed: the panel renders
57236
- * each. */
57237
- var ConsumableRemainingUnitSchema = _enum([
57238
- "days",
57239
- "hours",
57240
- "cycles"
57241
- ]);
57242
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
57243
- * days). `value` null = unknown (D393). */
57644
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
57645
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
57646
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
57244
57647
  var ConsumableRemainingSchema = object({
57245
- value: number().min(0).nullable(),
57246
- unit: ConsumableRemainingUnitSchema
57648
+ value: number().min(0).nullable().meta(readingMeta("device")),
57649
+ kind: ConsumableRemainingKindSchema
57247
57650
  });
57248
57651
  var ConsumablesStatusSchema = object({
57249
57652
  items: array(object({
@@ -57252,7 +57655,7 @@ var ConsumablesStatusSchema = object({
57252
57655
  /** Display name. */
57253
57656
  label: string().min(1),
57254
57657
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
57255
- level: number().min(0).max(100).nullable(),
57658
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57256
57659
  /** The provider's discrete verdict when known (binary mode). */
57257
57660
  status: ConsumableStatusSchema.nullable(),
57258
57661
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -57269,7 +57672,7 @@ var ConsumablesStatusSchema = object({
57269
57672
  })),
57270
57673
  lastChangedAt: number()
57271
57674
  });
57272
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57675
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57273
57676
  Object.values(DeviceType), method(object({
57274
57677
  deviceId: number().int().nonnegative(),
57275
57678
  key: string().min(1)
@@ -57294,39 +57697,17 @@ object({
57294
57697
  /** Current value. Numeric kind → number; select/text/datetime → string.
57295
57698
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
57296
57699
  * or time-only — HA accepts all three forms). */
57297
- value: union([number(), string()]),
57700
+ value: union([number().meta(readingMeta("device")), string()]),
57298
57701
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
57299
57702
  options: array(string()),
57300
57703
  /** Ms epoch when the slice was last updated. */
57301
57704
  lastChangedAt: number(),
57302
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
57303
- * Populated live from HA `attributes.unit_of_measurement` on each push.
57304
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57305
- unit: string().optional(),
57306
- /** Minimum allowed value. Populated live from HA `attributes.min`.
57307
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57308
- min: number().optional(),
57309
- /** Maximum allowed value. Populated live from HA `attributes.max`.
57310
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57311
- max: number().optional(),
57312
- /** Step increment. Populated live from HA `attributes.step`.
57313
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57314
- step: number().optional(),
57315
- /** Suggested decimal places for numeric display. Populated live from HA
57316
- * `attributes.suggested_display_precision`, or derived from `step` when the
57317
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
57318
- * other kinds. Falls back to auto-formatting when absent. */
57319
- precision: number().int().min(0).max(10).optional(),
57320
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
57321
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
57322
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
57323
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
57324
- * `has_date`/`has_time` attributes on each push. */
57325
- format: _enum([
57326
- "date",
57327
- "time",
57328
- "datetime"
57329
- ]).optional()
57705
+ /** Minimum allowed value, in the device quantity's base unit. */
57706
+ min: number().optional().meta(readingMeta("device")),
57707
+ /** Maximum allowed value, in the device quantity's base unit. */
57708
+ max: number().optional().meta(readingMeta("device")),
57709
+ /** Step increment, in the device quantity's base unit. */
57710
+ step: number().optional().meta(readingMeta("device"))
57330
57711
  });
57331
57712
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
57332
57713
  object({
@@ -57364,9 +57745,9 @@ object({
57364
57745
  ]),
57365
57746
  /** 0 = fully closed, 100 = fully open. Null when the device has
57366
57747
  * no intermediate position surface. */
57367
- position: number().min(0).max(100).nullable(),
57748
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57368
57749
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
57369
- tiltPosition: number().min(0).max(100).nullable(),
57750
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57370
57751
  /** Ms epoch when the slice was last updated. */
57371
57752
  lastChangedAt: number()
57372
57753
  });
@@ -57424,9 +57805,9 @@ var NormalizedRangeSchema$1 = object({
57424
57805
  object({
57425
57806
  mode: DayNightModeSchema,
57426
57807
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
57427
- sensitivity: number().optional(),
57808
+ sensitivity: number().optional().meta(readingMeta("none")),
57428
57809
  /** Delay before the IR-cut filter flips, in seconds. */
57429
- switchDelaySec: number().optional(),
57810
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
57430
57811
  lastFetchedAt: number()
57431
57812
  });
57432
57813
  /**
@@ -57581,11 +57962,11 @@ object({
57581
57962
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
57582
57963
  availablePrograms: array(string()),
57583
57964
  /** Seconds remaining on the active cycle, or null when not running / not known. */
57584
- remainingSeconds: number().int().nonnegative().nullable(),
57965
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57585
57966
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
57586
- delayedStartSeconds: number().int().nonnegative().nullable(),
57967
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57587
57968
  /** Cycle completion 0..100, or null when not applicable. */
57588
- progressPercent: number().min(0).max(100).nullable(),
57969
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57589
57970
  /** True when the door is reported open; null when the door state is not known. */
57590
57971
  doorOpen: boolean().nullable(),
57591
57972
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -57609,7 +57990,7 @@ object({
57609
57990
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
57610
57991
  machineCareDue: boolean().nullable(),
57611
57992
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
57612
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
57993
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
57613
57994
  /** Option states: null when the appliance does not expose the option or has not said. */
57614
57995
  halfLoad: boolean().nullable(),
57615
57996
  hygienePlus: boolean().nullable(),
@@ -57624,7 +58005,7 @@ object({
57624
58005
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
57625
58006
  activeEvents: array(string()),
57626
58007
  /** Numeric error code when the appliance reports one; null otherwise. */
57627
- errorCode: number().nullable(),
58008
+ errorCode: number().nullable().meta(readingMeta("none")),
57628
58009
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
57629
58010
  errorLabel: string().nullable(),
57630
58011
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -57770,24 +58151,14 @@ object({
57770
58151
  /** Ms epoch of the last press. null = never observed since this provider started. */
57771
58152
  lastPressedAt: number().nullable(),
57772
58153
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
57773
- pressCountSinceStart: number()
58154
+ pressCountSinceStart: number().meta(readingMeta("count"))
57774
58155
  });
57775
58156
  object({
57776
58157
  deviceId: number(),
57777
58158
  timestamp: number()
57778
58159
  });
57779
58160
  DeviceType.Button, DeviceType.Camera;
57780
- /**
57781
- * Enum-state sensor — a string value picked from a finite option set.
57782
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
57783
- * states, weather conditions, contact-source channels, mode strings).
57784
- * The option set is stable across observations; it is persisted in the
57785
- * device config blob (via the control slice at adoption) and is NOT
57786
- * stored in `sourceInfo`.
57787
- */
57788
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
57789
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
57790
- var EnumSensorDateTimeFormatSchema = _enum([
58161
+ _enum([
57791
58162
  "date",
57792
58163
  "time",
57793
58164
  "datetime"
@@ -57795,12 +58166,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
57795
58166
  object({
57796
58167
  value: string(),
57797
58168
  /**
57798
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
57799
- * as a locale date/time/datetime; absent for plain enum sensors. HA
57800
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
57801
- * a time-only sensor → `'time'`.
58169
+ * Set when `value` is a timestamp rather than an enum state, so history
58170
+ * does not learn ISO strings as states. The display shape (date / time /
58171
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
57802
58172
  */
57803
- format: EnumSensorDateTimeFormatSchema.optional(),
58173
+ timestamp: boolean().optional(),
57804
58174
  /** Ms epoch when the slice was last updated. */
57805
58175
  lastFetchedAt: number()
57806
58176
  });
@@ -57819,13 +58189,13 @@ var EventFireSchema = object({
57819
58189
  deviceId: number(),
57820
58190
  eventType: string(),
57821
58191
  data: record(string(), unknown()).nullable(),
57822
- timestamp: number(),
57823
- seq: number()
58192
+ timestamp: number().meta(readingMeta("none")),
58193
+ seq: number().meta(readingMeta("count"))
57824
58194
  });
57825
58195
  object({
57826
58196
  eventTypes: array(string()),
57827
58197
  lastEvent: EventFireSchema.nullable(),
57828
- eventCountSinceStart: number()
58198
+ eventCountSinceStart: number().meta(readingMeta("count"))
57829
58199
  });
57830
58200
  DeviceType.EventEmitter;
57831
58201
  var IdentitySchema = object({
@@ -58214,10 +58584,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
58214
58584
  object({
58215
58585
  /** Active speed as 0..100 inclusive. Null when the device has no
58216
58586
  * speed surface (single-speed fan). */
58217
- percentage: number().min(0).max(100).nullable(),
58587
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58218
58588
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
58219
58589
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
58220
- percentageStep: number().positive().optional(),
58590
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
58221
58591
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
58222
58592
  * — empty string when no preset is active or supported. */
58223
58593
  preset: string(),
@@ -58299,7 +58669,7 @@ var FeatureProbeStatusSchema = object({
58299
58669
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
58300
58670
  model: string().nullable(),
58301
58671
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
58302
- channelCount: number().nullable(),
58672
+ channelCount: number().nullable().meta(readingMeta("count")),
58303
58673
  /**
58304
58674
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
58305
58675
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -58331,9 +58701,39 @@ object({
58331
58701
  lastChangedAt: number()
58332
58702
  });
58333
58703
  DeviceType.Sensor;
58704
+ object({
58705
+ /** One row per member that has been commanded. A later command replaces that member's row. */
58706
+ results: array(object({
58707
+ deviceId: number().int().nonnegative(),
58708
+ cap: string().min(1),
58709
+ method: string().min(1),
58710
+ ok: boolean(),
58711
+ error: string().optional(),
58712
+ /** Ms epoch of the attempt. */
58713
+ at: number()
58714
+ })) });
58715
+ DeviceType.Switch, DeviceType.Light;
58716
+ object({
58717
+ /** Members whose current value entered the aggregate. */
58718
+ included: number().int().nonnegative().meta(readingMeta("count", {
58719
+ precision: 0,
58720
+ label: "Reporting"
58721
+ })),
58722
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
58723
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
58724
+ precision: 0,
58725
+ label: "Excluded"
58726
+ })),
58727
+ /** `included` + `excluded`. */
58728
+ total: number().int().nonnegative().meta(readingMeta("count", {
58729
+ precision: 0,
58730
+ label: "Members"
58731
+ }))
58732
+ });
58733
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
58334
58734
  object({
58335
58735
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
58336
- position: number().min(0).max(100).nullable(),
58736
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58337
58737
  /** Ms epoch when the slice was last updated (push or poll). */
58338
58738
  lastFetchedAt: number()
58339
58739
  });
@@ -58342,9 +58742,9 @@ object({
58342
58742
  /** Whether the humidifier is currently on. */
58343
58743
  on: boolean(),
58344
58744
  /** Current measured relative humidity (0..100). Null when not reported. */
58345
- currentHumidity: number().min(0).max(100).nullable(),
58745
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58346
58746
  /** Target relative humidity (0..100). Null when no setpoint surface. */
58347
- targetHumidity: number().min(0).max(100).nullable(),
58747
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58348
58748
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
58349
58749
  * device has no mode surface. */
58350
58750
  mode: string().nullable(),
@@ -58354,9 +58754,9 @@ object({
58354
58754
  * `idle` / `off`). Null when the device doesn't report it. */
58355
58755
  action: string().nullable(),
58356
58756
  /** HA `min_humidity` attribute. Null → UI uses 0. */
58357
- minHumidity: number().nullable(),
58757
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
58358
58758
  /** HA `max_humidity` attribute. Null → UI uses 100. */
58359
- maxHumidity: number().nullable(),
58759
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
58360
58760
  /** Ms epoch when the slice was last updated. */
58361
58761
  lastChangedAt: number()
58362
58762
  });
@@ -58380,22 +58780,13 @@ DeviceType.Humidifier, method(object({
58380
58780
  auth: "protected"
58381
58781
  });
58382
58782
  object({
58383
- /** Current relative humidity, 0..100. */
58384
- percent: number().min(0).max(100).meta({
58385
- unit: "%",
58783
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
58784
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
58785
+ precision: 0,
58386
58786
  label: "Humidity"
58387
- }),
58787
+ })),
58388
58788
  /** Ms epoch when the slice was last updated. */
58389
- lastFetchedAt: number(),
58390
- /** Live display unit from the upstream source (e.g. HA
58391
- * `attributes.unit_of_measurement`). The UI prefers this over the
58392
- * role's canonical unit. Absent → fall back to the canonical unit. */
58393
- unit: string().optional(),
58394
- /** Suggested decimal places for numeric display.
58395
- * Populated live from the upstream source when provided (e.g. HA
58396
- * `attributes.suggested_display_precision`). Falls back to
58397
- * auto-formatting when absent. */
58398
- precision: number().int().min(0).max(10).optional()
58789
+ lastFetchedAt: number()
58399
58790
  });
58400
58791
  DeviceType.Sensor, DeviceType.Thermostat;
58401
58792
  object({
@@ -58403,7 +58794,7 @@ object({
58403
58794
  * entity exposes no `entity_picture` (yet). */
58404
58795
  url: string().nullable(),
58405
58796
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
58406
- lastUpdated: number().nullable()
58797
+ lastUpdated: number().nullable().meta(readingMeta("none"))
58407
58798
  });
58408
58799
  DeviceType.Image;
58409
58800
  /**
@@ -58459,19 +58850,19 @@ var NormalizedRangeSchema = object({
58459
58850
  });
58460
58851
  object({
58461
58852
  /** Normalized 0–100. */
58462
- brightness: number().optional(),
58853
+ brightness: number().optional().meta(readingMeta("none")),
58463
58854
  /** Normalized 0–100. */
58464
- contrast: number().optional(),
58855
+ contrast: number().optional().meta(readingMeta("none")),
58465
58856
  /** Normalized 0–100. */
58466
- saturation: number().optional(),
58857
+ saturation: number().optional().meta(readingMeta("none")),
58467
58858
  /** Normalized 0–100. */
58468
- sharpness: number().optional(),
58859
+ sharpness: number().optional().meta(readingMeta("none")),
58469
58860
  mirror: boolean().optional(),
58470
58861
  flip: boolean().optional(),
58471
58862
  rotate: ImageRotateSchema.optional(),
58472
58863
  whiteBalance: WhiteBalanceModeSchema.optional(),
58473
58864
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
58474
- warmth: number().optional(),
58865
+ warmth: number().optional().meta(readingMeta("none")),
58475
58866
  exposureMode: ExposureModeSchema.optional(),
58476
58867
  backlightMode: BacklightModeSchema.optional(),
58477
58868
  lastFetchedAt: number()
@@ -58716,10 +59107,10 @@ var IntercomAbilitySchema = object({
58716
59107
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
58717
59108
  codecs: array(string()),
58718
59109
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
58719
- sampleRate: number(),
59110
+ sampleRate: number().meta(readingMeta("none")),
58720
59111
  duplex: _enum(["half", "full"]),
58721
59112
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
58722
- maxBacklogMs: number()
59113
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
58723
59114
  });
58724
59115
  var IntercomStatusSchema = object({
58725
59116
  /** True while a talk session is open. */
@@ -58830,13 +59221,13 @@ object({
58830
59221
  activity: LawnMowerActivitySchema,
58831
59222
  /** 0..100 battery percentage. Null when the device has no battery
58832
59223
  * reading. */
58833
- batteryLevel: number().min(0).max(100).nullable(),
59224
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58834
59225
  /** 0..100 mowing-completion percentage of the current task, or null when no
58835
59226
  * task is active / progress is unavailable. */
58836
- progressPercent: number().min(0).max(100).nullable(),
59227
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58837
59228
  /** Current device/event code (dynamic — mostly status, sometimes an error),
58838
59229
  * or null when unknown. */
58839
- currentCode: number().nullable(),
59230
+ currentCode: number().nullable().meta(readingMeta("none")),
58840
59231
  /** Human label for {@link currentCode}, or null when undecodable. */
58841
59232
  currentCodeLabel: string().nullable(),
58842
59233
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -59298,7 +59689,7 @@ object({
59298
59689
  state: MediaPlayerStateSchema,
59299
59690
  /** Volume as 0..100 inclusive. Null when the device has no volume
59300
59691
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59301
- volumeLevel: number().min(0).max(100).nullable(),
59692
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59302
59693
  /** Mute toggle distinct from volume=0. Null when no mute surface.
59303
59694
  * Pair with `DeviceFeature.MediaPlayerMute`. */
59304
59695
  isMuted: boolean().nullable(),
@@ -59311,10 +59702,10 @@ object({
59311
59702
  currentMedia: MediaInfoSchema.nullable(),
59312
59703
  /** Current playback position in ms. Null when not seekable or
59313
59704
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59314
- positionMs: number().int().nonnegative().nullable(),
59705
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59315
59706
  /** Total duration of the current media in ms. Null when unknown
59316
59707
  * (live stream) or nothing is playing. */
59317
- durationMs: number().int().nonnegative().nullable(),
59708
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59318
59709
  /** Shuffle toggle. Null when no shuffle surface.
59319
59710
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59320
59711
  shuffle: boolean().nullable(),
@@ -59614,7 +60005,7 @@ object({
59614
60005
  * it, every provider seeded it at boot, and every session left it
59615
60006
  * `detected: true` after its detach.
59616
60007
  */
59617
- autoClearAfterMs: number().nullable()
60008
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
59618
60009
  });
59619
60010
  /**
59620
60011
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -59664,7 +60055,7 @@ var MotionZoneRegionSchema = object({
59664
60055
  });
59665
60056
  object({
59666
60057
  enabled: boolean(),
59667
- sensitivity: number(),
60058
+ sensitivity: number().meta(readingMeta("none")),
59668
60059
  /** Grid region(s). Today exactly one `grid` shape. */
59669
60060
  regions: array(MotionZoneRegionSchema),
59670
60061
  lastFetchedAt: number()
@@ -59968,7 +60359,7 @@ var NavigationStatusSchema = object({
59968
60359
  following: boolean(),
59969
60360
  flash: boolean(),
59970
60361
  lightMode: NavigationLightModeSchema,
59971
- lightLevel: number().min(40).max(100),
60362
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
59972
60363
  /** Ms epoch when the slice was last updated. */
59973
60364
  lastChangedAt: number()
59974
60365
  });
@@ -60014,13 +60405,13 @@ var NetworkLinkStatusSchema = object({
60014
60405
  * one whose reading has not landed must not be drawn at 0 %. Consumers
60015
60406
  * SKIP a null rather than coerce it.
60016
60407
  */
60017
- signalPercent: number().min(0).max(100).nullable(),
60408
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60018
60409
  /** Raw received signal strength in dBm, when the firmware reports one. */
60019
- rssiDbm: number().optional(),
60410
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
60020
60411
  /** Network name of a wireless link, when the firmware reports it. */
60021
60412
  ssid: string().optional(),
60022
60413
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60023
- lastUpdated: number()
60414
+ lastUpdated: number().meta(readingMeta("none"))
60024
60415
  });
60025
60416
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
60026
60417
  deviceId: number(),
@@ -60339,7 +60730,7 @@ object({
60339
60730
  lastError: string().nullable(),
60340
60731
  /** Number of deliveries currently buffered server-side (rate-limited
60341
60732
  * notifiers may queue). 0 when the provider sends synchronously. */
60342
- queueDepth: number().int().nonnegative(),
60733
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
60343
60734
  /** Per-feature capability matrix — authoritative source for the
60344
60735
  * compose-form field gating. */
60345
60736
  supports: NotifierSupportsSchema
@@ -60389,15 +60780,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
60389
60780
  acceptedAt: number()
60390
60781
  });
60391
60782
  object({
60392
- value: number(),
60393
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
60394
- * Populated live from the upstream source on each state push.
60395
- * Absent for unitless sensors. */
60396
- unit: string().optional(),
60397
- /** Suggested decimal places for numeric display.
60398
- * Populated live from the upstream source when provided.
60399
- * Falls back to auto-formatting when absent. */
60400
- precision: number().int().min(0).max(10).optional(),
60783
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
60784
+ value: number().nullable().meta(readingMeta("device")),
60401
60785
  /** Ms epoch when the slice was last updated. */
60402
60786
  lastFetchedAt: number()
60403
60787
  });
@@ -60687,23 +61071,26 @@ object({
60687
61071
  /** Food currently in the bowl (grams). Null when the device has not
60688
61072
  * reported a reading yet. On dual-hopper models this is the combined
60689
61073
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
60690
- foodLevel: number().nullable(),
61074
+ foodLevel: number().nullable().meta(readingMeta("none")),
60691
61075
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
60692
61076
  * single-hopper models. */
60693
- food1: number().nullable(),
61077
+ food1: number().nullable().meta(readingMeta("none")),
60694
61078
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
60695
61079
  * single-hopper models. */
60696
- food2: number().nullable(),
61080
+ food2: number().nullable().meta(readingMeta("none")),
60697
61081
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
60698
61082
  * (`device_class: problem`, on = low). True when the bowl is empty /
60699
61083
  * below the feeder's low threshold. */
60700
61084
  lowFood: boolean(),
60701
61085
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
60702
61086
  * device has no battery reading. */
60703
- batteryPower: number().min(0).max(100).nullable(),
60704
- /** Days of desiccant life remaining. Null when the model has no
60705
- * desiccant sensor. */
60706
- desiccantLeftDays: number().nullable(),
61087
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61088
+ /**
61089
+ * Desiccant life remaining, stored in seconds (duration base, D944).
61090
+ * Providers convert from the days the feeder reports. Null when the
61091
+ * model has no desiccant sensor.
61092
+ */
61093
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
60707
61094
  /** True while a feed is in progress. */
60708
61095
  feeding: boolean(),
60709
61096
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -60714,7 +61101,7 @@ object({
60714
61101
  * with `errorCode` for consumers that want the raw integer. */
60715
61102
  error: string().nullable(),
60716
61103
  /** Raw device error code (0 / null = no error). */
60717
- errorCode: number().nullable(),
61104
+ errorCode: number().nullable().meta(readingMeta("none")),
60718
61105
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
60719
61106
  isDualHopper: boolean(),
60720
61107
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -60724,7 +61111,7 @@ object({
60724
61111
  /** Play a chime when dispensing. */
60725
61112
  feedSound: boolean(),
60726
61113
  /** Speaker / prompt volume level (device-scaled integer). */
60727
- volume: number(),
61114
+ volume: number().meta(readingMeta("none")),
60728
61115
  /** Ms epoch when the slice was last refreshed from the cloud. */
60729
61116
  lastFetchedAt: number()
60730
61117
  });
@@ -61161,36 +61548,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
61161
61548
  });
61162
61549
  object({
61163
61550
  /** Instantaneous power draw in watts. */
61164
- watts: number().optional().meta({
61165
- unit: "W",
61166
- label: "Power"
61167
- }),
61551
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
61168
61552
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61169
- kwhTotal: number().optional().meta({
61170
- unit: "kWh",
61171
- label: "Energy"
61172
- }),
61553
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61173
61554
  /** Voltage in volts. */
61174
- volts: number().optional(),
61555
+ volts: number().optional().meta(readingMeta("voltage")),
61175
61556
  /** Current in amperes. */
61176
- amps: number().optional(),
61177
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
61178
- va: number().optional(),
61557
+ amps: number().optional().meta(readingMeta("current")),
61558
+ /** Apparent power in volt-amperes. */
61559
+ va: number().optional().meta(readingMeta("apparent-power")),
61179
61560
  /** Power factor (0..1) if reported. */
61180
- powerFactor: number().min(0).max(1).optional(),
61561
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
61181
61562
  /** Ms epoch when the slice was last updated. */
61182
- lastFetchedAt: number(),
61183
- /** Live display unit of the single metric this slice carries (e.g. HA
61184
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
61185
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
61186
- * unit per slice is unambiguous. The UI prefers this over the role's
61187
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
61188
- unit: string().optional(),
61189
- /** Suggested decimal places for numeric display.
61190
- * Populated live from the upstream source when provided (e.g. HA
61191
- * `attributes.suggested_display_precision`). Falls back to
61192
- * auto-formatting when absent. */
61193
- precision: number().int().min(0).max(10).optional()
61563
+ lastFetchedAt: number()
61194
61564
  });
61195
61565
  DeviceType.Sensor;
61196
61566
  /**
@@ -61211,11 +61581,11 @@ DeviceType.Sensor;
61211
61581
  */
61212
61582
  var GpsLocationSchema = object({
61213
61583
  /** Latitude in decimal degrees, -90..90. */
61214
- latitude: number().min(-90).max(90),
61584
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
61215
61585
  /** Longitude in decimal degrees, -180..180. */
61216
- longitude: number().min(-180).max(180),
61586
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
61217
61587
  /** Reported accuracy in meters (lower = better). */
61218
- accuracyMeters: number().nonnegative()
61588
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
61219
61589
  });
61220
61590
  object({
61221
61591
  /** `home` / `not_home` / any user-defined zone name. */
@@ -61227,25 +61597,16 @@ object({
61227
61597
  gps: GpsLocationSchema.nullable(),
61228
61598
  /** Optional battery level of the tracking device (0..100). Null
61229
61599
  * when not reported. */
61230
- batteryPercent: number().min(0).max(100).nullable(),
61600
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61231
61601
  /** Ms epoch when the slice was last updated. */
61232
61602
  lastChangedAt: number()
61233
61603
  });
61234
61604
  DeviceType.Presence;
61235
61605
  object({
61236
- /** Current pressure in hPa. */
61237
- hpa: number(),
61606
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
61607
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
61238
61608
  /** Ms epoch when the slice was last updated. */
61239
- lastFetchedAt: number(),
61240
- /** Live display unit from the upstream source (e.g. HA
61241
- * `attributes.unit_of_measurement`). The UI prefers this over the
61242
- * role's canonical unit. Absent → fall back to the canonical unit. */
61243
- unit: string().optional(),
61244
- /** Suggested decimal places for numeric display.
61245
- * Populated live from the upstream source when provided (e.g. HA
61246
- * `attributes.suggested_display_precision`). Falls back to
61247
- * auto-formatting when absent. */
61248
- precision: number().int().min(0).max(10).optional()
61609
+ lastFetchedAt: number()
61249
61610
  });
61250
61611
  DeviceType.Sensor;
61251
61612
  /**
@@ -61578,8 +61939,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
61578
61939
  }), _void(), { kind: "mutation" });
61579
61940
  var PtzAutotrackSettingsSchema = object({
61580
61941
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
61581
- stopDelaySeconds: number().int().min(0).max(300),
61582
- disappearDelaySeconds: number().int().min(0).max(300)
61942
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
61943
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
61583
61944
  });
61584
61945
  /**
61585
61946
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -61636,7 +61997,7 @@ object({
61636
61997
  /** null = unknown — never coerced to false (D393). */
61637
61998
  running: boolean().nullable(),
61638
61999
  /** Epoch ms the current boolean began; null while `running` is null. */
61639
- since: number().nullable(),
62000
+ since: number().nullable().meta(readingMeta("none")),
61640
62001
  source: RunningStateSourceSchema.meta({ label: "Source" }),
61641
62002
  phase: string().nullable(),
61642
62003
  /** null until a reading has been published (D393). */
@@ -62834,9 +63195,9 @@ var RecordTriggerSchema = _enum([
62834
63195
  */
62835
63196
  var RecordWindowSchema = object({
62836
63197
  trigger: RecordTriggerSchema,
62837
- day: number().int().min(0).max(6),
62838
- startMinute: number().int().min(0).max(1439),
62839
- endMinute: number().int().min(1).max(1440)
63198
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
63199
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
63200
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
62840
63201
  });
62841
63202
  /** Status of one physical volume, as the camera itself describes it. */
62842
63203
  var OnboardStorageVolumeSchema = object({
@@ -62905,10 +63266,10 @@ object({
62905
63266
  /** Master "record to the card at all" switch. */
62906
63267
  enabled: boolean().nullable(),
62907
63268
  overwriteWhenFull: boolean().nullable(),
62908
- preRecordSec: number().nullable(),
62909
- postRecordSec: number().nullable(),
63269
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
63270
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
62910
63271
  /** Length of one recorded file, in minutes. */
62911
- segmentMinutes: number().nullable(),
63272
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
62912
63273
  /** The primary track's weekly windows, flattened. */
62913
63274
  windows: array(RecordWindowSchema),
62914
63275
  /**
@@ -62924,7 +63285,7 @@ object({
62924
63285
  * provider reporting a non-zero count also reports the schedule as not
62925
63286
  * writable.
62926
63287
  */
62927
- unreadableWindows: number(),
63288
+ unreadableWindows: number().meta(readingMeta("count")),
62928
63289
  /**
62929
63290
  * The camera is scheduled to record and has NO usable storage.
62930
63291
  *
@@ -63561,7 +63922,7 @@ var CamStreamDescriptorSchema = object({
63561
63922
  url: string().optional(),
63562
63923
  codec: string().optional(),
63563
63924
  resolution: CamStreamResolutionSchema.optional(),
63564
- fps: number().positive().optional(),
63925
+ fps: number().positive().optional().meta(readingMeta("rate")),
63565
63926
  label: string().optional(),
63566
63927
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
63567
63928
  deviceFeatures: array(string()).optional(),
@@ -63591,18 +63952,18 @@ var StreamProfileSchema = _enum([
63591
63952
  ]);
63592
63953
  /** The current encoding config of one stream profile. */
63593
63954
  var StreamProfileConfigSchema = object({
63594
- width: number(),
63595
- height: number(),
63955
+ width: number().meta(readingMeta("none")),
63956
+ height: number().meta(readingMeta("none")),
63596
63957
  codec: _enum(["h264", "h265"]),
63597
- framerate: number(),
63598
- bitrate: number(),
63958
+ framerate: number().meta(readingMeta("rate")),
63959
+ bitrate: number().meta(readingMeta("bitrate")),
63599
63960
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
63600
63961
  encoderProfile: _enum([
63601
63962
  "high",
63602
63963
  "main",
63603
63964
  "baseline"
63604
63965
  ]).optional(),
63605
- gop: number().optional(),
63966
+ gop: number().optional().meta(readingMeta("none")),
63606
63967
  /**
63607
63968
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
63608
63969
  *
@@ -64245,23 +64606,13 @@ object({
64245
64606
  });
64246
64607
  DeviceType.Sensor;
64247
64608
  object({
64248
- /** Current temperature in Celsius. */
64249
- celsius: number().meta({
64250
- unit: "°C",
64609
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64610
+ celsius: number().nullable().meta(readingMeta("temperature", {
64611
+ precision: 1,
64251
64612
  label: "Temperature"
64252
- }),
64613
+ })),
64253
64614
  /** Ms epoch when the slice was last updated (push or poll). */
64254
- lastFetchedAt: number(),
64255
- /** Live display unit from the upstream source (e.g. HA
64256
- * `attributes.unit_of_measurement`). The UI prefers this over the
64257
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
64258
- * Absent → the UI falls back to the role's canonical unit. */
64259
- unit: string().optional(),
64260
- /** Suggested decimal places for numeric display.
64261
- * Populated live from the upstream source when provided (e.g. HA
64262
- * `attributes.suggested_display_precision`). Falls back to
64263
- * auto-formatting when absent. */
64264
- precision: number().int().min(0).max(10).optional()
64615
+ lastFetchedAt: number()
64265
64616
  });
64266
64617
  DeviceType.Sensor, DeviceType.Thermostat;
64267
64618
  /**
@@ -64513,16 +64864,16 @@ object({
64513
64864
  ]),
64514
64865
  /** 0..100 battery percentage. Null when the device has no battery
64515
64866
  * reading. */
64516
- batteryLevel: number().min(0).max(100).nullable(),
64867
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64517
64868
  /** Current fan-speed token (provider-verbatim). Null when unknown or
64518
64869
  * the vacuum has no speed control. */
64519
64870
  fanSpeed: string().nullable(),
64520
64871
  /** Speed tokens the hardware accepts — drives the UI selector. */
64521
64872
  availableFanSpeeds: array(string()),
64522
64873
  /** 0..100 cleaning-completion percentage of the current task, or null. */
64523
- progressPercent: number().min(0).max(100).nullable(),
64874
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64524
64875
  /** Current error code (0 / null = no error). */
64525
- errorCode: number().nullable(),
64876
+ errorCode: number().nullable().meta(readingMeta("none")),
64526
64877
  /** Human label for {@link errorCode}, or null when none / undecodable. */
64527
64878
  errorLabel: string().nullable(),
64528
64879
  /** Ms epoch when the slice was last updated. */
@@ -64561,7 +64912,7 @@ object({
64561
64912
  ]),
64562
64913
  /** 0 = fully closed, 100 = fully open. Null when the device has no
64563
64914
  * intermediate position surface. */
64564
- position: number().min(0).max(100).nullable(),
64915
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64565
64916
  /** Ms epoch when the slice was last updated. */
64566
64917
  lastChangedAt: number()
64567
64918
  });
@@ -64589,9 +64940,9 @@ object({
64589
64940
  DeviceType.Sensor;
64590
64941
  object({
64591
64942
  /** Current measured temperature. Null when not reported. */
64592
- currentTemp: number().nullable(),
64943
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
64593
64944
  /** Target temperature setpoint. Null when no setpoint surface. */
64594
- targetTemp: number().nullable(),
64945
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
64595
64946
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
64596
64947
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
64597
64948
  * device reports an unknown state. */
@@ -64602,9 +64953,9 @@ object({
64602
64953
  * has no away surface. */
64603
64954
  away: boolean().nullable(),
64604
64955
  /** HA `min_temp` attribute. Null when not reported. */
64605
- minTemp: number().nullable(),
64956
+ minTemp: number().nullable().meta(readingMeta("temperature")),
64606
64957
  /** HA `max_temp` attribute. Null when not reported. */
64607
- maxTemp: number().nullable(),
64958
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
64608
64959
  /** Ms epoch when the slice was last updated. */
64609
64960
  lastChangedAt: number()
64610
64961
  });
@@ -64628,25 +64979,18 @@ DeviceType.WaterHeater, method(object({
64628
64979
  auth: "protected"
64629
64980
  });
64630
64981
  object({
64631
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
64632
- * when no condition has been reported yet. */
64982
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
64633
64983
  condition: string().nullable(),
64634
- /** Current temperature in the reported unit. Null when not provided. */
64635
- temperature: number().nullable(),
64636
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
64637
- temperatureUnit: string().nullable(),
64984
+ /** Current temperature in Celsius. Null when not provided or refused. */
64985
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
64638
64986
  /** Relative humidity (0..100). Null when not provided. */
64639
- humidity: number().min(0).max(100).nullable(),
64640
- /** Barometric pressure in the reported unit. Null when not provided. */
64641
- pressure: number().nullable(),
64642
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
64643
- pressureUnit: string().nullable(),
64644
- /** Wind speed in the reported unit. Null when not provided. */
64645
- windSpeed: number().nullable(),
64646
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
64647
- windSpeedUnit: string().nullable(),
64987
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
64988
+ /** Barometric pressure in hPa. Null when not provided or refused. */
64989
+ pressure: number().nullable().meta(readingMeta("pressure")),
64990
+ /** Wind speed in metres per second. Null when not provided or refused. */
64991
+ windSpeed: number().nullable().meta(readingMeta("speed")),
64648
64992
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
64649
- windBearing: number().nullable(),
64993
+ windBearing: number().nullable().meta(readingMeta("angle")),
64650
64994
  /** Ms epoch when the slice was last updated. */
64651
64995
  lastFetchedAt: number()
64652
64996
  });
@@ -65558,6 +65902,13 @@ Object.freeze({
65558
65902
  access: "view",
65559
65903
  role: "admin"
65560
65904
  },
65905
+ "addons.getPackageDocs": {
65906
+ capName: "addons",
65907
+ capScope: "system",
65908
+ addonId: null,
65909
+ access: "view",
65910
+ role: "admin"
65911
+ },
65561
65912
  "addons.getVersions": {
65562
65913
  capName: "addons",
65563
65914
  capScope: "system",
@@ -68666,6 +69017,20 @@ Object.freeze({
68666
69017
  access: "view",
68667
69018
  role: "viewer"
68668
69019
  },
69020
+ "groupCommands.getStatus": {
69021
+ capName: "group-commands",
69022
+ capScope: "device",
69023
+ addonId: null,
69024
+ access: "view",
69025
+ role: "viewer"
69026
+ },
69027
+ "groupStatus.getStatus": {
69028
+ capName: "group-status",
69029
+ capScope: "device",
69030
+ addonId: null,
69031
+ access: "view",
69032
+ role: "viewer"
69033
+ },
68669
69034
  "heatingValve.getStatus": {
68670
69035
  capName: "heating-valve",
68671
69036
  capScope: "device",
@@ -78488,6 +78853,37 @@ var SENSOR_CAP_VOCABULARY = {
78488
78853
  icon: "flame"
78489
78854
  }]
78490
78855
  },
78856
+ "group-commands": {
78857
+ scope: "device",
78858
+ noun: t("Group command", "Comando di gruppo")
78859
+ },
78860
+ "group-status": {
78861
+ scope: "device",
78862
+ noun: t("Group", "Gruppo"),
78863
+ state: [
78864
+ {
78865
+ kind: "numeric",
78866
+ field: "included",
78867
+ quantity: t("the members reporting", "i membri che rispondono"),
78868
+ label: t("Reporting", "Rispondono"),
78869
+ icon: "users"
78870
+ },
78871
+ {
78872
+ kind: "numeric",
78873
+ field: "excluded",
78874
+ quantity: t("the members left out", "i membri esclusi"),
78875
+ label: t("Excluded", "Esclusi"),
78876
+ icon: "users"
78877
+ },
78878
+ {
78879
+ kind: "numeric",
78880
+ field: "total",
78881
+ quantity: t("the member count", "il numero di membri"),
78882
+ label: t("Members", "Membri"),
78883
+ icon: "sigma"
78884
+ }
78885
+ ]
78886
+ },
78491
78887
  "humidity-sensor": {
78492
78888
  scope: "device",
78493
78889
  noun: t("Humidity", "Umidità"),
@@ -78521,7 +78917,6 @@ var SENSOR_CAP_VOCABULARY = {
78521
78917
  field: "value",
78522
78918
  quantity: t("the reading", "il valore"),
78523
78919
  label: t("Reading", "Valore"),
78524
- unitField: "unit",
78525
78920
  icon: "gauge"
78526
78921
  }]
78527
78922
  },
@@ -78657,7 +79052,7 @@ var SENSOR_CAP_VOCABULARY = {
78657
79052
  field: "temperature",
78658
79053
  quantity: t("the outdoor temperature", "la temperatura esterna"),
78659
79054
  label: t("Temperature", "Temperatura"),
78660
- unitField: "temperatureUnit",
79055
+ unit: "°C",
78661
79056
  icon: "thermometer"
78662
79057
  },
78663
79058
  {
@@ -78673,7 +79068,7 @@ var SENSOR_CAP_VOCABULARY = {
78673
79068
  field: "windSpeed",
78674
79069
  quantity: t("the wind speed", "la velocità del vento"),
78675
79070
  label: t("Wind", "Vento"),
78676
- unitField: "windSpeedUnit",
79071
+ unit: "m/s",
78677
79072
  icon: "wind"
78678
79073
  },
78679
79074
  {
@@ -78681,7 +79076,7 @@ var SENSOR_CAP_VOCABULARY = {
78681
79076
  field: "pressure",
78682
79077
  quantity: t("the air pressure", "la pressione atmosferica"),
78683
79078
  label: t("Pressure", "Pressione"),
78684
- unitField: "pressureUnit",
79079
+ unit: "hPa",
78685
79080
  icon: "gauge"
78686
79081
  }
78687
79082
  ]
@@ -79954,6 +80349,16 @@ Object.freeze({
79954
80349
  form: "single",
79955
80350
  optional: false
79956
80351
  }],
80352
+ "groupCommands.getStatus": [{
80353
+ name: "deviceId",
80354
+ form: "single",
80355
+ optional: false
80356
+ }],
80357
+ "groupStatus.getStatus": [{
80358
+ name: "deviceId",
80359
+ form: "single",
80360
+ optional: false
80361
+ }],
79957
80362
  "heatingValve.getStatus": [{
79958
80363
  name: "deviceId",
79959
80364
  form: "single",
@@ -81818,6 +82223,11 @@ var PulseOverrideDraftSchema = object({
81818
82223
  targetId: string().min(1),
81819
82224
  enabled: boolean(),
81820
82225
  debounceMs: number().nonnegative(),
82226
+ edges: array(_enum([
82227
+ "rising",
82228
+ "button",
82229
+ "native-event"
82230
+ ])).min(1).optional(),
81821
82231
  sources: array(OverrideSourceSchema).min(1)
81822
82232
  });
81823
82233
  var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
@@ -82140,6 +82550,8 @@ new Map(Object.entries({
82140
82550
  "detection-blind": "device-events",
82141
82551
  "motion-noisy": "device-events",
82142
82552
  "motion-calm": "device-events",
82553
+ "package-llm-silent": "device-events",
82554
+ "package-llm-answered": "device-events",
82143
82555
  "recording-stalled": "device-events",
82144
82556
  "recording-healthy": "device-events",
82145
82557
  "recording-degraded": "device-events",
@@ -82223,6 +82635,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
82223
82635
  label: "Motion without a subject",
82224
82636
  kinds: ["motion-noisy", "motion-calm"]
82225
82637
  },
82638
+ {
82639
+ id: "package-llm",
82640
+ family: "device-events",
82641
+ label: "Package confirm",
82642
+ kinds: ["package-llm-silent", "package-llm-answered"]
82643
+ },
82226
82644
  {
82227
82645
  id: "recording",
82228
82646
  family: "device-events",
@@ -82404,6 +82822,8 @@ var DEVICE_EVENTS = [
82404
82822
  "detection-blind",
82405
82823
  "motion-noisy",
82406
82824
  "motion-calm",
82825
+ "package-llm-silent",
82826
+ "package-llm-answered",
82407
82827
  "recording-stalled",
82408
82828
  "recording-healthy",
82409
82829
  "recording-degraded",
@@ -82672,6 +83092,15 @@ new Map([
82672
83092
  snapshotLabel: "Cameras with constant motion now",
82673
83093
  snapshotSection: null
82674
83094
  },
83095
+ {
83096
+ id: "package-llm-confirm",
83097
+ family: "device-events",
83098
+ label: "Package confirm (not answering / answering again)",
83099
+ bad: ["package-llm-silent"],
83100
+ recovery: ["package-llm-answered"],
83101
+ snapshotLabel: "Cameras whose package confirm is not answering",
83102
+ snapshotSection: null
83103
+ },
82675
83104
  {
82676
83105
  id: "recording-health",
82677
83106
  family: "device-events",