@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.
@@ -1,5 +1,5 @@
1
1
  import { randomBytes } from "node:crypto";
2
- //#region ../types/dist/event-category-DHXxB8t9.mjs
2
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -325,6 +325,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
325
325
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
326
326
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
327
327
  /**
328
+ * A camera's package LLM confirm has failed closed three times in a row
329
+ * (D931, 2026-10-26). One edge per camera until
330
+ * {@link PipelinePackageLlmAnswered}.
331
+ */
332
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
333
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
334
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
335
+ /**
328
336
  * Per-camera pipeline config was mutated by the orchestrator
329
337
  * (a settings change via `setAgentInferenceDevices` /
330
338
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5567,7 +5575,7 @@ var ZodIssueCode = {
5567
5575
  var ZodFirstPartyTypeKind;
5568
5576
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5569
5577
  //#endregion
5570
- //#region ../types/dist/sleep-BErAxhul.mjs
5578
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5571
5579
  /**
5572
5580
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5573
5581
  * window to float samples (D455).
@@ -8309,6 +8317,99 @@ function defineEntryKind(name, spec) {
8309
8317
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8310
8318
  return defined;
8311
8319
  }
8320
+ var READING_QUANTITIES = [
8321
+ "temperature",
8322
+ "pressure",
8323
+ "speed",
8324
+ "precipitation",
8325
+ "precipitation-rate",
8326
+ "length",
8327
+ "area",
8328
+ "illuminance",
8329
+ "irradiance",
8330
+ "power",
8331
+ "energy",
8332
+ "voltage",
8333
+ "current",
8334
+ "apparent-power",
8335
+ "humidity",
8336
+ "percentage",
8337
+ "concentration-volume",
8338
+ "concentration-mass",
8339
+ "concentration-ppb",
8340
+ "volume",
8341
+ "duration",
8342
+ "duration-ms",
8343
+ "information",
8344
+ "sound-level",
8345
+ "signal-level",
8346
+ "colour-temperature",
8347
+ "angle",
8348
+ "bitrate",
8349
+ "rate",
8350
+ "mass",
8351
+ "index",
8352
+ "ratio",
8353
+ "count",
8354
+ "none",
8355
+ "device"
8356
+ ];
8357
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8358
+ new Set(READING_QUANTITIES);
8359
+ /**
8360
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8361
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8362
+ */
8363
+ var QUANTITY_BASE = {
8364
+ temperature: "°C",
8365
+ pressure: "hPa",
8366
+ speed: "m/s",
8367
+ precipitation: "mm",
8368
+ "precipitation-rate": "mm/h",
8369
+ length: "m",
8370
+ area: "m²",
8371
+ illuminance: "lx",
8372
+ irradiance: "W/m²",
8373
+ power: "W",
8374
+ energy: "kWh",
8375
+ voltage: "V",
8376
+ current: "A",
8377
+ "apparent-power": "VA",
8378
+ humidity: "%",
8379
+ percentage: "%",
8380
+ "concentration-volume": "ppm",
8381
+ "concentration-mass": "µg/m³",
8382
+ "concentration-ppb": "ppb",
8383
+ volume: "L",
8384
+ duration: "s",
8385
+ "duration-ms": "ms",
8386
+ information: "B",
8387
+ "sound-level": "dBFS",
8388
+ "signal-level": "dBm",
8389
+ "colour-temperature": "mired",
8390
+ angle: "°",
8391
+ bitrate: "kbps",
8392
+ rate: "fps",
8393
+ mass: "kg",
8394
+ index: "none",
8395
+ ratio: "none",
8396
+ count: "none",
8397
+ none: "none",
8398
+ device: "device"
8399
+ };
8400
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8401
+ function baseUnitOf(quantity) {
8402
+ const symbol = QUANTITY_BASE[quantity];
8403
+ if (symbol === "none" || symbol === "device") return void 0;
8404
+ return symbol;
8405
+ }
8406
+ function readingMeta(quantity, extras) {
8407
+ return {
8408
+ quantity,
8409
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8410
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8411
+ };
8412
+ }
8312
8413
  /** Shared Zod schemas used across streaming capabilities. */
8313
8414
  var CamProfileSchema = _enum([
8314
8415
  "high",
@@ -8336,8 +8437,8 @@ var CamStreamKindSchema = _enum([
8336
8437
  "derived"
8337
8438
  ]);
8338
8439
  var CamStreamResolutionSchema = object({
8339
- width: number().int().positive(),
8340
- height: number().int().positive()
8440
+ width: number().int().positive().meta(readingMeta("none")),
8441
+ height: number().int().positive().meta(readingMeta("none"))
8341
8442
  });
8342
8443
  var CameraStreamSchema = object({
8343
8444
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8851,6 +8952,8 @@ var CAP_NAMES = Object.freeze([
8851
8952
  "filesystem-browse",
8852
8953
  "flood",
8853
8954
  "gas",
8955
+ "group-commands",
8956
+ "group-status",
8854
8957
  "heating-valve",
8855
8958
  "humidifier",
8856
8959
  "humidity-sensor",
@@ -8980,14 +9083,6 @@ Object.freeze({
8980
9083
  {
8981
9084
  "path": "aqi",
8982
9085
  "valueType": "number"
8983
- },
8984
- {
8985
- "path": "unit",
8986
- "valueType": "string"
8987
- },
8988
- {
8989
- "path": "precision",
8990
- "valueType": "number"
8991
9086
  }
8992
9087
  ],
8993
9088
  "alarm-panel": [
@@ -9023,20 +9118,10 @@ Object.freeze({
9023
9118
  "valueType": "boolean"
9024
9119
  }
9025
9120
  ],
9026
- "ambient-light-sensor": [
9027
- {
9028
- "path": "lux",
9029
- "valueType": "number"
9030
- },
9031
- {
9032
- "path": "unit",
9033
- "valueType": "string"
9034
- },
9035
- {
9036
- "path": "precision",
9037
- "valueType": "number"
9038
- }
9039
- ],
9121
+ "ambient-light-sensor": [{
9122
+ "path": "lux",
9123
+ "valueType": "number"
9124
+ }],
9040
9125
  "audio-metrics": [
9041
9126
  {
9042
9127
  "path": "ts",
@@ -9116,6 +9201,10 @@ Object.freeze({
9116
9201
  "path": "lastUpdated",
9117
9202
  "valueType": "number"
9118
9203
  },
9204
+ {
9205
+ "path": "volts",
9206
+ "valueType": "number"
9207
+ },
9119
9208
  {
9120
9209
  "path": "lastContactAt",
9121
9210
  "valueType": "number"
@@ -9358,10 +9447,6 @@ Object.freeze({
9358
9447
  "datetime"
9359
9448
  ]
9360
9449
  },
9361
- {
9362
- "path": "unit",
9363
- "valueType": "string"
9364
- },
9365
9450
  {
9366
9451
  "path": "min",
9367
9452
  "valueType": "number"
@@ -9373,19 +9458,6 @@ Object.freeze({
9373
9458
  {
9374
9459
  "path": "step",
9375
9460
  "valueType": "number"
9376
- },
9377
- {
9378
- "path": "precision",
9379
- "valueType": "number"
9380
- },
9381
- {
9382
- "path": "format",
9383
- "valueType": "enum",
9384
- "enumValues": [
9385
- "date",
9386
- "time",
9387
- "datetime"
9388
- ]
9389
9461
  }
9390
9462
  ],
9391
9463
  "cover": [
@@ -9626,13 +9698,8 @@ Object.freeze({
9626
9698
  "path": "value",
9627
9699
  "valueType": "string"
9628
9700
  }, {
9629
- "path": "format",
9630
- "valueType": "enum",
9631
- "enumValues": [
9632
- "date",
9633
- "time",
9634
- "datetime"
9635
- ]
9701
+ "path": "timestamp",
9702
+ "valueType": "boolean"
9636
9703
  }],
9637
9704
  "event-emitter": [
9638
9705
  {
@@ -9705,6 +9772,20 @@ Object.freeze({
9705
9772
  "path": "detected",
9706
9773
  "valueType": "boolean"
9707
9774
  }],
9775
+ "group-status": [
9776
+ {
9777
+ "path": "included",
9778
+ "valueType": "number"
9779
+ },
9780
+ {
9781
+ "path": "excluded",
9782
+ "valueType": "number"
9783
+ },
9784
+ {
9785
+ "path": "total",
9786
+ "valueType": "number"
9787
+ }
9788
+ ],
9708
9789
  "heating-valve": [{
9709
9790
  "path": "position",
9710
9791
  "valueType": "number"
@@ -9739,20 +9820,10 @@ Object.freeze({
9739
9820
  "valueType": "number"
9740
9821
  }
9741
9822
  ],
9742
- "humidity-sensor": [
9743
- {
9744
- "path": "percent",
9745
- "valueType": "number"
9746
- },
9747
- {
9748
- "path": "unit",
9749
- "valueType": "string"
9750
- },
9751
- {
9752
- "path": "precision",
9753
- "valueType": "number"
9754
- }
9755
- ],
9823
+ "humidity-sensor": [{
9824
+ "path": "percent",
9825
+ "valueType": "number"
9826
+ }],
9756
9827
  "image": [{
9757
9828
  "path": "url",
9758
9829
  "valueType": "string"
@@ -10091,20 +10162,10 @@ Object.freeze({
10091
10162
  "valueType": "boolean"
10092
10163
  }
10093
10164
  ],
10094
- "numeric-sensor": [
10095
- {
10096
- "path": "value",
10097
- "valueType": "number"
10098
- },
10099
- {
10100
- "path": "unit",
10101
- "valueType": "string"
10102
- },
10103
- {
10104
- "path": "precision",
10105
- "valueType": "number"
10106
- }
10107
- ],
10165
+ "numeric-sensor": [{
10166
+ "path": "value",
10167
+ "valueType": "number"
10168
+ }],
10108
10169
  "pet-feeder": [
10109
10170
  {
10110
10171
  "path": "foodLevel",
@@ -10196,14 +10257,6 @@ Object.freeze({
10196
10257
  {
10197
10258
  "path": "powerFactor",
10198
10259
  "valueType": "number"
10199
- },
10200
- {
10201
- "path": "unit",
10202
- "valueType": "string"
10203
- },
10204
- {
10205
- "path": "precision",
10206
- "valueType": "number"
10207
10260
  }
10208
10261
  ],
10209
10262
  "presence": [
@@ -10232,20 +10285,10 @@ Object.freeze({
10232
10285
  "valueType": "number"
10233
10286
  }
10234
10287
  ],
10235
- "pressure-sensor": [
10236
- {
10237
- "path": "hpa",
10238
- "valueType": "number"
10239
- },
10240
- {
10241
- "path": "unit",
10242
- "valueType": "string"
10243
- },
10244
- {
10245
- "path": "precision",
10246
- "valueType": "number"
10247
- }
10248
- ],
10288
+ "pressure-sensor": [{
10289
+ "path": "hpa",
10290
+ "valueType": "number"
10291
+ }],
10249
10292
  "privacy-mask": [{
10250
10293
  "path": "enabled",
10251
10294
  "valueType": "boolean"
@@ -10556,20 +10599,10 @@ Object.freeze({
10556
10599
  "path": "tampered",
10557
10600
  "valueType": "boolean"
10558
10601
  }],
10559
- "temperature-sensor": [
10560
- {
10561
- "path": "celsius",
10562
- "valueType": "number"
10563
- },
10564
- {
10565
- "path": "unit",
10566
- "valueType": "string"
10567
- },
10568
- {
10569
- "path": "precision",
10570
- "valueType": "number"
10571
- }
10572
- ],
10602
+ "temperature-sensor": [{
10603
+ "path": "celsius",
10604
+ "valueType": "number"
10605
+ }],
10573
10606
  "update": [
10574
10607
  {
10575
10608
  "path": "currentVersion",
@@ -10680,10 +10713,6 @@ Object.freeze({
10680
10713
  "path": "temperature",
10681
10714
  "valueType": "number"
10682
10715
  },
10683
- {
10684
- "path": "temperatureUnit",
10685
- "valueType": "string"
10686
- },
10687
10716
  {
10688
10717
  "path": "humidity",
10689
10718
  "valueType": "number"
@@ -10692,18 +10721,10 @@ Object.freeze({
10692
10721
  "path": "pressure",
10693
10722
  "valueType": "number"
10694
10723
  },
10695
- {
10696
- "path": "pressureUnit",
10697
- "valueType": "string"
10698
- },
10699
10724
  {
10700
10725
  "path": "windSpeed",
10701
10726
  "valueType": "number"
10702
10727
  },
10703
- {
10704
- "path": "windSpeedUnit",
10705
- "valueType": "string"
10706
- },
10707
10728
  {
10708
10729
  "path": "windBearing",
10709
10730
  "valueType": "number"
@@ -11110,7 +11131,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11110
11131
  return DeviceRole;
11111
11132
  }({});
11112
11133
  //#endregion
11113
- //#region ../types/dist/composition-kEl9mevu.mjs
11134
+ //#region ../types/dist/composition-OhpuespA.mjs
11114
11135
  /**
11115
11136
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11116
11137
  *
@@ -11865,15 +11886,74 @@ function validateExpressionSource(src) {
11865
11886
  }
11866
11887
  return null;
11867
11888
  }
11868
- /** A composed device's stableId is this prefix plus the block id. */
11869
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11870
- /** Longest snippet a `code` source may carry (slice 4). */
11871
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11889
+ /**
11890
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11891
+ * id. Split out so helper schemas can name it without a cycle through
11892
+ * `composition.ts` (D659).
11893
+ */
11872
11894
  var CompositionSourceRefSchema = object({
11873
11895
  /** The owning addon; `stableId` is unique only within it. */
11874
11896
  addonId: string().min(1),
11875
11897
  stableId: string().min(1)
11876
11898
  });
11899
+ var GROUP_TEMPLATE_IDS = [
11900
+ "temperature",
11901
+ "humidity",
11902
+ "pressure",
11903
+ "illuminance",
11904
+ "power",
11905
+ "energy",
11906
+ "battery",
11907
+ "numeric",
11908
+ "motion",
11909
+ "contact",
11910
+ "leak",
11911
+ "smoke",
11912
+ "occupancy",
11913
+ "binary",
11914
+ "switch",
11915
+ "light"
11916
+ ];
11917
+ var NUMERIC_AGGREGATES = [
11918
+ "mean",
11919
+ "median",
11920
+ "min",
11921
+ "max",
11922
+ "sum"
11923
+ ];
11924
+ var BINARY_AGGREGATES = ["any", "all"];
11925
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11926
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11927
+ var AGGREGATE_REDUCERS = [
11928
+ "mean",
11929
+ "median",
11930
+ "min",
11931
+ "max",
11932
+ "sum",
11933
+ "any",
11934
+ "all"
11935
+ ];
11936
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11937
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11938
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11939
+ /** One member. The field is stored so occupancy can name motion or binary. */
11940
+ var HelperMemberSchema = object({
11941
+ source: CompositionSourceRefSchema,
11942
+ cap: string().min(1),
11943
+ fieldPath: string().min(1)
11944
+ });
11945
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
11946
+ kind: literal("group"),
11947
+ templateId: GroupTemplateIdSchema,
11948
+ aggregate: AggregateReducerSchema,
11949
+ members: array(HelperMemberSchema).min(1).max(64),
11950
+ /** Actuator groups only. Readings leave this unset. */
11951
+ active: ActiveCriterionSchema.optional()
11952
+ })]);
11953
+ /** A composed device's stableId is this prefix plus the block id. */
11954
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11955
+ /** Longest snippet a `code` source may carry (slice 4). */
11956
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11877
11957
  /** One field of one capability of one source device. */
11878
11958
  var CompositionFieldReadSchema = object({
11879
11959
  source: CompositionSourceRefSchema,
@@ -11917,7 +11997,33 @@ var CompositionCodeSourceSchema = object({
11917
11997
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11918
11998
  CompositionFromSourceSchema,
11919
11999
  CompositionExpressionSourceSchema,
11920
- CompositionCodeSourceSchema
12000
+ CompositionCodeSourceSchema,
12001
+ object({
12002
+ kind: literal("aggregate"),
12003
+ reducer: _enum([
12004
+ "mean",
12005
+ "median",
12006
+ "min",
12007
+ "max",
12008
+ "sum",
12009
+ "any",
12010
+ "all"
12011
+ ]),
12012
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
12013
+ /** `value` is the reading; the others are the member counts. */
12014
+ publish: _enum([
12015
+ "value",
12016
+ "included",
12017
+ "excluded",
12018
+ "total"
12019
+ ]),
12020
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
12021
+ empty: _enum([
12022
+ "null",
12023
+ "omit",
12024
+ "offline"
12025
+ ])
12026
+ })
11921
12027
  ]);
11922
12028
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11923
12029
  kind: literal("forward"),
@@ -11963,7 +12069,13 @@ var CompositionSchema = object({
11963
12069
  kind: literal("existing"),
11964
12070
  device: CompositionSourceRefSchema
11965
12071
  })]),
11966
- features: array(CompositionFeatureSchema).min(1).max(16)
12072
+ features: array(CompositionFeatureSchema).min(1).max(16),
12073
+ /**
12074
+ * Present when the block is a helper template. Absent on a free
12075
+ * composition (the room example, device 4603). The editor reads this,
12076
+ * never the compiled features.
12077
+ */
12078
+ helper: HelperSpecSchema.optional()
11967
12079
  }).superRefine((composition, ctx) => {
11968
12080
  const seen = /* @__PURE__ */ new Set();
11969
12081
  composition.features.forEach((feature, index) => {
@@ -18250,7 +18362,11 @@ var CompositionProblemSchema = object({
18250
18362
  "item-field-reserved",
18251
18363
  "target-missing",
18252
18364
  "target-unreadable",
18253
- "not-replaceable"
18365
+ "not-replaceable",
18366
+ "native-capability-replace",
18367
+ "mixed-quantity",
18368
+ "helper-mismatch",
18369
+ "member-constraint"
18254
18370
  ]),
18255
18371
  path: string(),
18256
18372
  message: string()
@@ -18271,7 +18387,8 @@ var CompositionUnavailableModeSchema = _enum([
18271
18387
  "null",
18272
18388
  "offline",
18273
18389
  "unknown",
18274
- "release"
18390
+ "release",
18391
+ "omit"
18275
18392
  ]);
18276
18393
  /**
18277
18394
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -18308,9 +18425,11 @@ var CompositionFieldPlanSchema = object({
18308
18425
  reads: array(CompositionFieldReadSchema)
18309
18426
  });
18310
18427
  /**
18311
- * What a feature does to its target (D663): `new` builds a capability on a new
18312
- * device; on an existing device it `add`s a capability the device lacks, or
18313
- * `replace`s named fields of one it has natively.
18428
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
18429
+ * new device; on an existing device it `add`s a capability the device lacks.
18430
+ * Replacing a capability the device already serves is refused
18431
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
18432
+ * wire for a claim the hub already holds.
18314
18433
  */
18315
18434
  var CompositionFeatureModeSchema = _enum([
18316
18435
  "new",
@@ -18483,6 +18602,275 @@ Object.freeze({
18483
18602
  result: ["number", "null"]
18484
18603
  }
18485
18604
  });
18605
+ /**
18606
+ * One catalog row per base reading a group helper can build (D944 §3).
18607
+ * Data, not a form default written into the block.
18608
+ */
18609
+ var NOW = {
18610
+ fieldPath: "lastFetchedAt",
18611
+ expr: "now"
18612
+ };
18613
+ var CHANGED = {
18614
+ fieldPath: "lastChangedAt",
18615
+ expr: "now"
18616
+ };
18617
+ function numeric(input) {
18618
+ return {
18619
+ ...input,
18620
+ family: "numeric",
18621
+ deviceType: DeviceType.Sensor,
18622
+ memberDeviceType: DeviceType.Sensor,
18623
+ commandCaps: [],
18624
+ baseUnit: baseUnitOf(input.quantity),
18625
+ aggregates: NUMERIC_AGGREGATES,
18626
+ accept: [{
18627
+ cap: input.outputCap,
18628
+ fieldPath: input.outputField
18629
+ }]
18630
+ };
18631
+ }
18632
+ function binary(input) {
18633
+ return {
18634
+ ...input,
18635
+ family: "binary",
18636
+ deviceType: DeviceType.Sensor,
18637
+ memberDeviceType: DeviceType.Sensor,
18638
+ commandCaps: [],
18639
+ quantity: "none",
18640
+ baseUnit: void 0,
18641
+ aggregates: BINARY_AGGREGATES,
18642
+ empty: "offline"
18643
+ };
18644
+ }
18645
+ var GROUP_TEMPLATES = [
18646
+ numeric({
18647
+ id: "temperature",
18648
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18649
+ role: DeviceRole.TemperatureSensor,
18650
+ outputCap: "temperature-sensor",
18651
+ outputField: "celsius",
18652
+ quantity: "temperature",
18653
+ defaultAggregate: "mean",
18654
+ empty: "null",
18655
+ companions: [NOW]
18656
+ }),
18657
+ numeric({
18658
+ id: "humidity",
18659
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18660
+ role: DeviceRole.HumiditySensor,
18661
+ outputCap: "humidity-sensor",
18662
+ outputField: "percent",
18663
+ quantity: "humidity",
18664
+ defaultAggregate: "mean",
18665
+ empty: "null",
18666
+ companions: [NOW]
18667
+ }),
18668
+ numeric({
18669
+ id: "pressure",
18670
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18671
+ role: DeviceRole.PressureSensor,
18672
+ outputCap: "pressure-sensor",
18673
+ outputField: "hpa",
18674
+ quantity: "pressure",
18675
+ defaultAggregate: "mean",
18676
+ empty: "null",
18677
+ companions: [NOW]
18678
+ }),
18679
+ numeric({
18680
+ id: "illuminance",
18681
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18682
+ role: DeviceRole.AmbientLightSensor,
18683
+ outputCap: "ambient-light-sensor",
18684
+ outputField: "lux",
18685
+ quantity: "illuminance",
18686
+ defaultAggregate: "mean",
18687
+ empty: "null",
18688
+ companions: [NOW]
18689
+ }),
18690
+ numeric({
18691
+ id: "power",
18692
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18693
+ role: DeviceRole.PowerSensor,
18694
+ outputCap: "power-meter",
18695
+ outputField: "watts",
18696
+ quantity: "power",
18697
+ defaultAggregate: "sum",
18698
+ empty: "omit",
18699
+ companions: [NOW]
18700
+ }),
18701
+ numeric({
18702
+ id: "energy",
18703
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18704
+ role: DeviceRole.EnergySensor,
18705
+ outputCap: "power-meter",
18706
+ outputField: "kwhTotal",
18707
+ quantity: "energy",
18708
+ defaultAggregate: "sum",
18709
+ empty: "omit",
18710
+ companions: [NOW]
18711
+ }),
18712
+ numeric({
18713
+ id: "battery",
18714
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18715
+ role: DeviceRole.BatterySensor,
18716
+ outputCap: "battery",
18717
+ outputField: "percentage",
18718
+ quantity: "percentage",
18719
+ defaultAggregate: "mean",
18720
+ empty: "null",
18721
+ companions: [
18722
+ {
18723
+ fieldPath: "charging",
18724
+ expr: "\"unknown\""
18725
+ },
18726
+ {
18727
+ fieldPath: "sleeping",
18728
+ expr: "false"
18729
+ },
18730
+ {
18731
+ fieldPath: "lastUpdated",
18732
+ expr: "now"
18733
+ }
18734
+ ]
18735
+ }),
18736
+ numeric({
18737
+ id: "numeric",
18738
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18739
+ role: DeviceRole.NumericSensor,
18740
+ outputCap: "numeric-sensor",
18741
+ outputField: "value",
18742
+ quantity: "device",
18743
+ defaultAggregate: "mean",
18744
+ empty: "null",
18745
+ companions: [NOW]
18746
+ }),
18747
+ binary({
18748
+ id: "motion",
18749
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18750
+ role: DeviceRole.MotionSensor,
18751
+ outputCap: "motion",
18752
+ outputField: "detected",
18753
+ defaultAggregate: "any",
18754
+ accept: [{
18755
+ cap: "motion",
18756
+ fieldPath: "detected"
18757
+ }],
18758
+ companions: [{
18759
+ fieldPath: "lastDetectedAt",
18760
+ expr: "null"
18761
+ }, {
18762
+ fieldPath: "autoClearAfterMs",
18763
+ expr: "null"
18764
+ }]
18765
+ }),
18766
+ binary({
18767
+ id: "contact",
18768
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18769
+ role: DeviceRole.ContactSensor,
18770
+ outputCap: "contact",
18771
+ outputField: "entryOpen",
18772
+ defaultAggregate: "any",
18773
+ accept: [{
18774
+ cap: "contact",
18775
+ fieldPath: "entryOpen"
18776
+ }],
18777
+ companions: [CHANGED]
18778
+ }),
18779
+ binary({
18780
+ id: "leak",
18781
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18782
+ role: DeviceRole.LeakSensor,
18783
+ outputCap: "flood",
18784
+ outputField: "flooded",
18785
+ defaultAggregate: "any",
18786
+ accept: [{
18787
+ cap: "flood",
18788
+ fieldPath: "flooded"
18789
+ }],
18790
+ companions: [CHANGED]
18791
+ }),
18792
+ binary({
18793
+ id: "smoke",
18794
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18795
+ role: DeviceRole.SmokeSensor,
18796
+ outputCap: "smoke",
18797
+ outputField: "detected",
18798
+ defaultAggregate: "any",
18799
+ accept: [{
18800
+ cap: "smoke",
18801
+ fieldPath: "detected"
18802
+ }],
18803
+ companions: [CHANGED]
18804
+ }),
18805
+ binary({
18806
+ id: "occupancy",
18807
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18808
+ role: DeviceRole.BinarySensor,
18809
+ outputCap: "binary",
18810
+ outputField: "on",
18811
+ defaultAggregate: "any",
18812
+ accept: [{
18813
+ cap: "motion",
18814
+ fieldPath: "detected"
18815
+ }, {
18816
+ cap: "binary",
18817
+ fieldPath: "on"
18818
+ }],
18819
+ companions: [CHANGED]
18820
+ }),
18821
+ binary({
18822
+ id: "binary",
18823
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18824
+ role: DeviceRole.BinarySensor,
18825
+ outputCap: "binary",
18826
+ outputField: "on",
18827
+ defaultAggregate: "any",
18828
+ accept: [{
18829
+ cap: "binary",
18830
+ fieldPath: "on"
18831
+ }],
18832
+ companions: [CHANGED]
18833
+ }),
18834
+ actuator({
18835
+ id: "switch",
18836
+ 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.",
18837
+ deviceType: DeviceType.Switch,
18838
+ outputCap: "switch",
18839
+ commandCaps: ["switch"]
18840
+ }),
18841
+ actuator({
18842
+ id: "light",
18843
+ 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.",
18844
+ deviceType: DeviceType.Light,
18845
+ outputCap: "switch",
18846
+ commandCaps: [
18847
+ "switch",
18848
+ "brightness",
18849
+ "color"
18850
+ ]
18851
+ })
18852
+ ];
18853
+ function actuator(input) {
18854
+ return {
18855
+ ...input,
18856
+ family: "actuator",
18857
+ role: void 0,
18858
+ memberDeviceType: input.deviceType,
18859
+ outputField: "on",
18860
+ quantity: "none",
18861
+ baseUnit: void 0,
18862
+ aggregates: BINARY_AGGREGATES,
18863
+ defaultAggregate: "any",
18864
+ empty: "offline",
18865
+ accept: [{
18866
+ cap: "switch",
18867
+ fieldPath: "on"
18868
+ }],
18869
+ companions: [CHANGED]
18870
+ };
18871
+ }
18872
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18873
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18486
18874
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18487
18875
  /**
18488
18876
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -20196,12 +20584,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
20196
20584
  * providers detect renames by matching `uniqueId` and patch `id`
20197
20585
  * in-place so dispatch keeps working.
20198
20586
  *
20199
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
20200
- * `precision`) live in the cap STATUS SLICE so they flow live with every
20201
- * state push and are the single source of truth. The `device_class` tag is
20202
- * an HA-specific input that is consumed at the discovery boundary
20203
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
20204
- * stored after that point.
20587
+ * **Display unit and precision are NOT stored here.** They are a read-time
20588
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20589
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20590
+ * neither is repeated on each sample. The `device_class` tag is an
20591
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20592
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
20205
20593
  *
20206
20594
  * The `raw` escape hatch carries provider-specific attributes that don't
20207
20595
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -20215,6 +20603,14 @@ var SourceInfoSchema = object({
20215
20603
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
20216
20604
  * Used to detect rename when `id` changes. */
20217
20605
  uniqueId: string().optional(),
20606
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20607
+ quantity: ReadingQuantitySchema.optional(),
20608
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20609
+ dateTimeShape: _enum([
20610
+ "date",
20611
+ "time",
20612
+ "datetime"
20613
+ ]).optional(),
20218
20614
  /** Provider-specific extras that don't fit the structured slots.
20219
20615
  * Whitelist what goes here per provider — do NOT dump entire HA
20220
20616
  * attribute blobs (would explode the meta JSON column). */
@@ -41127,7 +41523,8 @@ method(object({
41127
41523
  "enum"
41128
41524
  ]),
41129
41525
  enumValues: array(string()).optional(),
41130
- item: boolean().optional()
41526
+ item: boolean().optional(),
41527
+ quantity: ReadingQuantitySchema.optional()
41131
41528
  })).readonly(),
41132
41529
  itemArray: object({
41133
41530
  path: string(),
@@ -44927,8 +45324,8 @@ var MaskPolygonShapeSchema = object({
44927
45324
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44928
45325
  var MaskGridShapeSchema = object({
44929
45326
  kind: literal("grid"),
44930
- gridWidth: number(),
44931
- gridHeight: number(),
45327
+ gridWidth: number().meta(readingMeta("none")),
45328
+ gridHeight: number().meta(readingMeta("none")),
44932
45329
  cells: array(boolean())
44933
45330
  });
44934
45331
  discriminatedUnion("kind", [
@@ -45040,6 +45437,8 @@ var NcSystemEventKindSchema = _enum([
45040
45437
  "detection-blind",
45041
45438
  "motion-noisy",
45042
45439
  "motion-calm",
45440
+ "package-llm-silent",
45441
+ "package-llm-answered",
45043
45442
  "recording-stalled",
45044
45443
  "recording-healthy",
45045
45444
  "recording-degraded",
@@ -56067,6 +56466,28 @@ var PackageVersionInfoSchema = object({
56067
56466
  deprecated: string().optional(),
56068
56467
  distTags: array(string()).readonly()
56069
56468
  });
56469
+ /**
56470
+ * One published document. `ok` is markdown from the registry. `missing` means
56471
+ * the registry answered and the file was not there. `error` is a registry or
56472
+ * tarball failure — never an empty string standing in for either.
56473
+ */
56474
+ var PackageDocBodySchema = discriminatedUnion("status", [
56475
+ object({
56476
+ status: literal("ok"),
56477
+ markdown: string()
56478
+ }),
56479
+ object({ status: literal("missing") }),
56480
+ object({
56481
+ status: literal("error"),
56482
+ message: string()
56483
+ })
56484
+ ]);
56485
+ var PackageDocsSchema = object({
56486
+ name: string(),
56487
+ version: string(),
56488
+ readme: PackageDocBodySchema,
56489
+ changelog: PackageDocBodySchema
56490
+ });
56070
56491
  var SearchResultSchema = object({
56071
56492
  name: string(),
56072
56493
  version: string(),
@@ -56285,7 +56706,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56285
56706
  }), object({ success: literal(true) }), {
56286
56707
  kind: "mutation",
56287
56708
  auth: "admin"
56288
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
56709
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
56710
+ name: string().min(1),
56711
+ version: string().min(1).optional()
56712
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
56289
56713
  kind: "mutation",
56290
56714
  auth: "admin"
56291
56715
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -56335,46 +56759,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56335
56759
  }), LogStreamEntrySchema, { kind: "subscription" });
56336
56760
  object({
56337
56761
  /** Carbon dioxide concentration in ppm. */
56338
- co2Ppm: number().min(0).optional(),
56762
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
56339
56763
  /** Total volatile organic compounds in ppb. */
56340
- vocPpb: number().min(0).optional(),
56764
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56341
56765
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56342
- pm25: number().min(0).optional(),
56766
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56343
56767
  /** Particulate matter ≤ 10 μm in µg/m³. */
56344
- pm10: number().min(0).optional(),
56768
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
56345
56769
  /** Composite AQI value (typically 0..500). */
56346
- aqi: number().optional(),
56770
+ aqi: number().optional().meta(readingMeta("index")),
56347
56771
  /** Ms epoch when the slice was last updated. */
56348
- lastFetchedAt: number(),
56349
- /** Live display unit of the single metric this slice carries (e.g. HA
56350
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
56351
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
56352
- * per slice is unambiguous. */
56353
- unit: string().optional(),
56354
- /** Suggested decimal places for numeric display.
56355
- * Populated live from the upstream source when provided (e.g. HA
56356
- * `attributes.suggested_display_precision`). Falls back to
56357
- * auto-formatting when absent. */
56358
- precision: number().int().min(0).max(10).optional()
56772
+ lastFetchedAt: number()
56359
56773
  });
56360
56774
  DeviceType.Sensor;
56361
56775
  object({
56362
- /** Current illuminance in lux (lx). */
56363
- lux: number().min(0).meta({
56364
- unit: "lx",
56776
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56777
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
56778
+ precision: 0,
56365
56779
  label: "Illuminance"
56366
- }),
56780
+ })),
56367
56781
  /** Ms epoch when the slice was last updated. */
56368
- lastFetchedAt: number(),
56369
- /** Live display unit from the upstream source (e.g. HA
56370
- * `attributes.unit_of_measurement`). The UI prefers this over the
56371
- * role's canonical unit. Absent → fall back to the canonical unit. */
56372
- unit: string().optional(),
56373
- /** Suggested decimal places for numeric display.
56374
- * Populated live from the upstream source when provided (e.g. HA
56375
- * `attributes.suggested_display_precision`). Falls back to
56376
- * auto-formatting when absent. */
56377
- precision: number().int().min(0).max(10).optional()
56782
+ lastFetchedAt: number()
56378
56783
  });
56379
56784
  DeviceType.Sensor;
56380
56785
  /**
@@ -56519,10 +56924,7 @@ var BatteryStatusSchema = object({
56519
56924
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
56520
56925
  * declines to band a non-finite reading, which is the correct shape.
56521
56926
  */
56522
- percentage: number().min(0).max(100).nullable().meta({
56523
- unit: "%",
56524
- label: "Charge"
56525
- }),
56927
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
56526
56928
  /**
56527
56929
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
56528
56930
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -56549,7 +56951,14 @@ var BatteryStatusSchema = object({
56549
56951
  */
56550
56952
  sleeping: boolean().meta({ label: "Sleeping" }),
56551
56953
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
56552
- lastUpdated: number(),
56954
+ lastUpdated: number().meta(readingMeta("none")),
56955
+ /**
56956
+ * Battery voltage in volts, for a cell the firmware reports as voltage
56957
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
56958
+ * that only report charge. A second reading on this cap, not a second
56959
+ * `numeric-sensor` on the same device.
56960
+ */
56961
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
56553
56962
  /**
56554
56963
  * Ms epoch of the last time the device PROVED it was reachable — a
56555
56964
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -56609,7 +57018,7 @@ object({
56609
57018
  DeviceType.Sensor;
56610
57019
  object({
56611
57020
  /** Current level as 0..100 inclusive. Firmware-reported. */
56612
- percentage: number().min(0).max(100),
57021
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
56613
57022
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
56614
57023
  lastChangedAt: number()
56615
57024
  });
@@ -57004,21 +57413,21 @@ object({
57004
57413
  /** Single setpoint in Celsius. Used by single-target modes
57005
57414
  * (heat / cool / dry). Null when the device is in a range mode
57006
57415
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
57007
- target: number().nullable(),
57416
+ target: number().nullable().meta(readingMeta("temperature")),
57008
57417
  /** Upper bound of the dual setpoint in Celsius. Populated only
57009
57418
  * in `heat_cool` mode. */
57010
- targetHigh: number().nullable(),
57419
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
57011
57420
  /** Lower bound of the dual setpoint in Celsius. Populated only
57012
57421
  * in `heat_cool` mode. */
57013
- targetLow: number().nullable(),
57422
+ targetLow: number().nullable().meta(readingMeta("temperature")),
57014
57423
  /** Target relative humidity (0..100). Null when the device has no
57015
57424
  * humidity control. */
57016
- targetHumidity: number().min(0).max(100).nullable(),
57425
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
57017
57426
  /** Current measured temperature in Celsius. Read-only — pushed by
57018
57427
  * the device's internal sensor. */
57019
- currentTemp: number().nullable(),
57428
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
57020
57429
  /** Current measured relative humidity (0..100). Read-only. */
57021
- currentHumidity: number().min(0).max(100).nullable(),
57430
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
57022
57431
  /** Vertical louver swing on/off. Null when the device has no vertical
57023
57432
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
57024
57433
  swingVertical: boolean().nullable(),
@@ -57026,11 +57435,11 @@ object({
57026
57435
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
57027
57436
  swingHorizontal: boolean().nullable(),
57028
57437
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
57029
- minTemp: number().nullable().optional(),
57438
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
57030
57439
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
57031
- maxTemp: number().nullable().optional(),
57440
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
57032
57441
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
57033
- tempStep: number().positive().nullable().optional(),
57442
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
57034
57443
  /** Ms epoch when the slice was last updated (push or command). */
57035
57444
  lastFetchedAt: number()
57036
57445
  });
@@ -57109,17 +57518,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
57109
57518
  * modes to expose in the picker without a slice fetch.
57110
57519
  */
57111
57520
  var RgbTripletSchema = object({
57112
- r: number().int().min(0).max(255),
57113
- g: number().int().min(0).max(255),
57114
- b: number().int().min(0).max(255)
57521
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
57522
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
57523
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
57115
57524
  });
57116
57525
  var HsvTripletSchema = object({
57117
57526
  /** Hue in degrees, 0..360. */
57118
- h: number().min(0).max(360),
57527
+ h: number().min(0).max(360).meta(readingMeta("none")),
57119
57528
  /** Saturation as 0..100 inclusive. */
57120
- s: number().min(0).max(100),
57529
+ s: number().min(0).max(100).meta(readingMeta("none")),
57121
57530
  /** Value/brightness as 0..100 inclusive. */
57122
- v: number().min(0).max(100)
57531
+ v: number().min(0).max(100).meta(readingMeta("none"))
57123
57532
  });
57124
57533
  var ColorInputSchema = discriminatedUnion("mode", [
57125
57534
  object({
@@ -57148,7 +57557,7 @@ object({
57148
57557
  /** Populated when `mode === 'hsv'`. */
57149
57558
  hsv: HsvTripletSchema.optional(),
57150
57559
  /** Populated when `mode === 'mired'`. */
57151
- mireds: number().int().optional(),
57560
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
57152
57561
  /** Ms epoch of the last operator-driven change. */
57153
57562
  lastChangedAt: number()
57154
57563
  });
@@ -57272,18 +57681,12 @@ _enum([
57272
57681
  "empty",
57273
57682
  "full"
57274
57683
  ]);
57275
- /** Units a remaining-life counter is reported in. Closed: the panel renders
57276
- * each. */
57277
- var ConsumableRemainingUnitSchema = _enum([
57278
- "days",
57279
- "hours",
57280
- "cycles"
57281
- ]);
57282
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
57283
- * days). `value` null = unknown (D393). */
57684
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
57685
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
57686
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
57284
57687
  var ConsumableRemainingSchema = object({
57285
- value: number().min(0).nullable(),
57286
- unit: ConsumableRemainingUnitSchema
57688
+ value: number().min(0).nullable().meta(readingMeta("device")),
57689
+ kind: ConsumableRemainingKindSchema
57287
57690
  });
57288
57691
  var ConsumablesStatusSchema = object({
57289
57692
  items: array(object({
@@ -57292,7 +57695,7 @@ var ConsumablesStatusSchema = object({
57292
57695
  /** Display name. */
57293
57696
  label: string().min(1),
57294
57697
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
57295
- level: number().min(0).max(100).nullable(),
57698
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57296
57699
  /** The provider's discrete verdict when known (binary mode). */
57297
57700
  status: ConsumableStatusSchema.nullable(),
57298
57701
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -57309,7 +57712,7 @@ var ConsumablesStatusSchema = object({
57309
57712
  })),
57310
57713
  lastChangedAt: number()
57311
57714
  });
57312
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57715
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57313
57716
  Object.values(DeviceType), method(object({
57314
57717
  deviceId: number().int().nonnegative(),
57315
57718
  key: string().min(1)
@@ -57334,39 +57737,17 @@ object({
57334
57737
  /** Current value. Numeric kind → number; select/text/datetime → string.
57335
57738
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
57336
57739
  * or time-only — HA accepts all three forms). */
57337
- value: union([number(), string()]),
57740
+ value: union([number().meta(readingMeta("device")), string()]),
57338
57741
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
57339
57742
  options: array(string()),
57340
57743
  /** Ms epoch when the slice was last updated. */
57341
57744
  lastChangedAt: number(),
57342
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
57343
- * Populated live from HA `attributes.unit_of_measurement` on each push.
57344
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57345
- unit: string().optional(),
57346
- /** Minimum allowed value. Populated live from HA `attributes.min`.
57347
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57348
- min: number().optional(),
57349
- /** Maximum allowed value. Populated live from HA `attributes.max`.
57350
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57351
- max: number().optional(),
57352
- /** Step increment. Populated live from HA `attributes.step`.
57353
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57354
- step: number().optional(),
57355
- /** Suggested decimal places for numeric display. Populated live from HA
57356
- * `attributes.suggested_display_precision`, or derived from `step` when the
57357
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
57358
- * other kinds. Falls back to auto-formatting when absent. */
57359
- precision: number().int().min(0).max(10).optional(),
57360
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
57361
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
57362
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
57363
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
57364
- * `has_date`/`has_time` attributes on each push. */
57365
- format: _enum([
57366
- "date",
57367
- "time",
57368
- "datetime"
57369
- ]).optional()
57745
+ /** Minimum allowed value, in the device quantity's base unit. */
57746
+ min: number().optional().meta(readingMeta("device")),
57747
+ /** Maximum allowed value, in the device quantity's base unit. */
57748
+ max: number().optional().meta(readingMeta("device")),
57749
+ /** Step increment, in the device quantity's base unit. */
57750
+ step: number().optional().meta(readingMeta("device"))
57370
57751
  });
57371
57752
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
57372
57753
  object({
@@ -57404,9 +57785,9 @@ object({
57404
57785
  ]),
57405
57786
  /** 0 = fully closed, 100 = fully open. Null when the device has
57406
57787
  * no intermediate position surface. */
57407
- position: number().min(0).max(100).nullable(),
57788
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57408
57789
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
57409
- tiltPosition: number().min(0).max(100).nullable(),
57790
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57410
57791
  /** Ms epoch when the slice was last updated. */
57411
57792
  lastChangedAt: number()
57412
57793
  });
@@ -57464,9 +57845,9 @@ var NormalizedRangeSchema$1 = object({
57464
57845
  object({
57465
57846
  mode: DayNightModeSchema,
57466
57847
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
57467
- sensitivity: number().optional(),
57848
+ sensitivity: number().optional().meta(readingMeta("none")),
57468
57849
  /** Delay before the IR-cut filter flips, in seconds. */
57469
- switchDelaySec: number().optional(),
57850
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
57470
57851
  lastFetchedAt: number()
57471
57852
  });
57472
57853
  /**
@@ -57621,11 +58002,11 @@ object({
57621
58002
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
57622
58003
  availablePrograms: array(string()),
57623
58004
  /** Seconds remaining on the active cycle, or null when not running / not known. */
57624
- remainingSeconds: number().int().nonnegative().nullable(),
58005
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57625
58006
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
57626
- delayedStartSeconds: number().int().nonnegative().nullable(),
58007
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57627
58008
  /** Cycle completion 0..100, or null when not applicable. */
57628
- progressPercent: number().min(0).max(100).nullable(),
58009
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57629
58010
  /** True when the door is reported open; null when the door state is not known. */
57630
58011
  doorOpen: boolean().nullable(),
57631
58012
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -57649,7 +58030,7 @@ object({
57649
58030
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
57650
58031
  machineCareDue: boolean().nullable(),
57651
58032
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
57652
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
58033
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
57653
58034
  /** Option states: null when the appliance does not expose the option or has not said. */
57654
58035
  halfLoad: boolean().nullable(),
57655
58036
  hygienePlus: boolean().nullable(),
@@ -57664,7 +58045,7 @@ object({
57664
58045
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
57665
58046
  activeEvents: array(string()),
57666
58047
  /** Numeric error code when the appliance reports one; null otherwise. */
57667
- errorCode: number().nullable(),
58048
+ errorCode: number().nullable().meta(readingMeta("none")),
57668
58049
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
57669
58050
  errorLabel: string().nullable(),
57670
58051
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -57810,24 +58191,14 @@ object({
57810
58191
  /** Ms epoch of the last press. null = never observed since this provider started. */
57811
58192
  lastPressedAt: number().nullable(),
57812
58193
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
57813
- pressCountSinceStart: number()
58194
+ pressCountSinceStart: number().meta(readingMeta("count"))
57814
58195
  });
57815
58196
  object({
57816
58197
  deviceId: number(),
57817
58198
  timestamp: number()
57818
58199
  });
57819
58200
  DeviceType.Button, DeviceType.Camera;
57820
- /**
57821
- * Enum-state sensor — a string value picked from a finite option set.
57822
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
57823
- * states, weather conditions, contact-source channels, mode strings).
57824
- * The option set is stable across observations; it is persisted in the
57825
- * device config blob (via the control slice at adoption) and is NOT
57826
- * stored in `sourceInfo`.
57827
- */
57828
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
57829
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
57830
- var EnumSensorDateTimeFormatSchema = _enum([
58201
+ _enum([
57831
58202
  "date",
57832
58203
  "time",
57833
58204
  "datetime"
@@ -57835,12 +58206,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
57835
58206
  object({
57836
58207
  value: string(),
57837
58208
  /**
57838
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
57839
- * as a locale date/time/datetime; absent for plain enum sensors. HA
57840
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
57841
- * a time-only sensor → `'time'`.
58209
+ * Set when `value` is a timestamp rather than an enum state, so history
58210
+ * does not learn ISO strings as states. The display shape (date / time /
58211
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
57842
58212
  */
57843
- format: EnumSensorDateTimeFormatSchema.optional(),
58213
+ timestamp: boolean().optional(),
57844
58214
  /** Ms epoch when the slice was last updated. */
57845
58215
  lastFetchedAt: number()
57846
58216
  });
@@ -57859,13 +58229,13 @@ var EventFireSchema = object({
57859
58229
  deviceId: number(),
57860
58230
  eventType: string(),
57861
58231
  data: record(string(), unknown()).nullable(),
57862
- timestamp: number(),
57863
- seq: number()
58232
+ timestamp: number().meta(readingMeta("none")),
58233
+ seq: number().meta(readingMeta("count"))
57864
58234
  });
57865
58235
  object({
57866
58236
  eventTypes: array(string()),
57867
58237
  lastEvent: EventFireSchema.nullable(),
57868
- eventCountSinceStart: number()
58238
+ eventCountSinceStart: number().meta(readingMeta("count"))
57869
58239
  });
57870
58240
  DeviceType.EventEmitter;
57871
58241
  var IdentitySchema = object({
@@ -58254,10 +58624,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
58254
58624
  object({
58255
58625
  /** Active speed as 0..100 inclusive. Null when the device has no
58256
58626
  * speed surface (single-speed fan). */
58257
- percentage: number().min(0).max(100).nullable(),
58627
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58258
58628
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
58259
58629
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
58260
- percentageStep: number().positive().optional(),
58630
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
58261
58631
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
58262
58632
  * — empty string when no preset is active or supported. */
58263
58633
  preset: string(),
@@ -58339,7 +58709,7 @@ var FeatureProbeStatusSchema = object({
58339
58709
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
58340
58710
  model: string().nullable(),
58341
58711
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
58342
- channelCount: number().nullable(),
58712
+ channelCount: number().nullable().meta(readingMeta("count")),
58343
58713
  /**
58344
58714
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
58345
58715
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -58371,9 +58741,39 @@ object({
58371
58741
  lastChangedAt: number()
58372
58742
  });
58373
58743
  DeviceType.Sensor;
58744
+ object({
58745
+ /** One row per member that has been commanded. A later command replaces that member's row. */
58746
+ results: array(object({
58747
+ deviceId: number().int().nonnegative(),
58748
+ cap: string().min(1),
58749
+ method: string().min(1),
58750
+ ok: boolean(),
58751
+ error: string().optional(),
58752
+ /** Ms epoch of the attempt. */
58753
+ at: number()
58754
+ })) });
58755
+ DeviceType.Switch, DeviceType.Light;
58756
+ object({
58757
+ /** Members whose current value entered the aggregate. */
58758
+ included: number().int().nonnegative().meta(readingMeta("count", {
58759
+ precision: 0,
58760
+ label: "Reporting"
58761
+ })),
58762
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
58763
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
58764
+ precision: 0,
58765
+ label: "Excluded"
58766
+ })),
58767
+ /** `included` + `excluded`. */
58768
+ total: number().int().nonnegative().meta(readingMeta("count", {
58769
+ precision: 0,
58770
+ label: "Members"
58771
+ }))
58772
+ });
58773
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
58374
58774
  object({
58375
58775
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
58376
- position: number().min(0).max(100).nullable(),
58776
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58377
58777
  /** Ms epoch when the slice was last updated (push or poll). */
58378
58778
  lastFetchedAt: number()
58379
58779
  });
@@ -58382,9 +58782,9 @@ object({
58382
58782
  /** Whether the humidifier is currently on. */
58383
58783
  on: boolean(),
58384
58784
  /** Current measured relative humidity (0..100). Null when not reported. */
58385
- currentHumidity: number().min(0).max(100).nullable(),
58785
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58386
58786
  /** Target relative humidity (0..100). Null when no setpoint surface. */
58387
- targetHumidity: number().min(0).max(100).nullable(),
58787
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58388
58788
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
58389
58789
  * device has no mode surface. */
58390
58790
  mode: string().nullable(),
@@ -58394,9 +58794,9 @@ object({
58394
58794
  * `idle` / `off`). Null when the device doesn't report it. */
58395
58795
  action: string().nullable(),
58396
58796
  /** HA `min_humidity` attribute. Null → UI uses 0. */
58397
- minHumidity: number().nullable(),
58797
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
58398
58798
  /** HA `max_humidity` attribute. Null → UI uses 100. */
58399
- maxHumidity: number().nullable(),
58799
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
58400
58800
  /** Ms epoch when the slice was last updated. */
58401
58801
  lastChangedAt: number()
58402
58802
  });
@@ -58420,22 +58820,13 @@ DeviceType.Humidifier, method(object({
58420
58820
  auth: "protected"
58421
58821
  });
58422
58822
  object({
58423
- /** Current relative humidity, 0..100. */
58424
- percent: number().min(0).max(100).meta({
58425
- unit: "%",
58823
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
58824
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
58825
+ precision: 0,
58426
58826
  label: "Humidity"
58427
- }),
58827
+ })),
58428
58828
  /** Ms epoch when the slice was last updated. */
58429
- lastFetchedAt: number(),
58430
- /** Live display unit from the upstream source (e.g. HA
58431
- * `attributes.unit_of_measurement`). The UI prefers this over the
58432
- * role's canonical unit. Absent → fall back to the canonical unit. */
58433
- unit: string().optional(),
58434
- /** Suggested decimal places for numeric display.
58435
- * Populated live from the upstream source when provided (e.g. HA
58436
- * `attributes.suggested_display_precision`). Falls back to
58437
- * auto-formatting when absent. */
58438
- precision: number().int().min(0).max(10).optional()
58829
+ lastFetchedAt: number()
58439
58830
  });
58440
58831
  DeviceType.Sensor, DeviceType.Thermostat;
58441
58832
  object({
@@ -58443,7 +58834,7 @@ object({
58443
58834
  * entity exposes no `entity_picture` (yet). */
58444
58835
  url: string().nullable(),
58445
58836
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
58446
- lastUpdated: number().nullable()
58837
+ lastUpdated: number().nullable().meta(readingMeta("none"))
58447
58838
  });
58448
58839
  DeviceType.Image;
58449
58840
  /**
@@ -58499,19 +58890,19 @@ var NormalizedRangeSchema = object({
58499
58890
  });
58500
58891
  object({
58501
58892
  /** Normalized 0–100. */
58502
- brightness: number().optional(),
58893
+ brightness: number().optional().meta(readingMeta("none")),
58503
58894
  /** Normalized 0–100. */
58504
- contrast: number().optional(),
58895
+ contrast: number().optional().meta(readingMeta("none")),
58505
58896
  /** Normalized 0–100. */
58506
- saturation: number().optional(),
58897
+ saturation: number().optional().meta(readingMeta("none")),
58507
58898
  /** Normalized 0–100. */
58508
- sharpness: number().optional(),
58899
+ sharpness: number().optional().meta(readingMeta("none")),
58509
58900
  mirror: boolean().optional(),
58510
58901
  flip: boolean().optional(),
58511
58902
  rotate: ImageRotateSchema.optional(),
58512
58903
  whiteBalance: WhiteBalanceModeSchema.optional(),
58513
58904
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
58514
- warmth: number().optional(),
58905
+ warmth: number().optional().meta(readingMeta("none")),
58515
58906
  exposureMode: ExposureModeSchema.optional(),
58516
58907
  backlightMode: BacklightModeSchema.optional(),
58517
58908
  lastFetchedAt: number()
@@ -58756,10 +59147,10 @@ var IntercomAbilitySchema = object({
58756
59147
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
58757
59148
  codecs: array(string()),
58758
59149
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
58759
- sampleRate: number(),
59150
+ sampleRate: number().meta(readingMeta("none")),
58760
59151
  duplex: _enum(["half", "full"]),
58761
59152
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
58762
- maxBacklogMs: number()
59153
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
58763
59154
  });
58764
59155
  var IntercomStatusSchema = object({
58765
59156
  /** True while a talk session is open. */
@@ -58870,13 +59261,13 @@ object({
58870
59261
  activity: LawnMowerActivitySchema,
58871
59262
  /** 0..100 battery percentage. Null when the device has no battery
58872
59263
  * reading. */
58873
- batteryLevel: number().min(0).max(100).nullable(),
59264
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58874
59265
  /** 0..100 mowing-completion percentage of the current task, or null when no
58875
59266
  * task is active / progress is unavailable. */
58876
- progressPercent: number().min(0).max(100).nullable(),
59267
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58877
59268
  /** Current device/event code (dynamic — mostly status, sometimes an error),
58878
59269
  * or null when unknown. */
58879
- currentCode: number().nullable(),
59270
+ currentCode: number().nullable().meta(readingMeta("none")),
58880
59271
  /** Human label for {@link currentCode}, or null when undecodable. */
58881
59272
  currentCodeLabel: string().nullable(),
58882
59273
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -59338,7 +59729,7 @@ object({
59338
59729
  state: MediaPlayerStateSchema,
59339
59730
  /** Volume as 0..100 inclusive. Null when the device has no volume
59340
59731
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59341
- volumeLevel: number().min(0).max(100).nullable(),
59732
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59342
59733
  /** Mute toggle distinct from volume=0. Null when no mute surface.
59343
59734
  * Pair with `DeviceFeature.MediaPlayerMute`. */
59344
59735
  isMuted: boolean().nullable(),
@@ -59351,10 +59742,10 @@ object({
59351
59742
  currentMedia: MediaInfoSchema.nullable(),
59352
59743
  /** Current playback position in ms. Null when not seekable or
59353
59744
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59354
- positionMs: number().int().nonnegative().nullable(),
59745
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59355
59746
  /** Total duration of the current media in ms. Null when unknown
59356
59747
  * (live stream) or nothing is playing. */
59357
- durationMs: number().int().nonnegative().nullable(),
59748
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59358
59749
  /** Shuffle toggle. Null when no shuffle surface.
59359
59750
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59360
59751
  shuffle: boolean().nullable(),
@@ -59654,7 +60045,7 @@ object({
59654
60045
  * it, every provider seeded it at boot, and every session left it
59655
60046
  * `detected: true` after its detach.
59656
60047
  */
59657
- autoClearAfterMs: number().nullable()
60048
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
59658
60049
  });
59659
60050
  /**
59660
60051
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -59704,7 +60095,7 @@ var MotionZoneRegionSchema = object({
59704
60095
  });
59705
60096
  object({
59706
60097
  enabled: boolean(),
59707
- sensitivity: number(),
60098
+ sensitivity: number().meta(readingMeta("none")),
59708
60099
  /** Grid region(s). Today exactly one `grid` shape. */
59709
60100
  regions: array(MotionZoneRegionSchema),
59710
60101
  lastFetchedAt: number()
@@ -60008,7 +60399,7 @@ var NavigationStatusSchema = object({
60008
60399
  following: boolean(),
60009
60400
  flash: boolean(),
60010
60401
  lightMode: NavigationLightModeSchema,
60011
- lightLevel: number().min(40).max(100),
60402
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
60012
60403
  /** Ms epoch when the slice was last updated. */
60013
60404
  lastChangedAt: number()
60014
60405
  });
@@ -60054,13 +60445,13 @@ var NetworkLinkStatusSchema = object({
60054
60445
  * one whose reading has not landed must not be drawn at 0 %. Consumers
60055
60446
  * SKIP a null rather than coerce it.
60056
60447
  */
60057
- signalPercent: number().min(0).max(100).nullable(),
60448
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60058
60449
  /** Raw received signal strength in dBm, when the firmware reports one. */
60059
- rssiDbm: number().optional(),
60450
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
60060
60451
  /** Network name of a wireless link, when the firmware reports it. */
60061
60452
  ssid: string().optional(),
60062
60453
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60063
- lastUpdated: number()
60454
+ lastUpdated: number().meta(readingMeta("none"))
60064
60455
  });
60065
60456
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
60066
60457
  deviceId: number(),
@@ -60379,7 +60770,7 @@ object({
60379
60770
  lastError: string().nullable(),
60380
60771
  /** Number of deliveries currently buffered server-side (rate-limited
60381
60772
  * notifiers may queue). 0 when the provider sends synchronously. */
60382
- queueDepth: number().int().nonnegative(),
60773
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
60383
60774
  /** Per-feature capability matrix — authoritative source for the
60384
60775
  * compose-form field gating. */
60385
60776
  supports: NotifierSupportsSchema
@@ -60429,15 +60820,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
60429
60820
  acceptedAt: number()
60430
60821
  });
60431
60822
  object({
60432
- value: number(),
60433
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
60434
- * Populated live from the upstream source on each state push.
60435
- * Absent for unitless sensors. */
60436
- unit: string().optional(),
60437
- /** Suggested decimal places for numeric display.
60438
- * Populated live from the upstream source when provided.
60439
- * Falls back to auto-formatting when absent. */
60440
- precision: number().int().min(0).max(10).optional(),
60823
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
60824
+ value: number().nullable().meta(readingMeta("device")),
60441
60825
  /** Ms epoch when the slice was last updated. */
60442
60826
  lastFetchedAt: number()
60443
60827
  });
@@ -60727,23 +61111,26 @@ object({
60727
61111
  /** Food currently in the bowl (grams). Null when the device has not
60728
61112
  * reported a reading yet. On dual-hopper models this is the combined
60729
61113
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
60730
- foodLevel: number().nullable(),
61114
+ foodLevel: number().nullable().meta(readingMeta("none")),
60731
61115
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
60732
61116
  * single-hopper models. */
60733
- food1: number().nullable(),
61117
+ food1: number().nullable().meta(readingMeta("none")),
60734
61118
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
60735
61119
  * single-hopper models. */
60736
- food2: number().nullable(),
61120
+ food2: number().nullable().meta(readingMeta("none")),
60737
61121
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
60738
61122
  * (`device_class: problem`, on = low). True when the bowl is empty /
60739
61123
  * below the feeder's low threshold. */
60740
61124
  lowFood: boolean(),
60741
61125
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
60742
61126
  * device has no battery reading. */
60743
- batteryPower: number().min(0).max(100).nullable(),
60744
- /** Days of desiccant life remaining. Null when the model has no
60745
- * desiccant sensor. */
60746
- desiccantLeftDays: number().nullable(),
61127
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61128
+ /**
61129
+ * Desiccant life remaining, stored in seconds (duration base, D944).
61130
+ * Providers convert from the days the feeder reports. Null when the
61131
+ * model has no desiccant sensor.
61132
+ */
61133
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
60747
61134
  /** True while a feed is in progress. */
60748
61135
  feeding: boolean(),
60749
61136
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -60754,7 +61141,7 @@ object({
60754
61141
  * with `errorCode` for consumers that want the raw integer. */
60755
61142
  error: string().nullable(),
60756
61143
  /** Raw device error code (0 / null = no error). */
60757
- errorCode: number().nullable(),
61144
+ errorCode: number().nullable().meta(readingMeta("none")),
60758
61145
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
60759
61146
  isDualHopper: boolean(),
60760
61147
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -60764,7 +61151,7 @@ object({
60764
61151
  /** Play a chime when dispensing. */
60765
61152
  feedSound: boolean(),
60766
61153
  /** Speaker / prompt volume level (device-scaled integer). */
60767
- volume: number(),
61154
+ volume: number().meta(readingMeta("none")),
60768
61155
  /** Ms epoch when the slice was last refreshed from the cloud. */
60769
61156
  lastFetchedAt: number()
60770
61157
  });
@@ -61201,36 +61588,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
61201
61588
  });
61202
61589
  object({
61203
61590
  /** Instantaneous power draw in watts. */
61204
- watts: number().optional().meta({
61205
- unit: "W",
61206
- label: "Power"
61207
- }),
61591
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
61208
61592
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61209
- kwhTotal: number().optional().meta({
61210
- unit: "kWh",
61211
- label: "Energy"
61212
- }),
61593
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61213
61594
  /** Voltage in volts. */
61214
- volts: number().optional(),
61595
+ volts: number().optional().meta(readingMeta("voltage")),
61215
61596
  /** Current in amperes. */
61216
- amps: number().optional(),
61217
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
61218
- va: number().optional(),
61597
+ amps: number().optional().meta(readingMeta("current")),
61598
+ /** Apparent power in volt-amperes. */
61599
+ va: number().optional().meta(readingMeta("apparent-power")),
61219
61600
  /** Power factor (0..1) if reported. */
61220
- powerFactor: number().min(0).max(1).optional(),
61601
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
61221
61602
  /** Ms epoch when the slice was last updated. */
61222
- lastFetchedAt: number(),
61223
- /** Live display unit of the single metric this slice carries (e.g. HA
61224
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
61225
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
61226
- * unit per slice is unambiguous. The UI prefers this over the role's
61227
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
61228
- unit: string().optional(),
61229
- /** Suggested decimal places for numeric display.
61230
- * Populated live from the upstream source when provided (e.g. HA
61231
- * `attributes.suggested_display_precision`). Falls back to
61232
- * auto-formatting when absent. */
61233
- precision: number().int().min(0).max(10).optional()
61603
+ lastFetchedAt: number()
61234
61604
  });
61235
61605
  DeviceType.Sensor;
61236
61606
  /**
@@ -61251,11 +61621,11 @@ DeviceType.Sensor;
61251
61621
  */
61252
61622
  var GpsLocationSchema = object({
61253
61623
  /** Latitude in decimal degrees, -90..90. */
61254
- latitude: number().min(-90).max(90),
61624
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
61255
61625
  /** Longitude in decimal degrees, -180..180. */
61256
- longitude: number().min(-180).max(180),
61626
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
61257
61627
  /** Reported accuracy in meters (lower = better). */
61258
- accuracyMeters: number().nonnegative()
61628
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
61259
61629
  });
61260
61630
  object({
61261
61631
  /** `home` / `not_home` / any user-defined zone name. */
@@ -61267,25 +61637,16 @@ object({
61267
61637
  gps: GpsLocationSchema.nullable(),
61268
61638
  /** Optional battery level of the tracking device (0..100). Null
61269
61639
  * when not reported. */
61270
- batteryPercent: number().min(0).max(100).nullable(),
61640
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61271
61641
  /** Ms epoch when the slice was last updated. */
61272
61642
  lastChangedAt: number()
61273
61643
  });
61274
61644
  DeviceType.Presence;
61275
61645
  object({
61276
- /** Current pressure in hPa. */
61277
- hpa: number(),
61646
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
61647
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
61278
61648
  /** Ms epoch when the slice was last updated. */
61279
- lastFetchedAt: number(),
61280
- /** Live display unit from the upstream source (e.g. HA
61281
- * `attributes.unit_of_measurement`). The UI prefers this over the
61282
- * role's canonical unit. Absent → fall back to the canonical unit. */
61283
- unit: string().optional(),
61284
- /** Suggested decimal places for numeric display.
61285
- * Populated live from the upstream source when provided (e.g. HA
61286
- * `attributes.suggested_display_precision`). Falls back to
61287
- * auto-formatting when absent. */
61288
- precision: number().int().min(0).max(10).optional()
61649
+ lastFetchedAt: number()
61289
61650
  });
61290
61651
  DeviceType.Sensor;
61291
61652
  /**
@@ -61618,8 +61979,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
61618
61979
  }), _void(), { kind: "mutation" });
61619
61980
  var PtzAutotrackSettingsSchema = object({
61620
61981
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
61621
- stopDelaySeconds: number().int().min(0).max(300),
61622
- disappearDelaySeconds: number().int().min(0).max(300)
61982
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
61983
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
61623
61984
  });
61624
61985
  /**
61625
61986
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -61676,7 +62037,7 @@ object({
61676
62037
  /** null = unknown — never coerced to false (D393). */
61677
62038
  running: boolean().nullable(),
61678
62039
  /** Epoch ms the current boolean began; null while `running` is null. */
61679
- since: number().nullable(),
62040
+ since: number().nullable().meta(readingMeta("none")),
61680
62041
  source: RunningStateSourceSchema.meta({ label: "Source" }),
61681
62042
  phase: string().nullable(),
61682
62043
  /** null until a reading has been published (D393). */
@@ -62874,9 +63235,9 @@ var RecordTriggerSchema = _enum([
62874
63235
  */
62875
63236
  var RecordWindowSchema = object({
62876
63237
  trigger: RecordTriggerSchema,
62877
- day: number().int().min(0).max(6),
62878
- startMinute: number().int().min(0).max(1439),
62879
- endMinute: number().int().min(1).max(1440)
63238
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
63239
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
63240
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
62880
63241
  });
62881
63242
  /** Status of one physical volume, as the camera itself describes it. */
62882
63243
  var OnboardStorageVolumeSchema = object({
@@ -62945,10 +63306,10 @@ object({
62945
63306
  /** Master "record to the card at all" switch. */
62946
63307
  enabled: boolean().nullable(),
62947
63308
  overwriteWhenFull: boolean().nullable(),
62948
- preRecordSec: number().nullable(),
62949
- postRecordSec: number().nullable(),
63309
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
63310
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
62950
63311
  /** Length of one recorded file, in minutes. */
62951
- segmentMinutes: number().nullable(),
63312
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
62952
63313
  /** The primary track's weekly windows, flattened. */
62953
63314
  windows: array(RecordWindowSchema),
62954
63315
  /**
@@ -62964,7 +63325,7 @@ object({
62964
63325
  * provider reporting a non-zero count also reports the schedule as not
62965
63326
  * writable.
62966
63327
  */
62967
- unreadableWindows: number(),
63328
+ unreadableWindows: number().meta(readingMeta("count")),
62968
63329
  /**
62969
63330
  * The camera is scheduled to record and has NO usable storage.
62970
63331
  *
@@ -63601,7 +63962,7 @@ var CamStreamDescriptorSchema = object({
63601
63962
  url: string().optional(),
63602
63963
  codec: string().optional(),
63603
63964
  resolution: CamStreamResolutionSchema.optional(),
63604
- fps: number().positive().optional(),
63965
+ fps: number().positive().optional().meta(readingMeta("rate")),
63605
63966
  label: string().optional(),
63606
63967
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
63607
63968
  deviceFeatures: array(string()).optional(),
@@ -63631,18 +63992,18 @@ var StreamProfileSchema = _enum([
63631
63992
  ]);
63632
63993
  /** The current encoding config of one stream profile. */
63633
63994
  var StreamProfileConfigSchema = object({
63634
- width: number(),
63635
- height: number(),
63995
+ width: number().meta(readingMeta("none")),
63996
+ height: number().meta(readingMeta("none")),
63636
63997
  codec: _enum(["h264", "h265"]),
63637
- framerate: number(),
63638
- bitrate: number(),
63998
+ framerate: number().meta(readingMeta("rate")),
63999
+ bitrate: number().meta(readingMeta("bitrate")),
63639
64000
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
63640
64001
  encoderProfile: _enum([
63641
64002
  "high",
63642
64003
  "main",
63643
64004
  "baseline"
63644
64005
  ]).optional(),
63645
- gop: number().optional(),
64006
+ gop: number().optional().meta(readingMeta("none")),
63646
64007
  /**
63647
64008
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
63648
64009
  *
@@ -64285,23 +64646,13 @@ object({
64285
64646
  });
64286
64647
  DeviceType.Sensor;
64287
64648
  object({
64288
- /** Current temperature in Celsius. */
64289
- celsius: number().meta({
64290
- unit: "°C",
64649
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64650
+ celsius: number().nullable().meta(readingMeta("temperature", {
64651
+ precision: 1,
64291
64652
  label: "Temperature"
64292
- }),
64653
+ })),
64293
64654
  /** Ms epoch when the slice was last updated (push or poll). */
64294
- lastFetchedAt: number(),
64295
- /** Live display unit from the upstream source (e.g. HA
64296
- * `attributes.unit_of_measurement`). The UI prefers this over the
64297
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
64298
- * Absent → the UI falls back to the role's canonical unit. */
64299
- unit: string().optional(),
64300
- /** Suggested decimal places for numeric display.
64301
- * Populated live from the upstream source when provided (e.g. HA
64302
- * `attributes.suggested_display_precision`). Falls back to
64303
- * auto-formatting when absent. */
64304
- precision: number().int().min(0).max(10).optional()
64655
+ lastFetchedAt: number()
64305
64656
  });
64306
64657
  DeviceType.Sensor, DeviceType.Thermostat;
64307
64658
  /**
@@ -64553,16 +64904,16 @@ object({
64553
64904
  ]),
64554
64905
  /** 0..100 battery percentage. Null when the device has no battery
64555
64906
  * reading. */
64556
- batteryLevel: number().min(0).max(100).nullable(),
64907
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64557
64908
  /** Current fan-speed token (provider-verbatim). Null when unknown or
64558
64909
  * the vacuum has no speed control. */
64559
64910
  fanSpeed: string().nullable(),
64560
64911
  /** Speed tokens the hardware accepts — drives the UI selector. */
64561
64912
  availableFanSpeeds: array(string()),
64562
64913
  /** 0..100 cleaning-completion percentage of the current task, or null. */
64563
- progressPercent: number().min(0).max(100).nullable(),
64914
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64564
64915
  /** Current error code (0 / null = no error). */
64565
- errorCode: number().nullable(),
64916
+ errorCode: number().nullable().meta(readingMeta("none")),
64566
64917
  /** Human label for {@link errorCode}, or null when none / undecodable. */
64567
64918
  errorLabel: string().nullable(),
64568
64919
  /** Ms epoch when the slice was last updated. */
@@ -64601,7 +64952,7 @@ object({
64601
64952
  ]),
64602
64953
  /** 0 = fully closed, 100 = fully open. Null when the device has no
64603
64954
  * intermediate position surface. */
64604
- position: number().min(0).max(100).nullable(),
64955
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64605
64956
  /** Ms epoch when the slice was last updated. */
64606
64957
  lastChangedAt: number()
64607
64958
  });
@@ -64629,9 +64980,9 @@ object({
64629
64980
  DeviceType.Sensor;
64630
64981
  object({
64631
64982
  /** Current measured temperature. Null when not reported. */
64632
- currentTemp: number().nullable(),
64983
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
64633
64984
  /** Target temperature setpoint. Null when no setpoint surface. */
64634
- targetTemp: number().nullable(),
64985
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
64635
64986
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
64636
64987
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
64637
64988
  * device reports an unknown state. */
@@ -64642,9 +64993,9 @@ object({
64642
64993
  * has no away surface. */
64643
64994
  away: boolean().nullable(),
64644
64995
  /** HA `min_temp` attribute. Null when not reported. */
64645
- minTemp: number().nullable(),
64996
+ minTemp: number().nullable().meta(readingMeta("temperature")),
64646
64997
  /** HA `max_temp` attribute. Null when not reported. */
64647
- maxTemp: number().nullable(),
64998
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
64648
64999
  /** Ms epoch when the slice was last updated. */
64649
65000
  lastChangedAt: number()
64650
65001
  });
@@ -64668,25 +65019,18 @@ DeviceType.WaterHeater, method(object({
64668
65019
  auth: "protected"
64669
65020
  });
64670
65021
  object({
64671
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
64672
- * when no condition has been reported yet. */
65022
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
64673
65023
  condition: string().nullable(),
64674
- /** Current temperature in the reported unit. Null when not provided. */
64675
- temperature: number().nullable(),
64676
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
64677
- temperatureUnit: string().nullable(),
65024
+ /** Current temperature in Celsius. Null when not provided or refused. */
65025
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
64678
65026
  /** Relative humidity (0..100). Null when not provided. */
64679
- humidity: number().min(0).max(100).nullable(),
64680
- /** Barometric pressure in the reported unit. Null when not provided. */
64681
- pressure: number().nullable(),
64682
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
64683
- pressureUnit: string().nullable(),
64684
- /** Wind speed in the reported unit. Null when not provided. */
64685
- windSpeed: number().nullable(),
64686
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
64687
- windSpeedUnit: string().nullable(),
65027
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
65028
+ /** Barometric pressure in hPa. Null when not provided or refused. */
65029
+ pressure: number().nullable().meta(readingMeta("pressure")),
65030
+ /** Wind speed in metres per second. Null when not provided or refused. */
65031
+ windSpeed: number().nullable().meta(readingMeta("speed")),
64688
65032
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
64689
- windBearing: number().nullable(),
65033
+ windBearing: number().nullable().meta(readingMeta("angle")),
64690
65034
  /** Ms epoch when the slice was last updated. */
64691
65035
  lastFetchedAt: number()
64692
65036
  });
@@ -65598,6 +65942,13 @@ Object.freeze({
65598
65942
  access: "view",
65599
65943
  role: "admin"
65600
65944
  },
65945
+ "addons.getPackageDocs": {
65946
+ capName: "addons",
65947
+ capScope: "system",
65948
+ addonId: null,
65949
+ access: "view",
65950
+ role: "admin"
65951
+ },
65601
65952
  "addons.getVersions": {
65602
65953
  capName: "addons",
65603
65954
  capScope: "system",
@@ -68706,6 +69057,20 @@ Object.freeze({
68706
69057
  access: "view",
68707
69058
  role: "viewer"
68708
69059
  },
69060
+ "groupCommands.getStatus": {
69061
+ capName: "group-commands",
69062
+ capScope: "device",
69063
+ addonId: null,
69064
+ access: "view",
69065
+ role: "viewer"
69066
+ },
69067
+ "groupStatus.getStatus": {
69068
+ capName: "group-status",
69069
+ capScope: "device",
69070
+ addonId: null,
69071
+ access: "view",
69072
+ role: "viewer"
69073
+ },
68709
69074
  "heatingValve.getStatus": {
68710
69075
  capName: "heating-valve",
68711
69076
  capScope: "device",
@@ -78528,6 +78893,37 @@ var SENSOR_CAP_VOCABULARY = {
78528
78893
  icon: "flame"
78529
78894
  }]
78530
78895
  },
78896
+ "group-commands": {
78897
+ scope: "device",
78898
+ noun: t("Group command", "Comando di gruppo")
78899
+ },
78900
+ "group-status": {
78901
+ scope: "device",
78902
+ noun: t("Group", "Gruppo"),
78903
+ state: [
78904
+ {
78905
+ kind: "numeric",
78906
+ field: "included",
78907
+ quantity: t("the members reporting", "i membri che rispondono"),
78908
+ label: t("Reporting", "Rispondono"),
78909
+ icon: "users"
78910
+ },
78911
+ {
78912
+ kind: "numeric",
78913
+ field: "excluded",
78914
+ quantity: t("the members left out", "i membri esclusi"),
78915
+ label: t("Excluded", "Esclusi"),
78916
+ icon: "users"
78917
+ },
78918
+ {
78919
+ kind: "numeric",
78920
+ field: "total",
78921
+ quantity: t("the member count", "il numero di membri"),
78922
+ label: t("Members", "Membri"),
78923
+ icon: "sigma"
78924
+ }
78925
+ ]
78926
+ },
78531
78927
  "humidity-sensor": {
78532
78928
  scope: "device",
78533
78929
  noun: t("Humidity", "Umidità"),
@@ -78561,7 +78957,6 @@ var SENSOR_CAP_VOCABULARY = {
78561
78957
  field: "value",
78562
78958
  quantity: t("the reading", "il valore"),
78563
78959
  label: t("Reading", "Valore"),
78564
- unitField: "unit",
78565
78960
  icon: "gauge"
78566
78961
  }]
78567
78962
  },
@@ -78697,7 +79092,7 @@ var SENSOR_CAP_VOCABULARY = {
78697
79092
  field: "temperature",
78698
79093
  quantity: t("the outdoor temperature", "la temperatura esterna"),
78699
79094
  label: t("Temperature", "Temperatura"),
78700
- unitField: "temperatureUnit",
79095
+ unit: "°C",
78701
79096
  icon: "thermometer"
78702
79097
  },
78703
79098
  {
@@ -78713,7 +79108,7 @@ var SENSOR_CAP_VOCABULARY = {
78713
79108
  field: "windSpeed",
78714
79109
  quantity: t("the wind speed", "la velocità del vento"),
78715
79110
  label: t("Wind", "Vento"),
78716
- unitField: "windSpeedUnit",
79111
+ unit: "m/s",
78717
79112
  icon: "wind"
78718
79113
  },
78719
79114
  {
@@ -78721,7 +79116,7 @@ var SENSOR_CAP_VOCABULARY = {
78721
79116
  field: "pressure",
78722
79117
  quantity: t("the air pressure", "la pressione atmosferica"),
78723
79118
  label: t("Pressure", "Pressione"),
78724
- unitField: "pressureUnit",
79119
+ unit: "hPa",
78725
79120
  icon: "gauge"
78726
79121
  }
78727
79122
  ]
@@ -79994,6 +80389,16 @@ Object.freeze({
79994
80389
  form: "single",
79995
80390
  optional: false
79996
80391
  }],
80392
+ "groupCommands.getStatus": [{
80393
+ name: "deviceId",
80394
+ form: "single",
80395
+ optional: false
80396
+ }],
80397
+ "groupStatus.getStatus": [{
80398
+ name: "deviceId",
80399
+ form: "single",
80400
+ optional: false
80401
+ }],
79997
80402
  "heatingValve.getStatus": [{
79998
80403
  name: "deviceId",
79999
80404
  form: "single",
@@ -81858,6 +82263,11 @@ var PulseOverrideDraftSchema = object({
81858
82263
  targetId: string().min(1),
81859
82264
  enabled: boolean(),
81860
82265
  debounceMs: number().nonnegative(),
82266
+ edges: array(_enum([
82267
+ "rising",
82268
+ "button",
82269
+ "native-event"
82270
+ ])).min(1).optional(),
81861
82271
  sources: array(OverrideSourceSchema).min(1)
81862
82272
  });
81863
82273
  var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
@@ -81973,6 +82383,8 @@ new Map(Object.entries({
81973
82383
  "detection-blind": "device-events",
81974
82384
  "motion-noisy": "device-events",
81975
82385
  "motion-calm": "device-events",
82386
+ "package-llm-silent": "device-events",
82387
+ "package-llm-answered": "device-events",
81976
82388
  "recording-stalled": "device-events",
81977
82389
  "recording-healthy": "device-events",
81978
82390
  "recording-degraded": "device-events",
@@ -82056,6 +82468,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
82056
82468
  label: "Motion without a subject",
82057
82469
  kinds: ["motion-noisy", "motion-calm"]
82058
82470
  },
82471
+ {
82472
+ id: "package-llm",
82473
+ family: "device-events",
82474
+ label: "Package confirm",
82475
+ kinds: ["package-llm-silent", "package-llm-answered"]
82476
+ },
82059
82477
  {
82060
82478
  id: "recording",
82061
82479
  family: "device-events",
@@ -82237,6 +82655,8 @@ var DEVICE_EVENTS = [
82237
82655
  "detection-blind",
82238
82656
  "motion-noisy",
82239
82657
  "motion-calm",
82658
+ "package-llm-silent",
82659
+ "package-llm-answered",
82240
82660
  "recording-stalled",
82241
82661
  "recording-healthy",
82242
82662
  "recording-degraded",
@@ -82505,6 +82925,15 @@ new Map([
82505
82925
  snapshotLabel: "Cameras with constant motion now",
82506
82926
  snapshotSection: null
82507
82927
  },
82928
+ {
82929
+ id: "package-llm-confirm",
82930
+ family: "device-events",
82931
+ label: "Package confirm (not answering / answering again)",
82932
+ bad: ["package-llm-silent"],
82933
+ recovery: ["package-llm-answered"],
82934
+ snapshotLabel: "Cameras whose package confirm is not answering",
82935
+ snapshotSection: null
82936
+ },
82508
82937
  {
82509
82938
  id: "recording-health",
82510
82939
  family: "device-events",