@camstack/addon-google-services 0.2.30 → 0.2.32

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.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  let node_crypto = require("node:crypto");
6
- //#region ../types/dist/event-category-DHXxB8t9.mjs
6
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
7
7
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
8
8
  EventCategory["SystemBoot"] = "system.boot";
9
9
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -329,6 +329,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
329
329
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
330
330
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
331
331
  /**
332
+ * A camera's package LLM confirm has failed closed three times in a row
333
+ * (D931, 2026-10-26). One edge per camera until
334
+ * {@link PipelinePackageLlmAnswered}.
335
+ */
336
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
337
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
338
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
339
+ /**
332
340
  * Per-camera pipeline config was mutated by the orchestrator
333
341
  * (a settings change via `setAgentInferenceDevices` /
334
342
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5571,7 +5579,7 @@ var ZodIssueCode = {
5571
5579
  var ZodFirstPartyTypeKind;
5572
5580
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5573
5581
  //#endregion
5574
- //#region ../types/dist/sleep-BErAxhul.mjs
5582
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5575
5583
  /**
5576
5584
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5577
5585
  * window to float samples (D455).
@@ -8313,6 +8321,99 @@ function defineEntryKind(name, spec) {
8313
8321
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8314
8322
  return defined;
8315
8323
  }
8324
+ var READING_QUANTITIES = [
8325
+ "temperature",
8326
+ "pressure",
8327
+ "speed",
8328
+ "precipitation",
8329
+ "precipitation-rate",
8330
+ "length",
8331
+ "area",
8332
+ "illuminance",
8333
+ "irradiance",
8334
+ "power",
8335
+ "energy",
8336
+ "voltage",
8337
+ "current",
8338
+ "apparent-power",
8339
+ "humidity",
8340
+ "percentage",
8341
+ "concentration-volume",
8342
+ "concentration-mass",
8343
+ "concentration-ppb",
8344
+ "volume",
8345
+ "duration",
8346
+ "duration-ms",
8347
+ "information",
8348
+ "sound-level",
8349
+ "signal-level",
8350
+ "colour-temperature",
8351
+ "angle",
8352
+ "bitrate",
8353
+ "rate",
8354
+ "mass",
8355
+ "index",
8356
+ "ratio",
8357
+ "count",
8358
+ "none",
8359
+ "device"
8360
+ ];
8361
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8362
+ new Set(READING_QUANTITIES);
8363
+ /**
8364
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8365
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8366
+ */
8367
+ var QUANTITY_BASE = {
8368
+ temperature: "°C",
8369
+ pressure: "hPa",
8370
+ speed: "m/s",
8371
+ precipitation: "mm",
8372
+ "precipitation-rate": "mm/h",
8373
+ length: "m",
8374
+ area: "m²",
8375
+ illuminance: "lx",
8376
+ irradiance: "W/m²",
8377
+ power: "W",
8378
+ energy: "kWh",
8379
+ voltage: "V",
8380
+ current: "A",
8381
+ "apparent-power": "VA",
8382
+ humidity: "%",
8383
+ percentage: "%",
8384
+ "concentration-volume": "ppm",
8385
+ "concentration-mass": "µg/m³",
8386
+ "concentration-ppb": "ppb",
8387
+ volume: "L",
8388
+ duration: "s",
8389
+ "duration-ms": "ms",
8390
+ information: "B",
8391
+ "sound-level": "dBFS",
8392
+ "signal-level": "dBm",
8393
+ "colour-temperature": "mired",
8394
+ angle: "°",
8395
+ bitrate: "kbps",
8396
+ rate: "fps",
8397
+ mass: "kg",
8398
+ index: "none",
8399
+ ratio: "none",
8400
+ count: "none",
8401
+ none: "none",
8402
+ device: "device"
8403
+ };
8404
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8405
+ function baseUnitOf(quantity) {
8406
+ const symbol = QUANTITY_BASE[quantity];
8407
+ if (symbol === "none" || symbol === "device") return void 0;
8408
+ return symbol;
8409
+ }
8410
+ function readingMeta(quantity, extras) {
8411
+ return {
8412
+ quantity,
8413
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8414
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8415
+ };
8416
+ }
8316
8417
  /** Shared Zod schemas used across streaming capabilities. */
8317
8418
  var CamProfileSchema = _enum([
8318
8419
  "high",
@@ -8340,8 +8441,8 @@ var CamStreamKindSchema = _enum([
8340
8441
  "derived"
8341
8442
  ]);
8342
8443
  var CamStreamResolutionSchema = object({
8343
- width: number().int().positive(),
8344
- height: number().int().positive()
8444
+ width: number().int().positive().meta(readingMeta("none")),
8445
+ height: number().int().positive().meta(readingMeta("none"))
8345
8446
  });
8346
8447
  var CameraStreamSchema = object({
8347
8448
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8855,6 +8956,8 @@ var CAP_NAMES = Object.freeze([
8855
8956
  "filesystem-browse",
8856
8957
  "flood",
8857
8958
  "gas",
8959
+ "group-commands",
8960
+ "group-status",
8858
8961
  "heating-valve",
8859
8962
  "humidifier",
8860
8963
  "humidity-sensor",
@@ -8984,14 +9087,6 @@ Object.freeze({
8984
9087
  {
8985
9088
  "path": "aqi",
8986
9089
  "valueType": "number"
8987
- },
8988
- {
8989
- "path": "unit",
8990
- "valueType": "string"
8991
- },
8992
- {
8993
- "path": "precision",
8994
- "valueType": "number"
8995
9090
  }
8996
9091
  ],
8997
9092
  "alarm-panel": [
@@ -9027,20 +9122,10 @@ Object.freeze({
9027
9122
  "valueType": "boolean"
9028
9123
  }
9029
9124
  ],
9030
- "ambient-light-sensor": [
9031
- {
9032
- "path": "lux",
9033
- "valueType": "number"
9034
- },
9035
- {
9036
- "path": "unit",
9037
- "valueType": "string"
9038
- },
9039
- {
9040
- "path": "precision",
9041
- "valueType": "number"
9042
- }
9043
- ],
9125
+ "ambient-light-sensor": [{
9126
+ "path": "lux",
9127
+ "valueType": "number"
9128
+ }],
9044
9129
  "audio-metrics": [
9045
9130
  {
9046
9131
  "path": "ts",
@@ -9120,6 +9205,10 @@ Object.freeze({
9120
9205
  "path": "lastUpdated",
9121
9206
  "valueType": "number"
9122
9207
  },
9208
+ {
9209
+ "path": "volts",
9210
+ "valueType": "number"
9211
+ },
9123
9212
  {
9124
9213
  "path": "lastContactAt",
9125
9214
  "valueType": "number"
@@ -9362,10 +9451,6 @@ Object.freeze({
9362
9451
  "datetime"
9363
9452
  ]
9364
9453
  },
9365
- {
9366
- "path": "unit",
9367
- "valueType": "string"
9368
- },
9369
9454
  {
9370
9455
  "path": "min",
9371
9456
  "valueType": "number"
@@ -9377,19 +9462,6 @@ Object.freeze({
9377
9462
  {
9378
9463
  "path": "step",
9379
9464
  "valueType": "number"
9380
- },
9381
- {
9382
- "path": "precision",
9383
- "valueType": "number"
9384
- },
9385
- {
9386
- "path": "format",
9387
- "valueType": "enum",
9388
- "enumValues": [
9389
- "date",
9390
- "time",
9391
- "datetime"
9392
- ]
9393
9465
  }
9394
9466
  ],
9395
9467
  "cover": [
@@ -9630,13 +9702,8 @@ Object.freeze({
9630
9702
  "path": "value",
9631
9703
  "valueType": "string"
9632
9704
  }, {
9633
- "path": "format",
9634
- "valueType": "enum",
9635
- "enumValues": [
9636
- "date",
9637
- "time",
9638
- "datetime"
9639
- ]
9705
+ "path": "timestamp",
9706
+ "valueType": "boolean"
9640
9707
  }],
9641
9708
  "event-emitter": [
9642
9709
  {
@@ -9709,6 +9776,20 @@ Object.freeze({
9709
9776
  "path": "detected",
9710
9777
  "valueType": "boolean"
9711
9778
  }],
9779
+ "group-status": [
9780
+ {
9781
+ "path": "included",
9782
+ "valueType": "number"
9783
+ },
9784
+ {
9785
+ "path": "excluded",
9786
+ "valueType": "number"
9787
+ },
9788
+ {
9789
+ "path": "total",
9790
+ "valueType": "number"
9791
+ }
9792
+ ],
9712
9793
  "heating-valve": [{
9713
9794
  "path": "position",
9714
9795
  "valueType": "number"
@@ -9743,20 +9824,10 @@ Object.freeze({
9743
9824
  "valueType": "number"
9744
9825
  }
9745
9826
  ],
9746
- "humidity-sensor": [
9747
- {
9748
- "path": "percent",
9749
- "valueType": "number"
9750
- },
9751
- {
9752
- "path": "unit",
9753
- "valueType": "string"
9754
- },
9755
- {
9756
- "path": "precision",
9757
- "valueType": "number"
9758
- }
9759
- ],
9827
+ "humidity-sensor": [{
9828
+ "path": "percent",
9829
+ "valueType": "number"
9830
+ }],
9760
9831
  "image": [{
9761
9832
  "path": "url",
9762
9833
  "valueType": "string"
@@ -10095,20 +10166,10 @@ Object.freeze({
10095
10166
  "valueType": "boolean"
10096
10167
  }
10097
10168
  ],
10098
- "numeric-sensor": [
10099
- {
10100
- "path": "value",
10101
- "valueType": "number"
10102
- },
10103
- {
10104
- "path": "unit",
10105
- "valueType": "string"
10106
- },
10107
- {
10108
- "path": "precision",
10109
- "valueType": "number"
10110
- }
10111
- ],
10169
+ "numeric-sensor": [{
10170
+ "path": "value",
10171
+ "valueType": "number"
10172
+ }],
10112
10173
  "pet-feeder": [
10113
10174
  {
10114
10175
  "path": "foodLevel",
@@ -10200,14 +10261,6 @@ Object.freeze({
10200
10261
  {
10201
10262
  "path": "powerFactor",
10202
10263
  "valueType": "number"
10203
- },
10204
- {
10205
- "path": "unit",
10206
- "valueType": "string"
10207
- },
10208
- {
10209
- "path": "precision",
10210
- "valueType": "number"
10211
10264
  }
10212
10265
  ],
10213
10266
  "presence": [
@@ -10236,20 +10289,10 @@ Object.freeze({
10236
10289
  "valueType": "number"
10237
10290
  }
10238
10291
  ],
10239
- "pressure-sensor": [
10240
- {
10241
- "path": "hpa",
10242
- "valueType": "number"
10243
- },
10244
- {
10245
- "path": "unit",
10246
- "valueType": "string"
10247
- },
10248
- {
10249
- "path": "precision",
10250
- "valueType": "number"
10251
- }
10252
- ],
10292
+ "pressure-sensor": [{
10293
+ "path": "hpa",
10294
+ "valueType": "number"
10295
+ }],
10253
10296
  "privacy-mask": [{
10254
10297
  "path": "enabled",
10255
10298
  "valueType": "boolean"
@@ -10560,20 +10603,10 @@ Object.freeze({
10560
10603
  "path": "tampered",
10561
10604
  "valueType": "boolean"
10562
10605
  }],
10563
- "temperature-sensor": [
10564
- {
10565
- "path": "celsius",
10566
- "valueType": "number"
10567
- },
10568
- {
10569
- "path": "unit",
10570
- "valueType": "string"
10571
- },
10572
- {
10573
- "path": "precision",
10574
- "valueType": "number"
10575
- }
10576
- ],
10606
+ "temperature-sensor": [{
10607
+ "path": "celsius",
10608
+ "valueType": "number"
10609
+ }],
10577
10610
  "update": [
10578
10611
  {
10579
10612
  "path": "currentVersion",
@@ -10684,10 +10717,6 @@ Object.freeze({
10684
10717
  "path": "temperature",
10685
10718
  "valueType": "number"
10686
10719
  },
10687
- {
10688
- "path": "temperatureUnit",
10689
- "valueType": "string"
10690
- },
10691
10720
  {
10692
10721
  "path": "humidity",
10693
10722
  "valueType": "number"
@@ -10696,18 +10725,10 @@ Object.freeze({
10696
10725
  "path": "pressure",
10697
10726
  "valueType": "number"
10698
10727
  },
10699
- {
10700
- "path": "pressureUnit",
10701
- "valueType": "string"
10702
- },
10703
10728
  {
10704
10729
  "path": "windSpeed",
10705
10730
  "valueType": "number"
10706
10731
  },
10707
- {
10708
- "path": "windSpeedUnit",
10709
- "valueType": "string"
10710
- },
10711
10732
  {
10712
10733
  "path": "windBearing",
10713
10734
  "valueType": "number"
@@ -11114,7 +11135,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11114
11135
  return DeviceRole;
11115
11136
  }({});
11116
11137
  //#endregion
11117
- //#region ../types/dist/composition-kEl9mevu.mjs
11138
+ //#region ../types/dist/composition-OhpuespA.mjs
11118
11139
  /**
11119
11140
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11120
11141
  *
@@ -11869,15 +11890,74 @@ function validateExpressionSource(src) {
11869
11890
  }
11870
11891
  return null;
11871
11892
  }
11872
- /** A composed device's stableId is this prefix plus the block id. */
11873
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11874
- /** Longest snippet a `code` source may carry (slice 4). */
11875
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11893
+ /**
11894
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11895
+ * id. Split out so helper schemas can name it without a cycle through
11896
+ * `composition.ts` (D659).
11897
+ */
11876
11898
  var CompositionSourceRefSchema = object({
11877
11899
  /** The owning addon; `stableId` is unique only within it. */
11878
11900
  addonId: string().min(1),
11879
11901
  stableId: string().min(1)
11880
11902
  });
11903
+ var GROUP_TEMPLATE_IDS = [
11904
+ "temperature",
11905
+ "humidity",
11906
+ "pressure",
11907
+ "illuminance",
11908
+ "power",
11909
+ "energy",
11910
+ "battery",
11911
+ "numeric",
11912
+ "motion",
11913
+ "contact",
11914
+ "leak",
11915
+ "smoke",
11916
+ "occupancy",
11917
+ "binary",
11918
+ "switch",
11919
+ "light"
11920
+ ];
11921
+ var NUMERIC_AGGREGATES = [
11922
+ "mean",
11923
+ "median",
11924
+ "min",
11925
+ "max",
11926
+ "sum"
11927
+ ];
11928
+ var BINARY_AGGREGATES = ["any", "all"];
11929
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11930
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11931
+ var AGGREGATE_REDUCERS = [
11932
+ "mean",
11933
+ "median",
11934
+ "min",
11935
+ "max",
11936
+ "sum",
11937
+ "any",
11938
+ "all"
11939
+ ];
11940
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11941
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11942
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11943
+ /** One member. The field is stored so occupancy can name motion or binary. */
11944
+ var HelperMemberSchema = object({
11945
+ source: CompositionSourceRefSchema,
11946
+ cap: string().min(1),
11947
+ fieldPath: string().min(1)
11948
+ });
11949
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
11950
+ kind: literal("group"),
11951
+ templateId: GroupTemplateIdSchema,
11952
+ aggregate: AggregateReducerSchema,
11953
+ members: array(HelperMemberSchema).min(1).max(64),
11954
+ /** Actuator groups only. Readings leave this unset. */
11955
+ active: ActiveCriterionSchema.optional()
11956
+ })]);
11957
+ /** A composed device's stableId is this prefix plus the block id. */
11958
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11959
+ /** Longest snippet a `code` source may carry (slice 4). */
11960
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11881
11961
  /** One field of one capability of one source device. */
11882
11962
  var CompositionFieldReadSchema = object({
11883
11963
  source: CompositionSourceRefSchema,
@@ -11921,7 +12001,33 @@ var CompositionCodeSourceSchema = object({
11921
12001
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11922
12002
  CompositionFromSourceSchema,
11923
12003
  CompositionExpressionSourceSchema,
11924
- CompositionCodeSourceSchema
12004
+ CompositionCodeSourceSchema,
12005
+ object({
12006
+ kind: literal("aggregate"),
12007
+ reducer: _enum([
12008
+ "mean",
12009
+ "median",
12010
+ "min",
12011
+ "max",
12012
+ "sum",
12013
+ "any",
12014
+ "all"
12015
+ ]),
12016
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
12017
+ /** `value` is the reading; the others are the member counts. */
12018
+ publish: _enum([
12019
+ "value",
12020
+ "included",
12021
+ "excluded",
12022
+ "total"
12023
+ ]),
12024
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
12025
+ empty: _enum([
12026
+ "null",
12027
+ "omit",
12028
+ "offline"
12029
+ ])
12030
+ })
11925
12031
  ]);
11926
12032
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11927
12033
  kind: literal("forward"),
@@ -11967,7 +12073,13 @@ var CompositionSchema = object({
11967
12073
  kind: literal("existing"),
11968
12074
  device: CompositionSourceRefSchema
11969
12075
  })]),
11970
- features: array(CompositionFeatureSchema).min(1).max(16)
12076
+ features: array(CompositionFeatureSchema).min(1).max(16),
12077
+ /**
12078
+ * Present when the block is a helper template. Absent on a free
12079
+ * composition (the room example, device 4603). The editor reads this,
12080
+ * never the compiled features.
12081
+ */
12082
+ helper: HelperSpecSchema.optional()
11971
12083
  }).superRefine((composition, ctx) => {
11972
12084
  const seen = /* @__PURE__ */ new Set();
11973
12085
  composition.features.forEach((feature, index) => {
@@ -18254,7 +18366,11 @@ var CompositionProblemSchema = object({
18254
18366
  "item-field-reserved",
18255
18367
  "target-missing",
18256
18368
  "target-unreadable",
18257
- "not-replaceable"
18369
+ "not-replaceable",
18370
+ "native-capability-replace",
18371
+ "mixed-quantity",
18372
+ "helper-mismatch",
18373
+ "member-constraint"
18258
18374
  ]),
18259
18375
  path: string(),
18260
18376
  message: string()
@@ -18275,7 +18391,8 @@ var CompositionUnavailableModeSchema = _enum([
18275
18391
  "null",
18276
18392
  "offline",
18277
18393
  "unknown",
18278
- "release"
18394
+ "release",
18395
+ "omit"
18279
18396
  ]);
18280
18397
  /**
18281
18398
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -18312,9 +18429,11 @@ var CompositionFieldPlanSchema = object({
18312
18429
  reads: array(CompositionFieldReadSchema)
18313
18430
  });
18314
18431
  /**
18315
- * What a feature does to its target (D663): `new` builds a capability on a new
18316
- * device; on an existing device it `add`s a capability the device lacks, or
18317
- * `replace`s named fields of one it has natively.
18432
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
18433
+ * new device; on an existing device it `add`s a capability the device lacks.
18434
+ * Replacing a capability the device already serves is refused
18435
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
18436
+ * wire for a claim the hub already holds.
18318
18437
  */
18319
18438
  var CompositionFeatureModeSchema = _enum([
18320
18439
  "new",
@@ -18487,6 +18606,275 @@ Object.freeze({
18487
18606
  result: ["number", "null"]
18488
18607
  }
18489
18608
  });
18609
+ /**
18610
+ * One catalog row per base reading a group helper can build (D944 §3).
18611
+ * Data, not a form default written into the block.
18612
+ */
18613
+ var NOW = {
18614
+ fieldPath: "lastFetchedAt",
18615
+ expr: "now"
18616
+ };
18617
+ var CHANGED = {
18618
+ fieldPath: "lastChangedAt",
18619
+ expr: "now"
18620
+ };
18621
+ function numeric(input) {
18622
+ return {
18623
+ ...input,
18624
+ family: "numeric",
18625
+ deviceType: DeviceType.Sensor,
18626
+ memberDeviceType: DeviceType.Sensor,
18627
+ commandCaps: [],
18628
+ baseUnit: baseUnitOf(input.quantity),
18629
+ aggregates: NUMERIC_AGGREGATES,
18630
+ accept: [{
18631
+ cap: input.outputCap,
18632
+ fieldPath: input.outputField
18633
+ }]
18634
+ };
18635
+ }
18636
+ function binary(input) {
18637
+ return {
18638
+ ...input,
18639
+ family: "binary",
18640
+ deviceType: DeviceType.Sensor,
18641
+ memberDeviceType: DeviceType.Sensor,
18642
+ commandCaps: [],
18643
+ quantity: "none",
18644
+ baseUnit: void 0,
18645
+ aggregates: BINARY_AGGREGATES,
18646
+ empty: "offline"
18647
+ };
18648
+ }
18649
+ var GROUP_TEMPLATES = [
18650
+ numeric({
18651
+ id: "temperature",
18652
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18653
+ role: DeviceRole.TemperatureSensor,
18654
+ outputCap: "temperature-sensor",
18655
+ outputField: "celsius",
18656
+ quantity: "temperature",
18657
+ defaultAggregate: "mean",
18658
+ empty: "null",
18659
+ companions: [NOW]
18660
+ }),
18661
+ numeric({
18662
+ id: "humidity",
18663
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18664
+ role: DeviceRole.HumiditySensor,
18665
+ outputCap: "humidity-sensor",
18666
+ outputField: "percent",
18667
+ quantity: "humidity",
18668
+ defaultAggregate: "mean",
18669
+ empty: "null",
18670
+ companions: [NOW]
18671
+ }),
18672
+ numeric({
18673
+ id: "pressure",
18674
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18675
+ role: DeviceRole.PressureSensor,
18676
+ outputCap: "pressure-sensor",
18677
+ outputField: "hpa",
18678
+ quantity: "pressure",
18679
+ defaultAggregate: "mean",
18680
+ empty: "null",
18681
+ companions: [NOW]
18682
+ }),
18683
+ numeric({
18684
+ id: "illuminance",
18685
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18686
+ role: DeviceRole.AmbientLightSensor,
18687
+ outputCap: "ambient-light-sensor",
18688
+ outputField: "lux",
18689
+ quantity: "illuminance",
18690
+ defaultAggregate: "mean",
18691
+ empty: "null",
18692
+ companions: [NOW]
18693
+ }),
18694
+ numeric({
18695
+ id: "power",
18696
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18697
+ role: DeviceRole.PowerSensor,
18698
+ outputCap: "power-meter",
18699
+ outputField: "watts",
18700
+ quantity: "power",
18701
+ defaultAggregate: "sum",
18702
+ empty: "omit",
18703
+ companions: [NOW]
18704
+ }),
18705
+ numeric({
18706
+ id: "energy",
18707
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18708
+ role: DeviceRole.EnergySensor,
18709
+ outputCap: "power-meter",
18710
+ outputField: "kwhTotal",
18711
+ quantity: "energy",
18712
+ defaultAggregate: "sum",
18713
+ empty: "omit",
18714
+ companions: [NOW]
18715
+ }),
18716
+ numeric({
18717
+ id: "battery",
18718
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18719
+ role: DeviceRole.BatterySensor,
18720
+ outputCap: "battery",
18721
+ outputField: "percentage",
18722
+ quantity: "percentage",
18723
+ defaultAggregate: "mean",
18724
+ empty: "null",
18725
+ companions: [
18726
+ {
18727
+ fieldPath: "charging",
18728
+ expr: "\"unknown\""
18729
+ },
18730
+ {
18731
+ fieldPath: "sleeping",
18732
+ expr: "false"
18733
+ },
18734
+ {
18735
+ fieldPath: "lastUpdated",
18736
+ expr: "now"
18737
+ }
18738
+ ]
18739
+ }),
18740
+ numeric({
18741
+ id: "numeric",
18742
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18743
+ role: DeviceRole.NumericSensor,
18744
+ outputCap: "numeric-sensor",
18745
+ outputField: "value",
18746
+ quantity: "device",
18747
+ defaultAggregate: "mean",
18748
+ empty: "null",
18749
+ companions: [NOW]
18750
+ }),
18751
+ binary({
18752
+ id: "motion",
18753
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18754
+ role: DeviceRole.MotionSensor,
18755
+ outputCap: "motion",
18756
+ outputField: "detected",
18757
+ defaultAggregate: "any",
18758
+ accept: [{
18759
+ cap: "motion",
18760
+ fieldPath: "detected"
18761
+ }],
18762
+ companions: [{
18763
+ fieldPath: "lastDetectedAt",
18764
+ expr: "null"
18765
+ }, {
18766
+ fieldPath: "autoClearAfterMs",
18767
+ expr: "null"
18768
+ }]
18769
+ }),
18770
+ binary({
18771
+ id: "contact",
18772
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18773
+ role: DeviceRole.ContactSensor,
18774
+ outputCap: "contact",
18775
+ outputField: "entryOpen",
18776
+ defaultAggregate: "any",
18777
+ accept: [{
18778
+ cap: "contact",
18779
+ fieldPath: "entryOpen"
18780
+ }],
18781
+ companions: [CHANGED]
18782
+ }),
18783
+ binary({
18784
+ id: "leak",
18785
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18786
+ role: DeviceRole.LeakSensor,
18787
+ outputCap: "flood",
18788
+ outputField: "flooded",
18789
+ defaultAggregate: "any",
18790
+ accept: [{
18791
+ cap: "flood",
18792
+ fieldPath: "flooded"
18793
+ }],
18794
+ companions: [CHANGED]
18795
+ }),
18796
+ binary({
18797
+ id: "smoke",
18798
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18799
+ role: DeviceRole.SmokeSensor,
18800
+ outputCap: "smoke",
18801
+ outputField: "detected",
18802
+ defaultAggregate: "any",
18803
+ accept: [{
18804
+ cap: "smoke",
18805
+ fieldPath: "detected"
18806
+ }],
18807
+ companions: [CHANGED]
18808
+ }),
18809
+ binary({
18810
+ id: "occupancy",
18811
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18812
+ role: DeviceRole.BinarySensor,
18813
+ outputCap: "binary",
18814
+ outputField: "on",
18815
+ defaultAggregate: "any",
18816
+ accept: [{
18817
+ cap: "motion",
18818
+ fieldPath: "detected"
18819
+ }, {
18820
+ cap: "binary",
18821
+ fieldPath: "on"
18822
+ }],
18823
+ companions: [CHANGED]
18824
+ }),
18825
+ binary({
18826
+ id: "binary",
18827
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18828
+ role: DeviceRole.BinarySensor,
18829
+ outputCap: "binary",
18830
+ outputField: "on",
18831
+ defaultAggregate: "any",
18832
+ accept: [{
18833
+ cap: "binary",
18834
+ fieldPath: "on"
18835
+ }],
18836
+ companions: [CHANGED]
18837
+ }),
18838
+ actuator({
18839
+ id: "switch",
18840
+ 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.",
18841
+ deviceType: DeviceType.Switch,
18842
+ outputCap: "switch",
18843
+ commandCaps: ["switch"]
18844
+ }),
18845
+ actuator({
18846
+ id: "light",
18847
+ 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.",
18848
+ deviceType: DeviceType.Light,
18849
+ outputCap: "switch",
18850
+ commandCaps: [
18851
+ "switch",
18852
+ "brightness",
18853
+ "color"
18854
+ ]
18855
+ })
18856
+ ];
18857
+ function actuator(input) {
18858
+ return {
18859
+ ...input,
18860
+ family: "actuator",
18861
+ role: void 0,
18862
+ memberDeviceType: input.deviceType,
18863
+ outputField: "on",
18864
+ quantity: "none",
18865
+ baseUnit: void 0,
18866
+ aggregates: BINARY_AGGREGATES,
18867
+ defaultAggregate: "any",
18868
+ empty: "offline",
18869
+ accept: [{
18870
+ cap: "switch",
18871
+ fieldPath: "on"
18872
+ }],
18873
+ companions: [CHANGED]
18874
+ };
18875
+ }
18876
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18877
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18490
18878
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18491
18879
  /**
18492
18880
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -20200,12 +20588,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
20200
20588
  * providers detect renames by matching `uniqueId` and patch `id`
20201
20589
  * in-place so dispatch keeps working.
20202
20590
  *
20203
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
20204
- * `precision`) live in the cap STATUS SLICE so they flow live with every
20205
- * state push and are the single source of truth. The `device_class` tag is
20206
- * an HA-specific input that is consumed at the discovery boundary
20207
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
20208
- * stored after that point.
20591
+ * **Display unit and precision are NOT stored here.** They are a read-time
20592
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20593
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20594
+ * neither is repeated on each sample. The `device_class` tag is an
20595
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20596
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
20209
20597
  *
20210
20598
  * The `raw` escape hatch carries provider-specific attributes that don't
20211
20599
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -20219,6 +20607,14 @@ var SourceInfoSchema = object({
20219
20607
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
20220
20608
  * Used to detect rename when `id` changes. */
20221
20609
  uniqueId: string().optional(),
20610
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20611
+ quantity: ReadingQuantitySchema.optional(),
20612
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20613
+ dateTimeShape: _enum([
20614
+ "date",
20615
+ "time",
20616
+ "datetime"
20617
+ ]).optional(),
20222
20618
  /** Provider-specific extras that don't fit the structured slots.
20223
20619
  * Whitelist what goes here per provider — do NOT dump entire HA
20224
20620
  * attribute blobs (would explode the meta JSON column). */
@@ -41131,7 +41527,8 @@ method(object({
41131
41527
  "enum"
41132
41528
  ]),
41133
41529
  enumValues: array(string()).optional(),
41134
- item: boolean().optional()
41530
+ item: boolean().optional(),
41531
+ quantity: ReadingQuantitySchema.optional()
41135
41532
  })).readonly(),
41136
41533
  itemArray: object({
41137
41534
  path: string(),
@@ -44931,8 +45328,8 @@ var MaskPolygonShapeSchema = object({
44931
45328
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44932
45329
  var MaskGridShapeSchema = object({
44933
45330
  kind: literal("grid"),
44934
- gridWidth: number(),
44935
- gridHeight: number(),
45331
+ gridWidth: number().meta(readingMeta("none")),
45332
+ gridHeight: number().meta(readingMeta("none")),
44936
45333
  cells: array(boolean())
44937
45334
  });
44938
45335
  discriminatedUnion("kind", [
@@ -45044,6 +45441,8 @@ var NcSystemEventKindSchema = _enum([
45044
45441
  "detection-blind",
45045
45442
  "motion-noisy",
45046
45443
  "motion-calm",
45444
+ "package-llm-silent",
45445
+ "package-llm-answered",
45047
45446
  "recording-stalled",
45048
45447
  "recording-healthy",
45049
45448
  "recording-degraded",
@@ -56071,6 +56470,28 @@ var PackageVersionInfoSchema = object({
56071
56470
  deprecated: string().optional(),
56072
56471
  distTags: array(string()).readonly()
56073
56472
  });
56473
+ /**
56474
+ * One published document. `ok` is markdown from the registry. `missing` means
56475
+ * the registry answered and the file was not there. `error` is a registry or
56476
+ * tarball failure — never an empty string standing in for either.
56477
+ */
56478
+ var PackageDocBodySchema = discriminatedUnion("status", [
56479
+ object({
56480
+ status: literal("ok"),
56481
+ markdown: string()
56482
+ }),
56483
+ object({ status: literal("missing") }),
56484
+ object({
56485
+ status: literal("error"),
56486
+ message: string()
56487
+ })
56488
+ ]);
56489
+ var PackageDocsSchema = object({
56490
+ name: string(),
56491
+ version: string(),
56492
+ readme: PackageDocBodySchema,
56493
+ changelog: PackageDocBodySchema
56494
+ });
56074
56495
  var SearchResultSchema = object({
56075
56496
  name: string(),
56076
56497
  version: string(),
@@ -56289,7 +56710,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56289
56710
  }), object({ success: literal(true) }), {
56290
56711
  kind: "mutation",
56291
56712
  auth: "admin"
56292
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
56713
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
56714
+ name: string().min(1),
56715
+ version: string().min(1).optional()
56716
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
56293
56717
  kind: "mutation",
56294
56718
  auth: "admin"
56295
56719
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -56339,46 +56763,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56339
56763
  }), LogStreamEntrySchema, { kind: "subscription" });
56340
56764
  object({
56341
56765
  /** Carbon dioxide concentration in ppm. */
56342
- co2Ppm: number().min(0).optional(),
56766
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
56343
56767
  /** Total volatile organic compounds in ppb. */
56344
- vocPpb: number().min(0).optional(),
56768
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56345
56769
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56346
- pm25: number().min(0).optional(),
56770
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56347
56771
  /** Particulate matter ≤ 10 μm in µg/m³. */
56348
- pm10: number().min(0).optional(),
56772
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
56349
56773
  /** Composite AQI value (typically 0..500). */
56350
- aqi: number().optional(),
56774
+ aqi: number().optional().meta(readingMeta("index")),
56351
56775
  /** Ms epoch when the slice was last updated. */
56352
- lastFetchedAt: number(),
56353
- /** Live display unit of the single metric this slice carries (e.g. HA
56354
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
56355
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
56356
- * per slice is unambiguous. */
56357
- unit: string().optional(),
56358
- /** Suggested decimal places for numeric display.
56359
- * Populated live from the upstream source when provided (e.g. HA
56360
- * `attributes.suggested_display_precision`). Falls back to
56361
- * auto-formatting when absent. */
56362
- precision: number().int().min(0).max(10).optional()
56776
+ lastFetchedAt: number()
56363
56777
  });
56364
56778
  DeviceType.Sensor;
56365
56779
  object({
56366
- /** Current illuminance in lux (lx). */
56367
- lux: number().min(0).meta({
56368
- unit: "lx",
56780
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56781
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
56782
+ precision: 0,
56369
56783
  label: "Illuminance"
56370
- }),
56784
+ })),
56371
56785
  /** Ms epoch when the slice was last updated. */
56372
- lastFetchedAt: number(),
56373
- /** Live display unit from the upstream source (e.g. HA
56374
- * `attributes.unit_of_measurement`). The UI prefers this over the
56375
- * role's canonical unit. Absent → fall back to the canonical unit. */
56376
- unit: string().optional(),
56377
- /** Suggested decimal places for numeric display.
56378
- * Populated live from the upstream source when provided (e.g. HA
56379
- * `attributes.suggested_display_precision`). Falls back to
56380
- * auto-formatting when absent. */
56381
- precision: number().int().min(0).max(10).optional()
56786
+ lastFetchedAt: number()
56382
56787
  });
56383
56788
  DeviceType.Sensor;
56384
56789
  /**
@@ -56523,10 +56928,7 @@ var BatteryStatusSchema = object({
56523
56928
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
56524
56929
  * declines to band a non-finite reading, which is the correct shape.
56525
56930
  */
56526
- percentage: number().min(0).max(100).nullable().meta({
56527
- unit: "%",
56528
- label: "Charge"
56529
- }),
56931
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
56530
56932
  /**
56531
56933
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
56532
56934
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -56553,7 +56955,14 @@ var BatteryStatusSchema = object({
56553
56955
  */
56554
56956
  sleeping: boolean().meta({ label: "Sleeping" }),
56555
56957
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
56556
- lastUpdated: number(),
56958
+ lastUpdated: number().meta(readingMeta("none")),
56959
+ /**
56960
+ * Battery voltage in volts, for a cell the firmware reports as voltage
56961
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
56962
+ * that only report charge. A second reading on this cap, not a second
56963
+ * `numeric-sensor` on the same device.
56964
+ */
56965
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
56557
56966
  /**
56558
56967
  * Ms epoch of the last time the device PROVED it was reachable — a
56559
56968
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -56613,7 +57022,7 @@ object({
56613
57022
  DeviceType.Sensor;
56614
57023
  object({
56615
57024
  /** Current level as 0..100 inclusive. Firmware-reported. */
56616
- percentage: number().min(0).max(100),
57025
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
56617
57026
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
56618
57027
  lastChangedAt: number()
56619
57028
  });
@@ -57008,21 +57417,21 @@ object({
57008
57417
  /** Single setpoint in Celsius. Used by single-target modes
57009
57418
  * (heat / cool / dry). Null when the device is in a range mode
57010
57419
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
57011
- target: number().nullable(),
57420
+ target: number().nullable().meta(readingMeta("temperature")),
57012
57421
  /** Upper bound of the dual setpoint in Celsius. Populated only
57013
57422
  * in `heat_cool` mode. */
57014
- targetHigh: number().nullable(),
57423
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
57015
57424
  /** Lower bound of the dual setpoint in Celsius. Populated only
57016
57425
  * in `heat_cool` mode. */
57017
- targetLow: number().nullable(),
57426
+ targetLow: number().nullable().meta(readingMeta("temperature")),
57018
57427
  /** Target relative humidity (0..100). Null when the device has no
57019
57428
  * humidity control. */
57020
- targetHumidity: number().min(0).max(100).nullable(),
57429
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
57021
57430
  /** Current measured temperature in Celsius. Read-only — pushed by
57022
57431
  * the device's internal sensor. */
57023
- currentTemp: number().nullable(),
57432
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
57024
57433
  /** Current measured relative humidity (0..100). Read-only. */
57025
- currentHumidity: number().min(0).max(100).nullable(),
57434
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
57026
57435
  /** Vertical louver swing on/off. Null when the device has no vertical
57027
57436
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
57028
57437
  swingVertical: boolean().nullable(),
@@ -57030,11 +57439,11 @@ object({
57030
57439
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
57031
57440
  swingHorizontal: boolean().nullable(),
57032
57441
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
57033
- minTemp: number().nullable().optional(),
57442
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
57034
57443
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
57035
- maxTemp: number().nullable().optional(),
57444
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
57036
57445
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
57037
- tempStep: number().positive().nullable().optional(),
57446
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
57038
57447
  /** Ms epoch when the slice was last updated (push or command). */
57039
57448
  lastFetchedAt: number()
57040
57449
  });
@@ -57113,17 +57522,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
57113
57522
  * modes to expose in the picker without a slice fetch.
57114
57523
  */
57115
57524
  var RgbTripletSchema = object({
57116
- r: number().int().min(0).max(255),
57117
- g: number().int().min(0).max(255),
57118
- b: number().int().min(0).max(255)
57525
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
57526
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
57527
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
57119
57528
  });
57120
57529
  var HsvTripletSchema = object({
57121
57530
  /** Hue in degrees, 0..360. */
57122
- h: number().min(0).max(360),
57531
+ h: number().min(0).max(360).meta(readingMeta("none")),
57123
57532
  /** Saturation as 0..100 inclusive. */
57124
- s: number().min(0).max(100),
57533
+ s: number().min(0).max(100).meta(readingMeta("none")),
57125
57534
  /** Value/brightness as 0..100 inclusive. */
57126
- v: number().min(0).max(100)
57535
+ v: number().min(0).max(100).meta(readingMeta("none"))
57127
57536
  });
57128
57537
  var ColorInputSchema = discriminatedUnion("mode", [
57129
57538
  object({
@@ -57152,7 +57561,7 @@ object({
57152
57561
  /** Populated when `mode === 'hsv'`. */
57153
57562
  hsv: HsvTripletSchema.optional(),
57154
57563
  /** Populated when `mode === 'mired'`. */
57155
- mireds: number().int().optional(),
57564
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
57156
57565
  /** Ms epoch of the last operator-driven change. */
57157
57566
  lastChangedAt: number()
57158
57567
  });
@@ -57276,18 +57685,12 @@ _enum([
57276
57685
  "empty",
57277
57686
  "full"
57278
57687
  ]);
57279
- /** Units a remaining-life counter is reported in. Closed: the panel renders
57280
- * each. */
57281
- var ConsumableRemainingUnitSchema = _enum([
57282
- "days",
57283
- "hours",
57284
- "cycles"
57285
- ]);
57286
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
57287
- * days). `value` null = unknown (D393). */
57688
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
57689
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
57690
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
57288
57691
  var ConsumableRemainingSchema = object({
57289
- value: number().min(0).nullable(),
57290
- unit: ConsumableRemainingUnitSchema
57692
+ value: number().min(0).nullable().meta(readingMeta("device")),
57693
+ kind: ConsumableRemainingKindSchema
57291
57694
  });
57292
57695
  var ConsumablesStatusSchema = object({
57293
57696
  items: array(object({
@@ -57296,7 +57699,7 @@ var ConsumablesStatusSchema = object({
57296
57699
  /** Display name. */
57297
57700
  label: string().min(1),
57298
57701
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
57299
- level: number().min(0).max(100).nullable(),
57702
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57300
57703
  /** The provider's discrete verdict when known (binary mode). */
57301
57704
  status: ConsumableStatusSchema.nullable(),
57302
57705
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -57313,7 +57716,7 @@ var ConsumablesStatusSchema = object({
57313
57716
  })),
57314
57717
  lastChangedAt: number()
57315
57718
  });
57316
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57719
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57317
57720
  Object.values(DeviceType), method(object({
57318
57721
  deviceId: number().int().nonnegative(),
57319
57722
  key: string().min(1)
@@ -57338,39 +57741,17 @@ object({
57338
57741
  /** Current value. Numeric kind → number; select/text/datetime → string.
57339
57742
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
57340
57743
  * or time-only — HA accepts all three forms). */
57341
- value: union([number(), string()]),
57744
+ value: union([number().meta(readingMeta("device")), string()]),
57342
57745
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
57343
57746
  options: array(string()),
57344
57747
  /** Ms epoch when the slice was last updated. */
57345
57748
  lastChangedAt: number(),
57346
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
57347
- * Populated live from HA `attributes.unit_of_measurement` on each push.
57348
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57349
- unit: string().optional(),
57350
- /** Minimum allowed value. Populated live from HA `attributes.min`.
57351
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57352
- min: number().optional(),
57353
- /** Maximum allowed value. Populated live from HA `attributes.max`.
57354
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57355
- max: number().optional(),
57356
- /** Step increment. Populated live from HA `attributes.step`.
57357
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57358
- step: number().optional(),
57359
- /** Suggested decimal places for numeric display. Populated live from HA
57360
- * `attributes.suggested_display_precision`, or derived from `step` when the
57361
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
57362
- * other kinds. Falls back to auto-formatting when absent. */
57363
- precision: number().int().min(0).max(10).optional(),
57364
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
57365
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
57366
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
57367
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
57368
- * `has_date`/`has_time` attributes on each push. */
57369
- format: _enum([
57370
- "date",
57371
- "time",
57372
- "datetime"
57373
- ]).optional()
57749
+ /** Minimum allowed value, in the device quantity's base unit. */
57750
+ min: number().optional().meta(readingMeta("device")),
57751
+ /** Maximum allowed value, in the device quantity's base unit. */
57752
+ max: number().optional().meta(readingMeta("device")),
57753
+ /** Step increment, in the device quantity's base unit. */
57754
+ step: number().optional().meta(readingMeta("device"))
57374
57755
  });
57375
57756
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
57376
57757
  object({
@@ -57408,9 +57789,9 @@ object({
57408
57789
  ]),
57409
57790
  /** 0 = fully closed, 100 = fully open. Null when the device has
57410
57791
  * no intermediate position surface. */
57411
- position: number().min(0).max(100).nullable(),
57792
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57412
57793
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
57413
- tiltPosition: number().min(0).max(100).nullable(),
57794
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57414
57795
  /** Ms epoch when the slice was last updated. */
57415
57796
  lastChangedAt: number()
57416
57797
  });
@@ -57468,9 +57849,9 @@ var NormalizedRangeSchema$1 = object({
57468
57849
  object({
57469
57850
  mode: DayNightModeSchema,
57470
57851
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
57471
- sensitivity: number().optional(),
57852
+ sensitivity: number().optional().meta(readingMeta("none")),
57472
57853
  /** Delay before the IR-cut filter flips, in seconds. */
57473
- switchDelaySec: number().optional(),
57854
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
57474
57855
  lastFetchedAt: number()
57475
57856
  });
57476
57857
  /**
@@ -57625,11 +58006,11 @@ object({
57625
58006
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
57626
58007
  availablePrograms: array(string()),
57627
58008
  /** Seconds remaining on the active cycle, or null when not running / not known. */
57628
- remainingSeconds: number().int().nonnegative().nullable(),
58009
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57629
58010
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
57630
- delayedStartSeconds: number().int().nonnegative().nullable(),
58011
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57631
58012
  /** Cycle completion 0..100, or null when not applicable. */
57632
- progressPercent: number().min(0).max(100).nullable(),
58013
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57633
58014
  /** True when the door is reported open; null when the door state is not known. */
57634
58015
  doorOpen: boolean().nullable(),
57635
58016
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -57653,7 +58034,7 @@ object({
57653
58034
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
57654
58035
  machineCareDue: boolean().nullable(),
57655
58036
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
57656
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
58037
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
57657
58038
  /** Option states: null when the appliance does not expose the option or has not said. */
57658
58039
  halfLoad: boolean().nullable(),
57659
58040
  hygienePlus: boolean().nullable(),
@@ -57668,7 +58049,7 @@ object({
57668
58049
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
57669
58050
  activeEvents: array(string()),
57670
58051
  /** Numeric error code when the appliance reports one; null otherwise. */
57671
- errorCode: number().nullable(),
58052
+ errorCode: number().nullable().meta(readingMeta("none")),
57672
58053
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
57673
58054
  errorLabel: string().nullable(),
57674
58055
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -57814,24 +58195,14 @@ object({
57814
58195
  /** Ms epoch of the last press. null = never observed since this provider started. */
57815
58196
  lastPressedAt: number().nullable(),
57816
58197
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
57817
- pressCountSinceStart: number()
58198
+ pressCountSinceStart: number().meta(readingMeta("count"))
57818
58199
  });
57819
58200
  object({
57820
58201
  deviceId: number(),
57821
58202
  timestamp: number()
57822
58203
  });
57823
58204
  DeviceType.Button, DeviceType.Camera;
57824
- /**
57825
- * Enum-state sensor — a string value picked from a finite option set.
57826
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
57827
- * states, weather conditions, contact-source channels, mode strings).
57828
- * The option set is stable across observations; it is persisted in the
57829
- * device config blob (via the control slice at adoption) and is NOT
57830
- * stored in `sourceInfo`.
57831
- */
57832
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
57833
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
57834
- var EnumSensorDateTimeFormatSchema = _enum([
58205
+ _enum([
57835
58206
  "date",
57836
58207
  "time",
57837
58208
  "datetime"
@@ -57839,12 +58210,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
57839
58210
  object({
57840
58211
  value: string(),
57841
58212
  /**
57842
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
57843
- * as a locale date/time/datetime; absent for plain enum sensors. HA
57844
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
57845
- * a time-only sensor → `'time'`.
58213
+ * Set when `value` is a timestamp rather than an enum state, so history
58214
+ * does not learn ISO strings as states. The display shape (date / time /
58215
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
57846
58216
  */
57847
- format: EnumSensorDateTimeFormatSchema.optional(),
58217
+ timestamp: boolean().optional(),
57848
58218
  /** Ms epoch when the slice was last updated. */
57849
58219
  lastFetchedAt: number()
57850
58220
  });
@@ -57863,13 +58233,13 @@ var EventFireSchema = object({
57863
58233
  deviceId: number(),
57864
58234
  eventType: string(),
57865
58235
  data: record(string(), unknown()).nullable(),
57866
- timestamp: number(),
57867
- seq: number()
58236
+ timestamp: number().meta(readingMeta("none")),
58237
+ seq: number().meta(readingMeta("count"))
57868
58238
  });
57869
58239
  object({
57870
58240
  eventTypes: array(string()),
57871
58241
  lastEvent: EventFireSchema.nullable(),
57872
- eventCountSinceStart: number()
58242
+ eventCountSinceStart: number().meta(readingMeta("count"))
57873
58243
  });
57874
58244
  DeviceType.EventEmitter;
57875
58245
  var IdentitySchema = object({
@@ -58258,10 +58628,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
58258
58628
  object({
58259
58629
  /** Active speed as 0..100 inclusive. Null when the device has no
58260
58630
  * speed surface (single-speed fan). */
58261
- percentage: number().min(0).max(100).nullable(),
58631
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58262
58632
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
58263
58633
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
58264
- percentageStep: number().positive().optional(),
58634
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
58265
58635
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
58266
58636
  * — empty string when no preset is active or supported. */
58267
58637
  preset: string(),
@@ -58343,7 +58713,7 @@ var FeatureProbeStatusSchema = object({
58343
58713
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
58344
58714
  model: string().nullable(),
58345
58715
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
58346
- channelCount: number().nullable(),
58716
+ channelCount: number().nullable().meta(readingMeta("count")),
58347
58717
  /**
58348
58718
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
58349
58719
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -58375,9 +58745,39 @@ object({
58375
58745
  lastChangedAt: number()
58376
58746
  });
58377
58747
  DeviceType.Sensor;
58748
+ object({
58749
+ /** One row per member that has been commanded. A later command replaces that member's row. */
58750
+ results: array(object({
58751
+ deviceId: number().int().nonnegative(),
58752
+ cap: string().min(1),
58753
+ method: string().min(1),
58754
+ ok: boolean(),
58755
+ error: string().optional(),
58756
+ /** Ms epoch of the attempt. */
58757
+ at: number()
58758
+ })) });
58759
+ DeviceType.Switch, DeviceType.Light;
58760
+ object({
58761
+ /** Members whose current value entered the aggregate. */
58762
+ included: number().int().nonnegative().meta(readingMeta("count", {
58763
+ precision: 0,
58764
+ label: "Reporting"
58765
+ })),
58766
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
58767
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
58768
+ precision: 0,
58769
+ label: "Excluded"
58770
+ })),
58771
+ /** `included` + `excluded`. */
58772
+ total: number().int().nonnegative().meta(readingMeta("count", {
58773
+ precision: 0,
58774
+ label: "Members"
58775
+ }))
58776
+ });
58777
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
58378
58778
  object({
58379
58779
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
58380
- position: number().min(0).max(100).nullable(),
58780
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58381
58781
  /** Ms epoch when the slice was last updated (push or poll). */
58382
58782
  lastFetchedAt: number()
58383
58783
  });
@@ -58386,9 +58786,9 @@ object({
58386
58786
  /** Whether the humidifier is currently on. */
58387
58787
  on: boolean(),
58388
58788
  /** Current measured relative humidity (0..100). Null when not reported. */
58389
- currentHumidity: number().min(0).max(100).nullable(),
58789
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58390
58790
  /** Target relative humidity (0..100). Null when no setpoint surface. */
58391
- targetHumidity: number().min(0).max(100).nullable(),
58791
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58392
58792
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
58393
58793
  * device has no mode surface. */
58394
58794
  mode: string().nullable(),
@@ -58398,9 +58798,9 @@ object({
58398
58798
  * `idle` / `off`). Null when the device doesn't report it. */
58399
58799
  action: string().nullable(),
58400
58800
  /** HA `min_humidity` attribute. Null → UI uses 0. */
58401
- minHumidity: number().nullable(),
58801
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
58402
58802
  /** HA `max_humidity` attribute. Null → UI uses 100. */
58403
- maxHumidity: number().nullable(),
58803
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
58404
58804
  /** Ms epoch when the slice was last updated. */
58405
58805
  lastChangedAt: number()
58406
58806
  });
@@ -58424,22 +58824,13 @@ DeviceType.Humidifier, method(object({
58424
58824
  auth: "protected"
58425
58825
  });
58426
58826
  object({
58427
- /** Current relative humidity, 0..100. */
58428
- percent: number().min(0).max(100).meta({
58429
- unit: "%",
58827
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
58828
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
58829
+ precision: 0,
58430
58830
  label: "Humidity"
58431
- }),
58831
+ })),
58432
58832
  /** Ms epoch when the slice was last updated. */
58433
- lastFetchedAt: number(),
58434
- /** Live display unit from the upstream source (e.g. HA
58435
- * `attributes.unit_of_measurement`). The UI prefers this over the
58436
- * role's canonical unit. Absent → fall back to the canonical unit. */
58437
- unit: string().optional(),
58438
- /** Suggested decimal places for numeric display.
58439
- * Populated live from the upstream source when provided (e.g. HA
58440
- * `attributes.suggested_display_precision`). Falls back to
58441
- * auto-formatting when absent. */
58442
- precision: number().int().min(0).max(10).optional()
58833
+ lastFetchedAt: number()
58443
58834
  });
58444
58835
  DeviceType.Sensor, DeviceType.Thermostat;
58445
58836
  object({
@@ -58447,7 +58838,7 @@ object({
58447
58838
  * entity exposes no `entity_picture` (yet). */
58448
58839
  url: string().nullable(),
58449
58840
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
58450
- lastUpdated: number().nullable()
58841
+ lastUpdated: number().nullable().meta(readingMeta("none"))
58451
58842
  });
58452
58843
  DeviceType.Image;
58453
58844
  /**
@@ -58503,19 +58894,19 @@ var NormalizedRangeSchema = object({
58503
58894
  });
58504
58895
  object({
58505
58896
  /** Normalized 0–100. */
58506
- brightness: number().optional(),
58897
+ brightness: number().optional().meta(readingMeta("none")),
58507
58898
  /** Normalized 0–100. */
58508
- contrast: number().optional(),
58899
+ contrast: number().optional().meta(readingMeta("none")),
58509
58900
  /** Normalized 0–100. */
58510
- saturation: number().optional(),
58901
+ saturation: number().optional().meta(readingMeta("none")),
58511
58902
  /** Normalized 0–100. */
58512
- sharpness: number().optional(),
58903
+ sharpness: number().optional().meta(readingMeta("none")),
58513
58904
  mirror: boolean().optional(),
58514
58905
  flip: boolean().optional(),
58515
58906
  rotate: ImageRotateSchema.optional(),
58516
58907
  whiteBalance: WhiteBalanceModeSchema.optional(),
58517
58908
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
58518
- warmth: number().optional(),
58909
+ warmth: number().optional().meta(readingMeta("none")),
58519
58910
  exposureMode: ExposureModeSchema.optional(),
58520
58911
  backlightMode: BacklightModeSchema.optional(),
58521
58912
  lastFetchedAt: number()
@@ -58760,10 +59151,10 @@ var IntercomAbilitySchema = object({
58760
59151
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
58761
59152
  codecs: array(string()),
58762
59153
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
58763
- sampleRate: number(),
59154
+ sampleRate: number().meta(readingMeta("none")),
58764
59155
  duplex: _enum(["half", "full"]),
58765
59156
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
58766
- maxBacklogMs: number()
59157
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
58767
59158
  });
58768
59159
  var IntercomStatusSchema = object({
58769
59160
  /** True while a talk session is open. */
@@ -58874,13 +59265,13 @@ object({
58874
59265
  activity: LawnMowerActivitySchema,
58875
59266
  /** 0..100 battery percentage. Null when the device has no battery
58876
59267
  * reading. */
58877
- batteryLevel: number().min(0).max(100).nullable(),
59268
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58878
59269
  /** 0..100 mowing-completion percentage of the current task, or null when no
58879
59270
  * task is active / progress is unavailable. */
58880
- progressPercent: number().min(0).max(100).nullable(),
59271
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58881
59272
  /** Current device/event code (dynamic — mostly status, sometimes an error),
58882
59273
  * or null when unknown. */
58883
- currentCode: number().nullable(),
59274
+ currentCode: number().nullable().meta(readingMeta("none")),
58884
59275
  /** Human label for {@link currentCode}, or null when undecodable. */
58885
59276
  currentCodeLabel: string().nullable(),
58886
59277
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -59342,7 +59733,7 @@ object({
59342
59733
  state: MediaPlayerStateSchema,
59343
59734
  /** Volume as 0..100 inclusive. Null when the device has no volume
59344
59735
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59345
- volumeLevel: number().min(0).max(100).nullable(),
59736
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59346
59737
  /** Mute toggle distinct from volume=0. Null when no mute surface.
59347
59738
  * Pair with `DeviceFeature.MediaPlayerMute`. */
59348
59739
  isMuted: boolean().nullable(),
@@ -59355,10 +59746,10 @@ object({
59355
59746
  currentMedia: MediaInfoSchema.nullable(),
59356
59747
  /** Current playback position in ms. Null when not seekable or
59357
59748
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59358
- positionMs: number().int().nonnegative().nullable(),
59749
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59359
59750
  /** Total duration of the current media in ms. Null when unknown
59360
59751
  * (live stream) or nothing is playing. */
59361
- durationMs: number().int().nonnegative().nullable(),
59752
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59362
59753
  /** Shuffle toggle. Null when no shuffle surface.
59363
59754
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59364
59755
  shuffle: boolean().nullable(),
@@ -59658,7 +60049,7 @@ object({
59658
60049
  * it, every provider seeded it at boot, and every session left it
59659
60050
  * `detected: true` after its detach.
59660
60051
  */
59661
- autoClearAfterMs: number().nullable()
60052
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
59662
60053
  });
59663
60054
  /**
59664
60055
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -59708,7 +60099,7 @@ var MotionZoneRegionSchema = object({
59708
60099
  });
59709
60100
  object({
59710
60101
  enabled: boolean(),
59711
- sensitivity: number(),
60102
+ sensitivity: number().meta(readingMeta("none")),
59712
60103
  /** Grid region(s). Today exactly one `grid` shape. */
59713
60104
  regions: array(MotionZoneRegionSchema),
59714
60105
  lastFetchedAt: number()
@@ -60012,7 +60403,7 @@ var NavigationStatusSchema = object({
60012
60403
  following: boolean(),
60013
60404
  flash: boolean(),
60014
60405
  lightMode: NavigationLightModeSchema,
60015
- lightLevel: number().min(40).max(100),
60406
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
60016
60407
  /** Ms epoch when the slice was last updated. */
60017
60408
  lastChangedAt: number()
60018
60409
  });
@@ -60058,13 +60449,13 @@ var NetworkLinkStatusSchema = object({
60058
60449
  * one whose reading has not landed must not be drawn at 0 %. Consumers
60059
60450
  * SKIP a null rather than coerce it.
60060
60451
  */
60061
- signalPercent: number().min(0).max(100).nullable(),
60452
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60062
60453
  /** Raw received signal strength in dBm, when the firmware reports one. */
60063
- rssiDbm: number().optional(),
60454
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
60064
60455
  /** Network name of a wireless link, when the firmware reports it. */
60065
60456
  ssid: string().optional(),
60066
60457
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60067
- lastUpdated: number()
60458
+ lastUpdated: number().meta(readingMeta("none"))
60068
60459
  });
60069
60460
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
60070
60461
  deviceId: number(),
@@ -60383,7 +60774,7 @@ object({
60383
60774
  lastError: string().nullable(),
60384
60775
  /** Number of deliveries currently buffered server-side (rate-limited
60385
60776
  * notifiers may queue). 0 when the provider sends synchronously. */
60386
- queueDepth: number().int().nonnegative(),
60777
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
60387
60778
  /** Per-feature capability matrix — authoritative source for the
60388
60779
  * compose-form field gating. */
60389
60780
  supports: NotifierSupportsSchema
@@ -60433,15 +60824,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
60433
60824
  acceptedAt: number()
60434
60825
  });
60435
60826
  object({
60436
- value: number(),
60437
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
60438
- * Populated live from the upstream source on each state push.
60439
- * Absent for unitless sensors. */
60440
- unit: string().optional(),
60441
- /** Suggested decimal places for numeric display.
60442
- * Populated live from the upstream source when provided.
60443
- * Falls back to auto-formatting when absent. */
60444
- precision: number().int().min(0).max(10).optional(),
60827
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
60828
+ value: number().nullable().meta(readingMeta("device")),
60445
60829
  /** Ms epoch when the slice was last updated. */
60446
60830
  lastFetchedAt: number()
60447
60831
  });
@@ -60731,23 +61115,26 @@ object({
60731
61115
  /** Food currently in the bowl (grams). Null when the device has not
60732
61116
  * reported a reading yet. On dual-hopper models this is the combined
60733
61117
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
60734
- foodLevel: number().nullable(),
61118
+ foodLevel: number().nullable().meta(readingMeta("none")),
60735
61119
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
60736
61120
  * single-hopper models. */
60737
- food1: number().nullable(),
61121
+ food1: number().nullable().meta(readingMeta("none")),
60738
61122
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
60739
61123
  * single-hopper models. */
60740
- food2: number().nullable(),
61124
+ food2: number().nullable().meta(readingMeta("none")),
60741
61125
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
60742
61126
  * (`device_class: problem`, on = low). True when the bowl is empty /
60743
61127
  * below the feeder's low threshold. */
60744
61128
  lowFood: boolean(),
60745
61129
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
60746
61130
  * device has no battery reading. */
60747
- batteryPower: number().min(0).max(100).nullable(),
60748
- /** Days of desiccant life remaining. Null when the model has no
60749
- * desiccant sensor. */
60750
- desiccantLeftDays: number().nullable(),
61131
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61132
+ /**
61133
+ * Desiccant life remaining, stored in seconds (duration base, D944).
61134
+ * Providers convert from the days the feeder reports. Null when the
61135
+ * model has no desiccant sensor.
61136
+ */
61137
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
60751
61138
  /** True while a feed is in progress. */
60752
61139
  feeding: boolean(),
60753
61140
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -60758,7 +61145,7 @@ object({
60758
61145
  * with `errorCode` for consumers that want the raw integer. */
60759
61146
  error: string().nullable(),
60760
61147
  /** Raw device error code (0 / null = no error). */
60761
- errorCode: number().nullable(),
61148
+ errorCode: number().nullable().meta(readingMeta("none")),
60762
61149
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
60763
61150
  isDualHopper: boolean(),
60764
61151
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -60768,7 +61155,7 @@ object({
60768
61155
  /** Play a chime when dispensing. */
60769
61156
  feedSound: boolean(),
60770
61157
  /** Speaker / prompt volume level (device-scaled integer). */
60771
- volume: number(),
61158
+ volume: number().meta(readingMeta("none")),
60772
61159
  /** Ms epoch when the slice was last refreshed from the cloud. */
60773
61160
  lastFetchedAt: number()
60774
61161
  });
@@ -61205,36 +61592,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
61205
61592
  });
61206
61593
  object({
61207
61594
  /** Instantaneous power draw in watts. */
61208
- watts: number().optional().meta({
61209
- unit: "W",
61210
- label: "Power"
61211
- }),
61595
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
61212
61596
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61213
- kwhTotal: number().optional().meta({
61214
- unit: "kWh",
61215
- label: "Energy"
61216
- }),
61597
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61217
61598
  /** Voltage in volts. */
61218
- volts: number().optional(),
61599
+ volts: number().optional().meta(readingMeta("voltage")),
61219
61600
  /** Current in amperes. */
61220
- amps: number().optional(),
61221
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
61222
- va: number().optional(),
61601
+ amps: number().optional().meta(readingMeta("current")),
61602
+ /** Apparent power in volt-amperes. */
61603
+ va: number().optional().meta(readingMeta("apparent-power")),
61223
61604
  /** Power factor (0..1) if reported. */
61224
- powerFactor: number().min(0).max(1).optional(),
61605
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
61225
61606
  /** Ms epoch when the slice was last updated. */
61226
- lastFetchedAt: number(),
61227
- /** Live display unit of the single metric this slice carries (e.g. HA
61228
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
61229
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
61230
- * unit per slice is unambiguous. The UI prefers this over the role's
61231
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
61232
- unit: string().optional(),
61233
- /** Suggested decimal places for numeric display.
61234
- * Populated live from the upstream source when provided (e.g. HA
61235
- * `attributes.suggested_display_precision`). Falls back to
61236
- * auto-formatting when absent. */
61237
- precision: number().int().min(0).max(10).optional()
61607
+ lastFetchedAt: number()
61238
61608
  });
61239
61609
  DeviceType.Sensor;
61240
61610
  /**
@@ -61255,11 +61625,11 @@ DeviceType.Sensor;
61255
61625
  */
61256
61626
  var GpsLocationSchema = object({
61257
61627
  /** Latitude in decimal degrees, -90..90. */
61258
- latitude: number().min(-90).max(90),
61628
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
61259
61629
  /** Longitude in decimal degrees, -180..180. */
61260
- longitude: number().min(-180).max(180),
61630
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
61261
61631
  /** Reported accuracy in meters (lower = better). */
61262
- accuracyMeters: number().nonnegative()
61632
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
61263
61633
  });
61264
61634
  object({
61265
61635
  /** `home` / `not_home` / any user-defined zone name. */
@@ -61271,25 +61641,16 @@ object({
61271
61641
  gps: GpsLocationSchema.nullable(),
61272
61642
  /** Optional battery level of the tracking device (0..100). Null
61273
61643
  * when not reported. */
61274
- batteryPercent: number().min(0).max(100).nullable(),
61644
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61275
61645
  /** Ms epoch when the slice was last updated. */
61276
61646
  lastChangedAt: number()
61277
61647
  });
61278
61648
  DeviceType.Presence;
61279
61649
  object({
61280
- /** Current pressure in hPa. */
61281
- hpa: number(),
61650
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
61651
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
61282
61652
  /** Ms epoch when the slice was last updated. */
61283
- lastFetchedAt: number(),
61284
- /** Live display unit from the upstream source (e.g. HA
61285
- * `attributes.unit_of_measurement`). The UI prefers this over the
61286
- * role's canonical unit. Absent → fall back to the canonical unit. */
61287
- unit: string().optional(),
61288
- /** Suggested decimal places for numeric display.
61289
- * Populated live from the upstream source when provided (e.g. HA
61290
- * `attributes.suggested_display_precision`). Falls back to
61291
- * auto-formatting when absent. */
61292
- precision: number().int().min(0).max(10).optional()
61653
+ lastFetchedAt: number()
61293
61654
  });
61294
61655
  DeviceType.Sensor;
61295
61656
  /**
@@ -61622,8 +61983,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
61622
61983
  }), _void(), { kind: "mutation" });
61623
61984
  var PtzAutotrackSettingsSchema = object({
61624
61985
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
61625
- stopDelaySeconds: number().int().min(0).max(300),
61626
- disappearDelaySeconds: number().int().min(0).max(300)
61986
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
61987
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
61627
61988
  });
61628
61989
  /**
61629
61990
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -61680,7 +62041,7 @@ object({
61680
62041
  /** null = unknown — never coerced to false (D393). */
61681
62042
  running: boolean().nullable(),
61682
62043
  /** Epoch ms the current boolean began; null while `running` is null. */
61683
- since: number().nullable(),
62044
+ since: number().nullable().meta(readingMeta("none")),
61684
62045
  source: RunningStateSourceSchema.meta({ label: "Source" }),
61685
62046
  phase: string().nullable(),
61686
62047
  /** null until a reading has been published (D393). */
@@ -62878,9 +63239,9 @@ var RecordTriggerSchema = _enum([
62878
63239
  */
62879
63240
  var RecordWindowSchema = object({
62880
63241
  trigger: RecordTriggerSchema,
62881
- day: number().int().min(0).max(6),
62882
- startMinute: number().int().min(0).max(1439),
62883
- endMinute: number().int().min(1).max(1440)
63242
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
63243
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
63244
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
62884
63245
  });
62885
63246
  /** Status of one physical volume, as the camera itself describes it. */
62886
63247
  var OnboardStorageVolumeSchema = object({
@@ -62949,10 +63310,10 @@ object({
62949
63310
  /** Master "record to the card at all" switch. */
62950
63311
  enabled: boolean().nullable(),
62951
63312
  overwriteWhenFull: boolean().nullable(),
62952
- preRecordSec: number().nullable(),
62953
- postRecordSec: number().nullable(),
63313
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
63314
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
62954
63315
  /** Length of one recorded file, in minutes. */
62955
- segmentMinutes: number().nullable(),
63316
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
62956
63317
  /** The primary track's weekly windows, flattened. */
62957
63318
  windows: array(RecordWindowSchema),
62958
63319
  /**
@@ -62968,7 +63329,7 @@ object({
62968
63329
  * provider reporting a non-zero count also reports the schedule as not
62969
63330
  * writable.
62970
63331
  */
62971
- unreadableWindows: number(),
63332
+ unreadableWindows: number().meta(readingMeta("count")),
62972
63333
  /**
62973
63334
  * The camera is scheduled to record and has NO usable storage.
62974
63335
  *
@@ -63605,7 +63966,7 @@ var CamStreamDescriptorSchema = object({
63605
63966
  url: string().optional(),
63606
63967
  codec: string().optional(),
63607
63968
  resolution: CamStreamResolutionSchema.optional(),
63608
- fps: number().positive().optional(),
63969
+ fps: number().positive().optional().meta(readingMeta("rate")),
63609
63970
  label: string().optional(),
63610
63971
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
63611
63972
  deviceFeatures: array(string()).optional(),
@@ -63635,18 +63996,18 @@ var StreamProfileSchema = _enum([
63635
63996
  ]);
63636
63997
  /** The current encoding config of one stream profile. */
63637
63998
  var StreamProfileConfigSchema = object({
63638
- width: number(),
63639
- height: number(),
63999
+ width: number().meta(readingMeta("none")),
64000
+ height: number().meta(readingMeta("none")),
63640
64001
  codec: _enum(["h264", "h265"]),
63641
- framerate: number(),
63642
- bitrate: number(),
64002
+ framerate: number().meta(readingMeta("rate")),
64003
+ bitrate: number().meta(readingMeta("bitrate")),
63643
64004
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
63644
64005
  encoderProfile: _enum([
63645
64006
  "high",
63646
64007
  "main",
63647
64008
  "baseline"
63648
64009
  ]).optional(),
63649
- gop: number().optional(),
64010
+ gop: number().optional().meta(readingMeta("none")),
63650
64011
  /**
63651
64012
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
63652
64013
  *
@@ -64289,23 +64650,13 @@ object({
64289
64650
  });
64290
64651
  DeviceType.Sensor;
64291
64652
  object({
64292
- /** Current temperature in Celsius. */
64293
- celsius: number().meta({
64294
- unit: "°C",
64653
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64654
+ celsius: number().nullable().meta(readingMeta("temperature", {
64655
+ precision: 1,
64295
64656
  label: "Temperature"
64296
- }),
64657
+ })),
64297
64658
  /** Ms epoch when the slice was last updated (push or poll). */
64298
- lastFetchedAt: number(),
64299
- /** Live display unit from the upstream source (e.g. HA
64300
- * `attributes.unit_of_measurement`). The UI prefers this over the
64301
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
64302
- * Absent → the UI falls back to the role's canonical unit. */
64303
- unit: string().optional(),
64304
- /** Suggested decimal places for numeric display.
64305
- * Populated live from the upstream source when provided (e.g. HA
64306
- * `attributes.suggested_display_precision`). Falls back to
64307
- * auto-formatting when absent. */
64308
- precision: number().int().min(0).max(10).optional()
64659
+ lastFetchedAt: number()
64309
64660
  });
64310
64661
  DeviceType.Sensor, DeviceType.Thermostat;
64311
64662
  /**
@@ -64557,16 +64908,16 @@ object({
64557
64908
  ]),
64558
64909
  /** 0..100 battery percentage. Null when the device has no battery
64559
64910
  * reading. */
64560
- batteryLevel: number().min(0).max(100).nullable(),
64911
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64561
64912
  /** Current fan-speed token (provider-verbatim). Null when unknown or
64562
64913
  * the vacuum has no speed control. */
64563
64914
  fanSpeed: string().nullable(),
64564
64915
  /** Speed tokens the hardware accepts — drives the UI selector. */
64565
64916
  availableFanSpeeds: array(string()),
64566
64917
  /** 0..100 cleaning-completion percentage of the current task, or null. */
64567
- progressPercent: number().min(0).max(100).nullable(),
64918
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64568
64919
  /** Current error code (0 / null = no error). */
64569
- errorCode: number().nullable(),
64920
+ errorCode: number().nullable().meta(readingMeta("none")),
64570
64921
  /** Human label for {@link errorCode}, or null when none / undecodable. */
64571
64922
  errorLabel: string().nullable(),
64572
64923
  /** Ms epoch when the slice was last updated. */
@@ -64605,7 +64956,7 @@ object({
64605
64956
  ]),
64606
64957
  /** 0 = fully closed, 100 = fully open. Null when the device has no
64607
64958
  * intermediate position surface. */
64608
- position: number().min(0).max(100).nullable(),
64959
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64609
64960
  /** Ms epoch when the slice was last updated. */
64610
64961
  lastChangedAt: number()
64611
64962
  });
@@ -64633,9 +64984,9 @@ object({
64633
64984
  DeviceType.Sensor;
64634
64985
  object({
64635
64986
  /** Current measured temperature. Null when not reported. */
64636
- currentTemp: number().nullable(),
64987
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
64637
64988
  /** Target temperature setpoint. Null when no setpoint surface. */
64638
- targetTemp: number().nullable(),
64989
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
64639
64990
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
64640
64991
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
64641
64992
  * device reports an unknown state. */
@@ -64646,9 +64997,9 @@ object({
64646
64997
  * has no away surface. */
64647
64998
  away: boolean().nullable(),
64648
64999
  /** HA `min_temp` attribute. Null when not reported. */
64649
- minTemp: number().nullable(),
65000
+ minTemp: number().nullable().meta(readingMeta("temperature")),
64650
65001
  /** HA `max_temp` attribute. Null when not reported. */
64651
- maxTemp: number().nullable(),
65002
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
64652
65003
  /** Ms epoch when the slice was last updated. */
64653
65004
  lastChangedAt: number()
64654
65005
  });
@@ -64672,25 +65023,18 @@ DeviceType.WaterHeater, method(object({
64672
65023
  auth: "protected"
64673
65024
  });
64674
65025
  object({
64675
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
64676
- * when no condition has been reported yet. */
65026
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
64677
65027
  condition: string().nullable(),
64678
- /** Current temperature in the reported unit. Null when not provided. */
64679
- temperature: number().nullable(),
64680
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
64681
- temperatureUnit: string().nullable(),
65028
+ /** Current temperature in Celsius. Null when not provided or refused. */
65029
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
64682
65030
  /** Relative humidity (0..100). Null when not provided. */
64683
- humidity: number().min(0).max(100).nullable(),
64684
- /** Barometric pressure in the reported unit. Null when not provided. */
64685
- pressure: number().nullable(),
64686
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
64687
- pressureUnit: string().nullable(),
64688
- /** Wind speed in the reported unit. Null when not provided. */
64689
- windSpeed: number().nullable(),
64690
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
64691
- windSpeedUnit: string().nullable(),
65031
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
65032
+ /** Barometric pressure in hPa. Null when not provided or refused. */
65033
+ pressure: number().nullable().meta(readingMeta("pressure")),
65034
+ /** Wind speed in metres per second. Null when not provided or refused. */
65035
+ windSpeed: number().nullable().meta(readingMeta("speed")),
64692
65036
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
64693
- windBearing: number().nullable(),
65037
+ windBearing: number().nullable().meta(readingMeta("angle")),
64694
65038
  /** Ms epoch when the slice was last updated. */
64695
65039
  lastFetchedAt: number()
64696
65040
  });
@@ -65602,6 +65946,13 @@ Object.freeze({
65602
65946
  access: "view",
65603
65947
  role: "admin"
65604
65948
  },
65949
+ "addons.getPackageDocs": {
65950
+ capName: "addons",
65951
+ capScope: "system",
65952
+ addonId: null,
65953
+ access: "view",
65954
+ role: "admin"
65955
+ },
65605
65956
  "addons.getVersions": {
65606
65957
  capName: "addons",
65607
65958
  capScope: "system",
@@ -68710,6 +69061,20 @@ Object.freeze({
68710
69061
  access: "view",
68711
69062
  role: "viewer"
68712
69063
  },
69064
+ "groupCommands.getStatus": {
69065
+ capName: "group-commands",
69066
+ capScope: "device",
69067
+ addonId: null,
69068
+ access: "view",
69069
+ role: "viewer"
69070
+ },
69071
+ "groupStatus.getStatus": {
69072
+ capName: "group-status",
69073
+ capScope: "device",
69074
+ addonId: null,
69075
+ access: "view",
69076
+ role: "viewer"
69077
+ },
68713
69078
  "heatingValve.getStatus": {
68714
69079
  capName: "heating-valve",
68715
69080
  capScope: "device",
@@ -78532,6 +78897,37 @@ var SENSOR_CAP_VOCABULARY = {
78532
78897
  icon: "flame"
78533
78898
  }]
78534
78899
  },
78900
+ "group-commands": {
78901
+ scope: "device",
78902
+ noun: t("Group command", "Comando di gruppo")
78903
+ },
78904
+ "group-status": {
78905
+ scope: "device",
78906
+ noun: t("Group", "Gruppo"),
78907
+ state: [
78908
+ {
78909
+ kind: "numeric",
78910
+ field: "included",
78911
+ quantity: t("the members reporting", "i membri che rispondono"),
78912
+ label: t("Reporting", "Rispondono"),
78913
+ icon: "users"
78914
+ },
78915
+ {
78916
+ kind: "numeric",
78917
+ field: "excluded",
78918
+ quantity: t("the members left out", "i membri esclusi"),
78919
+ label: t("Excluded", "Esclusi"),
78920
+ icon: "users"
78921
+ },
78922
+ {
78923
+ kind: "numeric",
78924
+ field: "total",
78925
+ quantity: t("the member count", "il numero di membri"),
78926
+ label: t("Members", "Membri"),
78927
+ icon: "sigma"
78928
+ }
78929
+ ]
78930
+ },
78535
78931
  "humidity-sensor": {
78536
78932
  scope: "device",
78537
78933
  noun: t("Humidity", "Umidità"),
@@ -78565,7 +78961,6 @@ var SENSOR_CAP_VOCABULARY = {
78565
78961
  field: "value",
78566
78962
  quantity: t("the reading", "il valore"),
78567
78963
  label: t("Reading", "Valore"),
78568
- unitField: "unit",
78569
78964
  icon: "gauge"
78570
78965
  }]
78571
78966
  },
@@ -78701,7 +79096,7 @@ var SENSOR_CAP_VOCABULARY = {
78701
79096
  field: "temperature",
78702
79097
  quantity: t("the outdoor temperature", "la temperatura esterna"),
78703
79098
  label: t("Temperature", "Temperatura"),
78704
- unitField: "temperatureUnit",
79099
+ unit: "°C",
78705
79100
  icon: "thermometer"
78706
79101
  },
78707
79102
  {
@@ -78717,7 +79112,7 @@ var SENSOR_CAP_VOCABULARY = {
78717
79112
  field: "windSpeed",
78718
79113
  quantity: t("the wind speed", "la velocità del vento"),
78719
79114
  label: t("Wind", "Vento"),
78720
- unitField: "windSpeedUnit",
79115
+ unit: "m/s",
78721
79116
  icon: "wind"
78722
79117
  },
78723
79118
  {
@@ -78725,7 +79120,7 @@ var SENSOR_CAP_VOCABULARY = {
78725
79120
  field: "pressure",
78726
79121
  quantity: t("the air pressure", "la pressione atmosferica"),
78727
79122
  label: t("Pressure", "Pressione"),
78728
- unitField: "pressureUnit",
79123
+ unit: "hPa",
78729
79124
  icon: "gauge"
78730
79125
  }
78731
79126
  ]
@@ -79998,6 +80393,16 @@ Object.freeze({
79998
80393
  form: "single",
79999
80394
  optional: false
80000
80395
  }],
80396
+ "groupCommands.getStatus": [{
80397
+ name: "deviceId",
80398
+ form: "single",
80399
+ optional: false
80400
+ }],
80401
+ "groupStatus.getStatus": [{
80402
+ name: "deviceId",
80403
+ form: "single",
80404
+ optional: false
80405
+ }],
80001
80406
  "heatingValve.getStatus": [{
80002
80407
  name: "deviceId",
80003
80408
  form: "single",
@@ -81862,6 +82267,11 @@ var PulseOverrideDraftSchema = object({
81862
82267
  targetId: string().min(1),
81863
82268
  enabled: boolean(),
81864
82269
  debounceMs: number().nonnegative(),
82270
+ edges: array(_enum([
82271
+ "rising",
82272
+ "button",
82273
+ "native-event"
82274
+ ])).min(1).optional(),
81865
82275
  sources: array(OverrideSourceSchema).min(1)
81866
82276
  });
81867
82277
  var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
@@ -81977,6 +82387,8 @@ new Map(Object.entries({
81977
82387
  "detection-blind": "device-events",
81978
82388
  "motion-noisy": "device-events",
81979
82389
  "motion-calm": "device-events",
82390
+ "package-llm-silent": "device-events",
82391
+ "package-llm-answered": "device-events",
81980
82392
  "recording-stalled": "device-events",
81981
82393
  "recording-healthy": "device-events",
81982
82394
  "recording-degraded": "device-events",
@@ -82060,6 +82472,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
82060
82472
  label: "Motion without a subject",
82061
82473
  kinds: ["motion-noisy", "motion-calm"]
82062
82474
  },
82475
+ {
82476
+ id: "package-llm",
82477
+ family: "device-events",
82478
+ label: "Package confirm",
82479
+ kinds: ["package-llm-silent", "package-llm-answered"]
82480
+ },
82063
82481
  {
82064
82482
  id: "recording",
82065
82483
  family: "device-events",
@@ -82241,6 +82659,8 @@ var DEVICE_EVENTS = [
82241
82659
  "detection-blind",
82242
82660
  "motion-noisy",
82243
82661
  "motion-calm",
82662
+ "package-llm-silent",
82663
+ "package-llm-answered",
82244
82664
  "recording-stalled",
82245
82665
  "recording-healthy",
82246
82666
  "recording-degraded",
@@ -82509,6 +82929,15 @@ new Map([
82509
82929
  snapshotLabel: "Cameras with constant motion now",
82510
82930
  snapshotSection: null
82511
82931
  },
82932
+ {
82933
+ id: "package-llm-confirm",
82934
+ family: "device-events",
82935
+ label: "Package confirm (not answering / answering again)",
82936
+ bad: ["package-llm-silent"],
82937
+ recovery: ["package-llm-answered"],
82938
+ snapshotLabel: "Cameras whose package confirm is not answering",
82939
+ snapshotSection: null
82940
+ },
82512
82941
  {
82513
82942
  id: "recording-health",
82514
82943
  family: "device-events",