@camstack/addon-waste-collections 0.1.28 → 0.1.30

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.
@@ -6,7 +6,7 @@ let node_fs = require("node:fs");
6
6
  let node_path = require("node:path");
7
7
  let node_child_process = require("node:child_process");
8
8
  let node_url = require("node:url");
9
- //#region ../types/dist/event-category-DHXxB8t9.mjs
9
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
10
10
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
11
11
  EventCategory["SystemBoot"] = "system.boot";
12
12
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -332,6 +332,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
332
332
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
333
333
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
334
334
  /**
335
+ * A camera's package LLM confirm has failed closed three times in a row
336
+ * (D931, 2026-10-26). One edge per camera until
337
+ * {@link PipelinePackageLlmAnswered}.
338
+ */
339
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
340
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
341
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
342
+ /**
335
343
  * Per-camera pipeline config was mutated by the orchestrator
336
344
  * (a settings change via `setAgentInferenceDevices` /
337
345
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5574,7 +5582,7 @@ var ZodIssueCode = {
5574
5582
  var ZodFirstPartyTypeKind;
5575
5583
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5576
5584
  //#endregion
5577
- //#region ../types/dist/sleep-BErAxhul.mjs
5585
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5578
5586
  /**
5579
5587
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5580
5588
  * window to float samples (D455).
@@ -8326,6 +8334,99 @@ function defineEntryKind(name, spec) {
8326
8334
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8327
8335
  return defined;
8328
8336
  }
8337
+ var READING_QUANTITIES = [
8338
+ "temperature",
8339
+ "pressure",
8340
+ "speed",
8341
+ "precipitation",
8342
+ "precipitation-rate",
8343
+ "length",
8344
+ "area",
8345
+ "illuminance",
8346
+ "irradiance",
8347
+ "power",
8348
+ "energy",
8349
+ "voltage",
8350
+ "current",
8351
+ "apparent-power",
8352
+ "humidity",
8353
+ "percentage",
8354
+ "concentration-volume",
8355
+ "concentration-mass",
8356
+ "concentration-ppb",
8357
+ "volume",
8358
+ "duration",
8359
+ "duration-ms",
8360
+ "information",
8361
+ "sound-level",
8362
+ "signal-level",
8363
+ "colour-temperature",
8364
+ "angle",
8365
+ "bitrate",
8366
+ "rate",
8367
+ "mass",
8368
+ "index",
8369
+ "ratio",
8370
+ "count",
8371
+ "none",
8372
+ "device"
8373
+ ];
8374
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8375
+ new Set(READING_QUANTITIES);
8376
+ /**
8377
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8378
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8379
+ */
8380
+ var QUANTITY_BASE = {
8381
+ temperature: "°C",
8382
+ pressure: "hPa",
8383
+ speed: "m/s",
8384
+ precipitation: "mm",
8385
+ "precipitation-rate": "mm/h",
8386
+ length: "m",
8387
+ area: "m²",
8388
+ illuminance: "lx",
8389
+ irradiance: "W/m²",
8390
+ power: "W",
8391
+ energy: "kWh",
8392
+ voltage: "V",
8393
+ current: "A",
8394
+ "apparent-power": "VA",
8395
+ humidity: "%",
8396
+ percentage: "%",
8397
+ "concentration-volume": "ppm",
8398
+ "concentration-mass": "µg/m³",
8399
+ "concentration-ppb": "ppb",
8400
+ volume: "L",
8401
+ duration: "s",
8402
+ "duration-ms": "ms",
8403
+ information: "B",
8404
+ "sound-level": "dBFS",
8405
+ "signal-level": "dBm",
8406
+ "colour-temperature": "mired",
8407
+ angle: "°",
8408
+ bitrate: "kbps",
8409
+ rate: "fps",
8410
+ mass: "kg",
8411
+ index: "none",
8412
+ ratio: "none",
8413
+ count: "none",
8414
+ none: "none",
8415
+ device: "device"
8416
+ };
8417
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8418
+ function baseUnitOf(quantity) {
8419
+ const symbol = QUANTITY_BASE[quantity];
8420
+ if (symbol === "none" || symbol === "device") return void 0;
8421
+ return symbol;
8422
+ }
8423
+ function readingMeta(quantity, extras) {
8424
+ return {
8425
+ quantity,
8426
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8427
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8428
+ };
8429
+ }
8329
8430
  /** Shared Zod schemas used across streaming capabilities. */
8330
8431
  var CamProfileSchema = _enum([
8331
8432
  "high",
@@ -8353,8 +8454,8 @@ var CamStreamKindSchema = _enum([
8353
8454
  "derived"
8354
8455
  ]);
8355
8456
  var CamStreamResolutionSchema = object({
8356
- width: number().int().positive(),
8357
- height: number().int().positive()
8457
+ width: number().int().positive().meta(readingMeta("none")),
8458
+ height: number().int().positive().meta(readingMeta("none"))
8358
8459
  });
8359
8460
  var CameraStreamSchema = object({
8360
8461
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8890,6 +8991,8 @@ var CAP_NAMES = Object.freeze([
8890
8991
  "filesystem-browse",
8891
8992
  "flood",
8892
8993
  "gas",
8994
+ "group-commands",
8995
+ "group-status",
8893
8996
  "heating-valve",
8894
8997
  "humidifier",
8895
8998
  "humidity-sensor",
@@ -9019,14 +9122,6 @@ Object.freeze({
9019
9122
  {
9020
9123
  "path": "aqi",
9021
9124
  "valueType": "number"
9022
- },
9023
- {
9024
- "path": "unit",
9025
- "valueType": "string"
9026
- },
9027
- {
9028
- "path": "precision",
9029
- "valueType": "number"
9030
9125
  }
9031
9126
  ],
9032
9127
  "alarm-panel": [
@@ -9062,20 +9157,10 @@ Object.freeze({
9062
9157
  "valueType": "boolean"
9063
9158
  }
9064
9159
  ],
9065
- "ambient-light-sensor": [
9066
- {
9067
- "path": "lux",
9068
- "valueType": "number"
9069
- },
9070
- {
9071
- "path": "unit",
9072
- "valueType": "string"
9073
- },
9074
- {
9075
- "path": "precision",
9076
- "valueType": "number"
9077
- }
9078
- ],
9160
+ "ambient-light-sensor": [{
9161
+ "path": "lux",
9162
+ "valueType": "number"
9163
+ }],
9079
9164
  "audio-metrics": [
9080
9165
  {
9081
9166
  "path": "ts",
@@ -9155,6 +9240,10 @@ Object.freeze({
9155
9240
  "path": "lastUpdated",
9156
9241
  "valueType": "number"
9157
9242
  },
9243
+ {
9244
+ "path": "volts",
9245
+ "valueType": "number"
9246
+ },
9158
9247
  {
9159
9248
  "path": "lastContactAt",
9160
9249
  "valueType": "number"
@@ -9397,10 +9486,6 @@ Object.freeze({
9397
9486
  "datetime"
9398
9487
  ]
9399
9488
  },
9400
- {
9401
- "path": "unit",
9402
- "valueType": "string"
9403
- },
9404
9489
  {
9405
9490
  "path": "min",
9406
9491
  "valueType": "number"
@@ -9412,19 +9497,6 @@ Object.freeze({
9412
9497
  {
9413
9498
  "path": "step",
9414
9499
  "valueType": "number"
9415
- },
9416
- {
9417
- "path": "precision",
9418
- "valueType": "number"
9419
- },
9420
- {
9421
- "path": "format",
9422
- "valueType": "enum",
9423
- "enumValues": [
9424
- "date",
9425
- "time",
9426
- "datetime"
9427
- ]
9428
9500
  }
9429
9501
  ],
9430
9502
  "cover": [
@@ -9665,13 +9737,8 @@ Object.freeze({
9665
9737
  "path": "value",
9666
9738
  "valueType": "string"
9667
9739
  }, {
9668
- "path": "format",
9669
- "valueType": "enum",
9670
- "enumValues": [
9671
- "date",
9672
- "time",
9673
- "datetime"
9674
- ]
9740
+ "path": "timestamp",
9741
+ "valueType": "boolean"
9675
9742
  }],
9676
9743
  "event-emitter": [
9677
9744
  {
@@ -9744,6 +9811,20 @@ Object.freeze({
9744
9811
  "path": "detected",
9745
9812
  "valueType": "boolean"
9746
9813
  }],
9814
+ "group-status": [
9815
+ {
9816
+ "path": "included",
9817
+ "valueType": "number"
9818
+ },
9819
+ {
9820
+ "path": "excluded",
9821
+ "valueType": "number"
9822
+ },
9823
+ {
9824
+ "path": "total",
9825
+ "valueType": "number"
9826
+ }
9827
+ ],
9747
9828
  "heating-valve": [{
9748
9829
  "path": "position",
9749
9830
  "valueType": "number"
@@ -9778,20 +9859,10 @@ Object.freeze({
9778
9859
  "valueType": "number"
9779
9860
  }
9780
9861
  ],
9781
- "humidity-sensor": [
9782
- {
9783
- "path": "percent",
9784
- "valueType": "number"
9785
- },
9786
- {
9787
- "path": "unit",
9788
- "valueType": "string"
9789
- },
9790
- {
9791
- "path": "precision",
9792
- "valueType": "number"
9793
- }
9794
- ],
9862
+ "humidity-sensor": [{
9863
+ "path": "percent",
9864
+ "valueType": "number"
9865
+ }],
9795
9866
  "image": [{
9796
9867
  "path": "url",
9797
9868
  "valueType": "string"
@@ -10130,20 +10201,10 @@ Object.freeze({
10130
10201
  "valueType": "boolean"
10131
10202
  }
10132
10203
  ],
10133
- "numeric-sensor": [
10134
- {
10135
- "path": "value",
10136
- "valueType": "number"
10137
- },
10138
- {
10139
- "path": "unit",
10140
- "valueType": "string"
10141
- },
10142
- {
10143
- "path": "precision",
10144
- "valueType": "number"
10145
- }
10146
- ],
10204
+ "numeric-sensor": [{
10205
+ "path": "value",
10206
+ "valueType": "number"
10207
+ }],
10147
10208
  "pet-feeder": [
10148
10209
  {
10149
10210
  "path": "foodLevel",
@@ -10235,14 +10296,6 @@ Object.freeze({
10235
10296
  {
10236
10297
  "path": "powerFactor",
10237
10298
  "valueType": "number"
10238
- },
10239
- {
10240
- "path": "unit",
10241
- "valueType": "string"
10242
- },
10243
- {
10244
- "path": "precision",
10245
- "valueType": "number"
10246
10299
  }
10247
10300
  ],
10248
10301
  "presence": [
@@ -10271,20 +10324,10 @@ Object.freeze({
10271
10324
  "valueType": "number"
10272
10325
  }
10273
10326
  ],
10274
- "pressure-sensor": [
10275
- {
10276
- "path": "hpa",
10277
- "valueType": "number"
10278
- },
10279
- {
10280
- "path": "unit",
10281
- "valueType": "string"
10282
- },
10283
- {
10284
- "path": "precision",
10285
- "valueType": "number"
10286
- }
10287
- ],
10327
+ "pressure-sensor": [{
10328
+ "path": "hpa",
10329
+ "valueType": "number"
10330
+ }],
10288
10331
  "privacy-mask": [{
10289
10332
  "path": "enabled",
10290
10333
  "valueType": "boolean"
@@ -10595,20 +10638,10 @@ Object.freeze({
10595
10638
  "path": "tampered",
10596
10639
  "valueType": "boolean"
10597
10640
  }],
10598
- "temperature-sensor": [
10599
- {
10600
- "path": "celsius",
10601
- "valueType": "number"
10602
- },
10603
- {
10604
- "path": "unit",
10605
- "valueType": "string"
10606
- },
10607
- {
10608
- "path": "precision",
10609
- "valueType": "number"
10610
- }
10611
- ],
10641
+ "temperature-sensor": [{
10642
+ "path": "celsius",
10643
+ "valueType": "number"
10644
+ }],
10612
10645
  "update": [
10613
10646
  {
10614
10647
  "path": "currentVersion",
@@ -10719,10 +10752,6 @@ Object.freeze({
10719
10752
  "path": "temperature",
10720
10753
  "valueType": "number"
10721
10754
  },
10722
- {
10723
- "path": "temperatureUnit",
10724
- "valueType": "string"
10725
- },
10726
10755
  {
10727
10756
  "path": "humidity",
10728
10757
  "valueType": "number"
@@ -10731,18 +10760,10 @@ Object.freeze({
10731
10760
  "path": "pressure",
10732
10761
  "valueType": "number"
10733
10762
  },
10734
- {
10735
- "path": "pressureUnit",
10736
- "valueType": "string"
10737
- },
10738
10763
  {
10739
10764
  "path": "windSpeed",
10740
10765
  "valueType": "number"
10741
10766
  },
10742
- {
10743
- "path": "windSpeedUnit",
10744
- "valueType": "string"
10745
- },
10746
10767
  {
10747
10768
  "path": "windBearing",
10748
10769
  "valueType": "number"
@@ -11135,7 +11156,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11135
11156
  return DeviceRole;
11136
11157
  }({});
11137
11158
  //#endregion
11138
- //#region ../types/dist/composition-kEl9mevu.mjs
11159
+ //#region ../types/dist/composition-OhpuespA.mjs
11139
11160
  /**
11140
11161
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11141
11162
  *
@@ -11890,15 +11911,74 @@ function validateExpressionSource(src) {
11890
11911
  }
11891
11912
  return null;
11892
11913
  }
11893
- /** A composed device's stableId is this prefix plus the block id. */
11894
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11895
- /** Longest snippet a `code` source may carry (slice 4). */
11896
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11914
+ /**
11915
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11916
+ * id. Split out so helper schemas can name it without a cycle through
11917
+ * `composition.ts` (D659).
11918
+ */
11897
11919
  var CompositionSourceRefSchema = object({
11898
11920
  /** The owning addon; `stableId` is unique only within it. */
11899
11921
  addonId: string().min(1),
11900
11922
  stableId: string().min(1)
11901
11923
  });
11924
+ var GROUP_TEMPLATE_IDS = [
11925
+ "temperature",
11926
+ "humidity",
11927
+ "pressure",
11928
+ "illuminance",
11929
+ "power",
11930
+ "energy",
11931
+ "battery",
11932
+ "numeric",
11933
+ "motion",
11934
+ "contact",
11935
+ "leak",
11936
+ "smoke",
11937
+ "occupancy",
11938
+ "binary",
11939
+ "switch",
11940
+ "light"
11941
+ ];
11942
+ var NUMERIC_AGGREGATES = [
11943
+ "mean",
11944
+ "median",
11945
+ "min",
11946
+ "max",
11947
+ "sum"
11948
+ ];
11949
+ var BINARY_AGGREGATES = ["any", "all"];
11950
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11951
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11952
+ var AGGREGATE_REDUCERS = [
11953
+ "mean",
11954
+ "median",
11955
+ "min",
11956
+ "max",
11957
+ "sum",
11958
+ "any",
11959
+ "all"
11960
+ ];
11961
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11962
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11963
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11964
+ /** One member. The field is stored so occupancy can name motion or binary. */
11965
+ var HelperMemberSchema = object({
11966
+ source: CompositionSourceRefSchema,
11967
+ cap: string().min(1),
11968
+ fieldPath: string().min(1)
11969
+ });
11970
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
11971
+ kind: literal("group"),
11972
+ templateId: GroupTemplateIdSchema,
11973
+ aggregate: AggregateReducerSchema,
11974
+ members: array(HelperMemberSchema).min(1).max(64),
11975
+ /** Actuator groups only. Readings leave this unset. */
11976
+ active: ActiveCriterionSchema.optional()
11977
+ })]);
11978
+ /** A composed device's stableId is this prefix plus the block id. */
11979
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11980
+ /** Longest snippet a `code` source may carry (slice 4). */
11981
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11902
11982
  /** One field of one capability of one source device. */
11903
11983
  var CompositionFieldReadSchema = object({
11904
11984
  source: CompositionSourceRefSchema,
@@ -11942,7 +12022,33 @@ var CompositionCodeSourceSchema = object({
11942
12022
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11943
12023
  CompositionFromSourceSchema,
11944
12024
  CompositionExpressionSourceSchema,
11945
- CompositionCodeSourceSchema
12025
+ CompositionCodeSourceSchema,
12026
+ object({
12027
+ kind: literal("aggregate"),
12028
+ reducer: _enum([
12029
+ "mean",
12030
+ "median",
12031
+ "min",
12032
+ "max",
12033
+ "sum",
12034
+ "any",
12035
+ "all"
12036
+ ]),
12037
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
12038
+ /** `value` is the reading; the others are the member counts. */
12039
+ publish: _enum([
12040
+ "value",
12041
+ "included",
12042
+ "excluded",
12043
+ "total"
12044
+ ]),
12045
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
12046
+ empty: _enum([
12047
+ "null",
12048
+ "omit",
12049
+ "offline"
12050
+ ])
12051
+ })
11946
12052
  ]);
11947
12053
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11948
12054
  kind: literal("forward"),
@@ -11988,7 +12094,13 @@ var CompositionSchema = object({
11988
12094
  kind: literal("existing"),
11989
12095
  device: CompositionSourceRefSchema
11990
12096
  })]),
11991
- features: array(CompositionFeatureSchema).min(1).max(16)
12097
+ features: array(CompositionFeatureSchema).min(1).max(16),
12098
+ /**
12099
+ * Present when the block is a helper template. Absent on a free
12100
+ * composition (the room example, device 4603). The editor reads this,
12101
+ * never the compiled features.
12102
+ */
12103
+ helper: HelperSpecSchema.optional()
11992
12104
  }).superRefine((composition, ctx) => {
11993
12105
  const seen = /* @__PURE__ */ new Set();
11994
12106
  composition.features.forEach((feature, index) => {
@@ -18090,7 +18202,11 @@ var CompositionProblemSchema = object({
18090
18202
  "item-field-reserved",
18091
18203
  "target-missing",
18092
18204
  "target-unreadable",
18093
- "not-replaceable"
18205
+ "not-replaceable",
18206
+ "native-capability-replace",
18207
+ "mixed-quantity",
18208
+ "helper-mismatch",
18209
+ "member-constraint"
18094
18210
  ]),
18095
18211
  path: string(),
18096
18212
  message: string()
@@ -18111,7 +18227,8 @@ var CompositionUnavailableModeSchema = _enum([
18111
18227
  "null",
18112
18228
  "offline",
18113
18229
  "unknown",
18114
- "release"
18230
+ "release",
18231
+ "omit"
18115
18232
  ]);
18116
18233
  /**
18117
18234
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -18148,9 +18265,11 @@ var CompositionFieldPlanSchema = object({
18148
18265
  reads: array(CompositionFieldReadSchema)
18149
18266
  });
18150
18267
  /**
18151
- * What a feature does to its target (D663): `new` builds a capability on a new
18152
- * device; on an existing device it `add`s a capability the device lacks, or
18153
- * `replace`s named fields of one it has natively.
18268
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
18269
+ * new device; on an existing device it `add`s a capability the device lacks.
18270
+ * Replacing a capability the device already serves is refused
18271
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
18272
+ * wire for a claim the hub already holds.
18154
18273
  */
18155
18274
  var CompositionFeatureModeSchema = _enum([
18156
18275
  "new",
@@ -18323,6 +18442,275 @@ Object.freeze({
18323
18442
  result: ["number", "null"]
18324
18443
  }
18325
18444
  });
18445
+ /**
18446
+ * One catalog row per base reading a group helper can build (D944 §3).
18447
+ * Data, not a form default written into the block.
18448
+ */
18449
+ var NOW = {
18450
+ fieldPath: "lastFetchedAt",
18451
+ expr: "now"
18452
+ };
18453
+ var CHANGED = {
18454
+ fieldPath: "lastChangedAt",
18455
+ expr: "now"
18456
+ };
18457
+ function numeric(input) {
18458
+ return {
18459
+ ...input,
18460
+ family: "numeric",
18461
+ deviceType: DeviceType.Sensor,
18462
+ memberDeviceType: DeviceType.Sensor,
18463
+ commandCaps: [],
18464
+ baseUnit: baseUnitOf(input.quantity),
18465
+ aggregates: NUMERIC_AGGREGATES,
18466
+ accept: [{
18467
+ cap: input.outputCap,
18468
+ fieldPath: input.outputField
18469
+ }]
18470
+ };
18471
+ }
18472
+ function binary(input) {
18473
+ return {
18474
+ ...input,
18475
+ family: "binary",
18476
+ deviceType: DeviceType.Sensor,
18477
+ memberDeviceType: DeviceType.Sensor,
18478
+ commandCaps: [],
18479
+ quantity: "none",
18480
+ baseUnit: void 0,
18481
+ aggregates: BINARY_AGGREGATES,
18482
+ empty: "offline"
18483
+ };
18484
+ }
18485
+ var GROUP_TEMPLATES = [
18486
+ numeric({
18487
+ id: "temperature",
18488
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18489
+ role: DeviceRole.TemperatureSensor,
18490
+ outputCap: "temperature-sensor",
18491
+ outputField: "celsius",
18492
+ quantity: "temperature",
18493
+ defaultAggregate: "mean",
18494
+ empty: "null",
18495
+ companions: [NOW]
18496
+ }),
18497
+ numeric({
18498
+ id: "humidity",
18499
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18500
+ role: DeviceRole.HumiditySensor,
18501
+ outputCap: "humidity-sensor",
18502
+ outputField: "percent",
18503
+ quantity: "humidity",
18504
+ defaultAggregate: "mean",
18505
+ empty: "null",
18506
+ companions: [NOW]
18507
+ }),
18508
+ numeric({
18509
+ id: "pressure",
18510
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18511
+ role: DeviceRole.PressureSensor,
18512
+ outputCap: "pressure-sensor",
18513
+ outputField: "hpa",
18514
+ quantity: "pressure",
18515
+ defaultAggregate: "mean",
18516
+ empty: "null",
18517
+ companions: [NOW]
18518
+ }),
18519
+ numeric({
18520
+ id: "illuminance",
18521
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18522
+ role: DeviceRole.AmbientLightSensor,
18523
+ outputCap: "ambient-light-sensor",
18524
+ outputField: "lux",
18525
+ quantity: "illuminance",
18526
+ defaultAggregate: "mean",
18527
+ empty: "null",
18528
+ companions: [NOW]
18529
+ }),
18530
+ numeric({
18531
+ id: "power",
18532
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18533
+ role: DeviceRole.PowerSensor,
18534
+ outputCap: "power-meter",
18535
+ outputField: "watts",
18536
+ quantity: "power",
18537
+ defaultAggregate: "sum",
18538
+ empty: "omit",
18539
+ companions: [NOW]
18540
+ }),
18541
+ numeric({
18542
+ id: "energy",
18543
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18544
+ role: DeviceRole.EnergySensor,
18545
+ outputCap: "power-meter",
18546
+ outputField: "kwhTotal",
18547
+ quantity: "energy",
18548
+ defaultAggregate: "sum",
18549
+ empty: "omit",
18550
+ companions: [NOW]
18551
+ }),
18552
+ numeric({
18553
+ id: "battery",
18554
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18555
+ role: DeviceRole.BatterySensor,
18556
+ outputCap: "battery",
18557
+ outputField: "percentage",
18558
+ quantity: "percentage",
18559
+ defaultAggregate: "mean",
18560
+ empty: "null",
18561
+ companions: [
18562
+ {
18563
+ fieldPath: "charging",
18564
+ expr: "\"unknown\""
18565
+ },
18566
+ {
18567
+ fieldPath: "sleeping",
18568
+ expr: "false"
18569
+ },
18570
+ {
18571
+ fieldPath: "lastUpdated",
18572
+ expr: "now"
18573
+ }
18574
+ ]
18575
+ }),
18576
+ numeric({
18577
+ id: "numeric",
18578
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18579
+ role: DeviceRole.NumericSensor,
18580
+ outputCap: "numeric-sensor",
18581
+ outputField: "value",
18582
+ quantity: "device",
18583
+ defaultAggregate: "mean",
18584
+ empty: "null",
18585
+ companions: [NOW]
18586
+ }),
18587
+ binary({
18588
+ id: "motion",
18589
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18590
+ role: DeviceRole.MotionSensor,
18591
+ outputCap: "motion",
18592
+ outputField: "detected",
18593
+ defaultAggregate: "any",
18594
+ accept: [{
18595
+ cap: "motion",
18596
+ fieldPath: "detected"
18597
+ }],
18598
+ companions: [{
18599
+ fieldPath: "lastDetectedAt",
18600
+ expr: "null"
18601
+ }, {
18602
+ fieldPath: "autoClearAfterMs",
18603
+ expr: "null"
18604
+ }]
18605
+ }),
18606
+ binary({
18607
+ id: "contact",
18608
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18609
+ role: DeviceRole.ContactSensor,
18610
+ outputCap: "contact",
18611
+ outputField: "entryOpen",
18612
+ defaultAggregate: "any",
18613
+ accept: [{
18614
+ cap: "contact",
18615
+ fieldPath: "entryOpen"
18616
+ }],
18617
+ companions: [CHANGED]
18618
+ }),
18619
+ binary({
18620
+ id: "leak",
18621
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18622
+ role: DeviceRole.LeakSensor,
18623
+ outputCap: "flood",
18624
+ outputField: "flooded",
18625
+ defaultAggregate: "any",
18626
+ accept: [{
18627
+ cap: "flood",
18628
+ fieldPath: "flooded"
18629
+ }],
18630
+ companions: [CHANGED]
18631
+ }),
18632
+ binary({
18633
+ id: "smoke",
18634
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18635
+ role: DeviceRole.SmokeSensor,
18636
+ outputCap: "smoke",
18637
+ outputField: "detected",
18638
+ defaultAggregate: "any",
18639
+ accept: [{
18640
+ cap: "smoke",
18641
+ fieldPath: "detected"
18642
+ }],
18643
+ companions: [CHANGED]
18644
+ }),
18645
+ binary({
18646
+ id: "occupancy",
18647
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18648
+ role: DeviceRole.BinarySensor,
18649
+ outputCap: "binary",
18650
+ outputField: "on",
18651
+ defaultAggregate: "any",
18652
+ accept: [{
18653
+ cap: "motion",
18654
+ fieldPath: "detected"
18655
+ }, {
18656
+ cap: "binary",
18657
+ fieldPath: "on"
18658
+ }],
18659
+ companions: [CHANGED]
18660
+ }),
18661
+ binary({
18662
+ id: "binary",
18663
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18664
+ role: DeviceRole.BinarySensor,
18665
+ outputCap: "binary",
18666
+ outputField: "on",
18667
+ defaultAggregate: "any",
18668
+ accept: [{
18669
+ cap: "binary",
18670
+ fieldPath: "on"
18671
+ }],
18672
+ companions: [CHANGED]
18673
+ }),
18674
+ actuator({
18675
+ id: "switch",
18676
+ 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.",
18677
+ deviceType: DeviceType.Switch,
18678
+ outputCap: "switch",
18679
+ commandCaps: ["switch"]
18680
+ }),
18681
+ actuator({
18682
+ id: "light",
18683
+ 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.",
18684
+ deviceType: DeviceType.Light,
18685
+ outputCap: "switch",
18686
+ commandCaps: [
18687
+ "switch",
18688
+ "brightness",
18689
+ "color"
18690
+ ]
18691
+ })
18692
+ ];
18693
+ function actuator(input) {
18694
+ return {
18695
+ ...input,
18696
+ family: "actuator",
18697
+ role: void 0,
18698
+ memberDeviceType: input.deviceType,
18699
+ outputField: "on",
18700
+ quantity: "none",
18701
+ baseUnit: void 0,
18702
+ aggregates: BINARY_AGGREGATES,
18703
+ defaultAggregate: "any",
18704
+ empty: "offline",
18705
+ accept: [{
18706
+ cap: "switch",
18707
+ fieldPath: "on"
18708
+ }],
18709
+ companions: [CHANGED]
18710
+ };
18711
+ }
18712
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18713
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18326
18714
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18327
18715
  /**
18328
18716
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -20036,12 +20424,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
20036
20424
  * providers detect renames by matching `uniqueId` and patch `id`
20037
20425
  * in-place so dispatch keeps working.
20038
20426
  *
20039
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
20040
- * `precision`) live in the cap STATUS SLICE so they flow live with every
20041
- * state push and are the single source of truth. The `device_class` tag is
20042
- * an HA-specific input that is consumed at the discovery boundary
20043
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
20044
- * stored after that point.
20427
+ * **Display unit and precision are NOT stored here.** They are a read-time
20428
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20429
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20430
+ * neither is repeated on each sample. The `device_class` tag is an
20431
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20432
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
20045
20433
  *
20046
20434
  * The `raw` escape hatch carries provider-specific attributes that don't
20047
20435
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -20055,6 +20443,14 @@ var SourceInfoSchema = object({
20055
20443
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
20056
20444
  * Used to detect rename when `id` changes. */
20057
20445
  uniqueId: string().optional(),
20446
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20447
+ quantity: ReadingQuantitySchema.optional(),
20448
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20449
+ dateTimeShape: _enum([
20450
+ "date",
20451
+ "time",
20452
+ "datetime"
20453
+ ]).optional(),
20058
20454
  /** Provider-specific extras that don't fit the structured slots.
20059
20455
  * Whitelist what goes here per provider — do NOT dump entire HA
20060
20456
  * attribute blobs (would explode the meta JSON column). */
@@ -40967,7 +41363,8 @@ method(object({
40967
41363
  "enum"
40968
41364
  ]),
40969
41365
  enumValues: array(string()).optional(),
40970
- item: boolean().optional()
41366
+ item: boolean().optional(),
41367
+ quantity: ReadingQuantitySchema.optional()
40971
41368
  })).readonly(),
40972
41369
  itemArray: object({
40973
41370
  path: string(),
@@ -44767,8 +45164,8 @@ var MaskPolygonShapeSchema = object({
44767
45164
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44768
45165
  var MaskGridShapeSchema = object({
44769
45166
  kind: literal("grid"),
44770
- gridWidth: number(),
44771
- gridHeight: number(),
45167
+ gridWidth: number().meta(readingMeta("none")),
45168
+ gridHeight: number().meta(readingMeta("none")),
44772
45169
  cells: array(boolean())
44773
45170
  });
44774
45171
  discriminatedUnion("kind", [
@@ -44880,6 +45277,8 @@ var NcSystemEventKindSchema = _enum([
44880
45277
  "detection-blind",
44881
45278
  "motion-noisy",
44882
45279
  "motion-calm",
45280
+ "package-llm-silent",
45281
+ "package-llm-answered",
44883
45282
  "recording-stalled",
44884
45283
  "recording-healthy",
44885
45284
  "recording-degraded",
@@ -55894,6 +56293,28 @@ var PackageVersionInfoSchema = object({
55894
56293
  deprecated: string().optional(),
55895
56294
  distTags: array(string()).readonly()
55896
56295
  });
56296
+ /**
56297
+ * One published document. `ok` is markdown from the registry. `missing` means
56298
+ * the registry answered and the file was not there. `error` is a registry or
56299
+ * tarball failure — never an empty string standing in for either.
56300
+ */
56301
+ var PackageDocBodySchema = discriminatedUnion("status", [
56302
+ object({
56303
+ status: literal("ok"),
56304
+ markdown: string()
56305
+ }),
56306
+ object({ status: literal("missing") }),
56307
+ object({
56308
+ status: literal("error"),
56309
+ message: string()
56310
+ })
56311
+ ]);
56312
+ var PackageDocsSchema = object({
56313
+ name: string(),
56314
+ version: string(),
56315
+ readme: PackageDocBodySchema,
56316
+ changelog: PackageDocBodySchema
56317
+ });
55897
56318
  var SearchResultSchema = object({
55898
56319
  name: string(),
55899
56320
  version: string(),
@@ -56112,7 +56533,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56112
56533
  }), object({ success: literal(true) }), {
56113
56534
  kind: "mutation",
56114
56535
  auth: "admin"
56115
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
56536
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
56537
+ name: string().min(1),
56538
+ version: string().min(1).optional()
56539
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
56116
56540
  kind: "mutation",
56117
56541
  auth: "admin"
56118
56542
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -56162,46 +56586,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56162
56586
  }), LogStreamEntrySchema, { kind: "subscription" });
56163
56587
  object({
56164
56588
  /** Carbon dioxide concentration in ppm. */
56165
- co2Ppm: number().min(0).optional(),
56589
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
56166
56590
  /** Total volatile organic compounds in ppb. */
56167
- vocPpb: number().min(0).optional(),
56591
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56168
56592
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56169
- pm25: number().min(0).optional(),
56593
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56170
56594
  /** Particulate matter ≤ 10 μm in µg/m³. */
56171
- pm10: number().min(0).optional(),
56595
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
56172
56596
  /** Composite AQI value (typically 0..500). */
56173
- aqi: number().optional(),
56597
+ aqi: number().optional().meta(readingMeta("index")),
56174
56598
  /** Ms epoch when the slice was last updated. */
56175
- lastFetchedAt: number(),
56176
- /** Live display unit of the single metric this slice carries (e.g. HA
56177
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
56178
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
56179
- * per slice is unambiguous. */
56180
- unit: string().optional(),
56181
- /** Suggested decimal places for numeric display.
56182
- * Populated live from the upstream source when provided (e.g. HA
56183
- * `attributes.suggested_display_precision`). Falls back to
56184
- * auto-formatting when absent. */
56185
- precision: number().int().min(0).max(10).optional()
56599
+ lastFetchedAt: number()
56186
56600
  });
56187
56601
  DeviceType.Sensor;
56188
56602
  object({
56189
- /** Current illuminance in lux (lx). */
56190
- lux: number().min(0).meta({
56191
- unit: "lx",
56603
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56604
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
56605
+ precision: 0,
56192
56606
  label: "Illuminance"
56193
- }),
56607
+ })),
56194
56608
  /** Ms epoch when the slice was last updated. */
56195
- lastFetchedAt: number(),
56196
- /** Live display unit from the upstream source (e.g. HA
56197
- * `attributes.unit_of_measurement`). The UI prefers this over the
56198
- * role's canonical unit. Absent → fall back to the canonical unit. */
56199
- unit: string().optional(),
56200
- /** Suggested decimal places for numeric display.
56201
- * Populated live from the upstream source when provided (e.g. HA
56202
- * `attributes.suggested_display_precision`). Falls back to
56203
- * auto-formatting when absent. */
56204
- precision: number().int().min(0).max(10).optional()
56609
+ lastFetchedAt: number()
56205
56610
  });
56206
56611
  DeviceType.Sensor;
56207
56612
  /**
@@ -56346,10 +56751,7 @@ var BatteryStatusSchema = object({
56346
56751
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
56347
56752
  * declines to band a non-finite reading, which is the correct shape.
56348
56753
  */
56349
- percentage: number().min(0).max(100).nullable().meta({
56350
- unit: "%",
56351
- label: "Charge"
56352
- }),
56754
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
56353
56755
  /**
56354
56756
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
56355
56757
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -56376,7 +56778,14 @@ var BatteryStatusSchema = object({
56376
56778
  */
56377
56779
  sleeping: boolean().meta({ label: "Sleeping" }),
56378
56780
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
56379
- lastUpdated: number(),
56781
+ lastUpdated: number().meta(readingMeta("none")),
56782
+ /**
56783
+ * Battery voltage in volts, for a cell the firmware reports as voltage
56784
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
56785
+ * that only report charge. A second reading on this cap, not a second
56786
+ * `numeric-sensor` on the same device.
56787
+ */
56788
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
56380
56789
  /**
56381
56790
  * Ms epoch of the last time the device PROVED it was reachable — a
56382
56791
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -56436,7 +56845,7 @@ object({
56436
56845
  DeviceType.Sensor;
56437
56846
  object({
56438
56847
  /** Current level as 0..100 inclusive. Firmware-reported. */
56439
- percentage: number().min(0).max(100),
56848
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
56440
56849
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
56441
56850
  lastChangedAt: number()
56442
56851
  });
@@ -56831,21 +57240,21 @@ object({
56831
57240
  /** Single setpoint in Celsius. Used by single-target modes
56832
57241
  * (heat / cool / dry). Null when the device is in a range mode
56833
57242
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
56834
- target: number().nullable(),
57243
+ target: number().nullable().meta(readingMeta("temperature")),
56835
57244
  /** Upper bound of the dual setpoint in Celsius. Populated only
56836
57245
  * in `heat_cool` mode. */
56837
- targetHigh: number().nullable(),
57246
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
56838
57247
  /** Lower bound of the dual setpoint in Celsius. Populated only
56839
57248
  * in `heat_cool` mode. */
56840
- targetLow: number().nullable(),
57249
+ targetLow: number().nullable().meta(readingMeta("temperature")),
56841
57250
  /** Target relative humidity (0..100). Null when the device has no
56842
57251
  * humidity control. */
56843
- targetHumidity: number().min(0).max(100).nullable(),
57252
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56844
57253
  /** Current measured temperature in Celsius. Read-only — pushed by
56845
57254
  * the device's internal sensor. */
56846
- currentTemp: number().nullable(),
57255
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
56847
57256
  /** Current measured relative humidity (0..100). Read-only. */
56848
- currentHumidity: number().min(0).max(100).nullable(),
57257
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56849
57258
  /** Vertical louver swing on/off. Null when the device has no vertical
56850
57259
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
56851
57260
  swingVertical: boolean().nullable(),
@@ -56853,11 +57262,11 @@ object({
56853
57262
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
56854
57263
  swingHorizontal: boolean().nullable(),
56855
57264
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
56856
- minTemp: number().nullable().optional(),
57265
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
56857
57266
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
56858
- maxTemp: number().nullable().optional(),
57267
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
56859
57268
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
56860
- tempStep: number().positive().nullable().optional(),
57269
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
56861
57270
  /** Ms epoch when the slice was last updated (push or command). */
56862
57271
  lastFetchedAt: number()
56863
57272
  });
@@ -56936,17 +57345,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
56936
57345
  * modes to expose in the picker without a slice fetch.
56937
57346
  */
56938
57347
  var RgbTripletSchema = object({
56939
- r: number().int().min(0).max(255),
56940
- g: number().int().min(0).max(255),
56941
- b: number().int().min(0).max(255)
57348
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
57349
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
57350
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
56942
57351
  });
56943
57352
  var HsvTripletSchema = object({
56944
57353
  /** Hue in degrees, 0..360. */
56945
- h: number().min(0).max(360),
57354
+ h: number().min(0).max(360).meta(readingMeta("none")),
56946
57355
  /** Saturation as 0..100 inclusive. */
56947
- s: number().min(0).max(100),
57356
+ s: number().min(0).max(100).meta(readingMeta("none")),
56948
57357
  /** Value/brightness as 0..100 inclusive. */
56949
- v: number().min(0).max(100)
57358
+ v: number().min(0).max(100).meta(readingMeta("none"))
56950
57359
  });
56951
57360
  var ColorInputSchema = discriminatedUnion("mode", [
56952
57361
  object({
@@ -56975,7 +57384,7 @@ object({
56975
57384
  /** Populated when `mode === 'hsv'`. */
56976
57385
  hsv: HsvTripletSchema.optional(),
56977
57386
  /** Populated when `mode === 'mired'`. */
56978
- mireds: number().int().optional(),
57387
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
56979
57388
  /** Ms epoch of the last operator-driven change. */
56980
57389
  lastChangedAt: number()
56981
57390
  });
@@ -57099,18 +57508,12 @@ _enum([
57099
57508
  "empty",
57100
57509
  "full"
57101
57510
  ]);
57102
- /** Units a remaining-life counter is reported in. Closed: the panel renders
57103
- * each. */
57104
- var ConsumableRemainingUnitSchema = _enum([
57105
- "days",
57106
- "hours",
57107
- "cycles"
57108
- ]);
57109
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
57110
- * days). `value` null = unknown (D393). */
57511
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
57512
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
57513
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
57111
57514
  var ConsumableRemainingSchema = object({
57112
- value: number().min(0).nullable(),
57113
- unit: ConsumableRemainingUnitSchema
57515
+ value: number().min(0).nullable().meta(readingMeta("device")),
57516
+ kind: ConsumableRemainingKindSchema
57114
57517
  });
57115
57518
  var ConsumablesStatusSchema = object({
57116
57519
  items: array(object({
@@ -57119,7 +57522,7 @@ var ConsumablesStatusSchema = object({
57119
57522
  /** Display name. */
57120
57523
  label: string().min(1),
57121
57524
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
57122
- level: number().min(0).max(100).nullable(),
57525
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57123
57526
  /** The provider's discrete verdict when known (binary mode). */
57124
57527
  status: ConsumableStatusSchema.nullable(),
57125
57528
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -57136,7 +57539,7 @@ var ConsumablesStatusSchema = object({
57136
57539
  })),
57137
57540
  lastChangedAt: number()
57138
57541
  });
57139
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57542
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57140
57543
  Object.values(DeviceType), method(object({
57141
57544
  deviceId: number().int().nonnegative(),
57142
57545
  key: string().min(1)
@@ -57161,39 +57564,17 @@ object({
57161
57564
  /** Current value. Numeric kind → number; select/text/datetime → string.
57162
57565
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
57163
57566
  * or time-only — HA accepts all three forms). */
57164
- value: union([number(), string()]),
57567
+ value: union([number().meta(readingMeta("device")), string()]),
57165
57568
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
57166
57569
  options: array(string()),
57167
57570
  /** Ms epoch when the slice was last updated. */
57168
57571
  lastChangedAt: number(),
57169
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
57170
- * Populated live from HA `attributes.unit_of_measurement` on each push.
57171
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57172
- unit: string().optional(),
57173
- /** Minimum allowed value. Populated live from HA `attributes.min`.
57174
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57175
- min: number().optional(),
57176
- /** Maximum allowed value. Populated live from HA `attributes.max`.
57177
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57178
- max: number().optional(),
57179
- /** Step increment. Populated live from HA `attributes.step`.
57180
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57181
- step: number().optional(),
57182
- /** Suggested decimal places for numeric display. Populated live from HA
57183
- * `attributes.suggested_display_precision`, or derived from `step` when the
57184
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
57185
- * other kinds. Falls back to auto-formatting when absent. */
57186
- precision: number().int().min(0).max(10).optional(),
57187
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
57188
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
57189
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
57190
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
57191
- * `has_date`/`has_time` attributes on each push. */
57192
- format: _enum([
57193
- "date",
57194
- "time",
57195
- "datetime"
57196
- ]).optional()
57572
+ /** Minimum allowed value, in the device quantity's base unit. */
57573
+ min: number().optional().meta(readingMeta("device")),
57574
+ /** Maximum allowed value, in the device quantity's base unit. */
57575
+ max: number().optional().meta(readingMeta("device")),
57576
+ /** Step increment, in the device quantity's base unit. */
57577
+ step: number().optional().meta(readingMeta("device"))
57197
57578
  });
57198
57579
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
57199
57580
  object({
@@ -57231,9 +57612,9 @@ object({
57231
57612
  ]),
57232
57613
  /** 0 = fully closed, 100 = fully open. Null when the device has
57233
57614
  * no intermediate position surface. */
57234
- position: number().min(0).max(100).nullable(),
57615
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57235
57616
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
57236
- tiltPosition: number().min(0).max(100).nullable(),
57617
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57237
57618
  /** Ms epoch when the slice was last updated. */
57238
57619
  lastChangedAt: number()
57239
57620
  });
@@ -57291,9 +57672,9 @@ var NormalizedRangeSchema$1 = object({
57291
57672
  object({
57292
57673
  mode: DayNightModeSchema,
57293
57674
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
57294
- sensitivity: number().optional(),
57675
+ sensitivity: number().optional().meta(readingMeta("none")),
57295
57676
  /** Delay before the IR-cut filter flips, in seconds. */
57296
- switchDelaySec: number().optional(),
57677
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
57297
57678
  lastFetchedAt: number()
57298
57679
  });
57299
57680
  /**
@@ -57448,11 +57829,11 @@ object({
57448
57829
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
57449
57830
  availablePrograms: array(string()),
57450
57831
  /** Seconds remaining on the active cycle, or null when not running / not known. */
57451
- remainingSeconds: number().int().nonnegative().nullable(),
57832
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57452
57833
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
57453
- delayedStartSeconds: number().int().nonnegative().nullable(),
57834
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57454
57835
  /** Cycle completion 0..100, or null when not applicable. */
57455
- progressPercent: number().min(0).max(100).nullable(),
57836
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57456
57837
  /** True when the door is reported open; null when the door state is not known. */
57457
57838
  doorOpen: boolean().nullable(),
57458
57839
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -57476,7 +57857,7 @@ object({
57476
57857
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
57477
57858
  machineCareDue: boolean().nullable(),
57478
57859
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
57479
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
57860
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
57480
57861
  /** Option states: null when the appliance does not expose the option or has not said. */
57481
57862
  halfLoad: boolean().nullable(),
57482
57863
  hygienePlus: boolean().nullable(),
@@ -57491,7 +57872,7 @@ object({
57491
57872
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
57492
57873
  activeEvents: array(string()),
57493
57874
  /** Numeric error code when the appliance reports one; null otherwise. */
57494
- errorCode: number().nullable(),
57875
+ errorCode: number().nullable().meta(readingMeta("none")),
57495
57876
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
57496
57877
  errorLabel: string().nullable(),
57497
57878
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -57637,24 +58018,14 @@ object({
57637
58018
  /** Ms epoch of the last press. null = never observed since this provider started. */
57638
58019
  lastPressedAt: number().nullable(),
57639
58020
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
57640
- pressCountSinceStart: number()
58021
+ pressCountSinceStart: number().meta(readingMeta("count"))
57641
58022
  });
57642
58023
  object({
57643
58024
  deviceId: number(),
57644
58025
  timestamp: number()
57645
58026
  });
57646
58027
  DeviceType.Button, DeviceType.Camera;
57647
- /**
57648
- * Enum-state sensor — a string value picked from a finite option set.
57649
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
57650
- * states, weather conditions, contact-source channels, mode strings).
57651
- * The option set is stable across observations; it is persisted in the
57652
- * device config blob (via the control slice at adoption) and is NOT
57653
- * stored in `sourceInfo`.
57654
- */
57655
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
57656
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
57657
- var EnumSensorDateTimeFormatSchema = _enum([
58028
+ _enum([
57658
58029
  "date",
57659
58030
  "time",
57660
58031
  "datetime"
@@ -57662,12 +58033,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
57662
58033
  object({
57663
58034
  value: string(),
57664
58035
  /**
57665
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
57666
- * as a locale date/time/datetime; absent for plain enum sensors. HA
57667
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
57668
- * a time-only sensor → `'time'`.
58036
+ * Set when `value` is a timestamp rather than an enum state, so history
58037
+ * does not learn ISO strings as states. The display shape (date / time /
58038
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
57669
58039
  */
57670
- format: EnumSensorDateTimeFormatSchema.optional(),
58040
+ timestamp: boolean().optional(),
57671
58041
  /** Ms epoch when the slice was last updated. */
57672
58042
  lastFetchedAt: number()
57673
58043
  });
@@ -57686,13 +58056,13 @@ var EventFireSchema = object({
57686
58056
  deviceId: number(),
57687
58057
  eventType: string(),
57688
58058
  data: record(string(), unknown()).nullable(),
57689
- timestamp: number(),
57690
- seq: number()
58059
+ timestamp: number().meta(readingMeta("none")),
58060
+ seq: number().meta(readingMeta("count"))
57691
58061
  });
57692
58062
  object({
57693
58063
  eventTypes: array(string()),
57694
58064
  lastEvent: EventFireSchema.nullable(),
57695
- eventCountSinceStart: number()
58065
+ eventCountSinceStart: number().meta(readingMeta("count"))
57696
58066
  });
57697
58067
  DeviceType.EventEmitter;
57698
58068
  var IdentitySchema = object({
@@ -58081,10 +58451,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
58081
58451
  object({
58082
58452
  /** Active speed as 0..100 inclusive. Null when the device has no
58083
58453
  * speed surface (single-speed fan). */
58084
- percentage: number().min(0).max(100).nullable(),
58454
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58085
58455
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
58086
58456
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
58087
- percentageStep: number().positive().optional(),
58457
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
58088
58458
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
58089
58459
  * — empty string when no preset is active or supported. */
58090
58460
  preset: string(),
@@ -58166,7 +58536,7 @@ var FeatureProbeStatusSchema = object({
58166
58536
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
58167
58537
  model: string().nullable(),
58168
58538
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
58169
- channelCount: number().nullable(),
58539
+ channelCount: number().nullable().meta(readingMeta("count")),
58170
58540
  /**
58171
58541
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
58172
58542
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -58198,9 +58568,39 @@ object({
58198
58568
  lastChangedAt: number()
58199
58569
  });
58200
58570
  DeviceType.Sensor;
58571
+ object({
58572
+ /** One row per member that has been commanded. A later command replaces that member's row. */
58573
+ results: array(object({
58574
+ deviceId: number().int().nonnegative(),
58575
+ cap: string().min(1),
58576
+ method: string().min(1),
58577
+ ok: boolean(),
58578
+ error: string().optional(),
58579
+ /** Ms epoch of the attempt. */
58580
+ at: number()
58581
+ })) });
58582
+ DeviceType.Switch, DeviceType.Light;
58583
+ object({
58584
+ /** Members whose current value entered the aggregate. */
58585
+ included: number().int().nonnegative().meta(readingMeta("count", {
58586
+ precision: 0,
58587
+ label: "Reporting"
58588
+ })),
58589
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
58590
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
58591
+ precision: 0,
58592
+ label: "Excluded"
58593
+ })),
58594
+ /** `included` + `excluded`. */
58595
+ total: number().int().nonnegative().meta(readingMeta("count", {
58596
+ precision: 0,
58597
+ label: "Members"
58598
+ }))
58599
+ });
58600
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
58201
58601
  object({
58202
58602
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
58203
- position: number().min(0).max(100).nullable(),
58603
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58204
58604
  /** Ms epoch when the slice was last updated (push or poll). */
58205
58605
  lastFetchedAt: number()
58206
58606
  });
@@ -58209,9 +58609,9 @@ object({
58209
58609
  /** Whether the humidifier is currently on. */
58210
58610
  on: boolean(),
58211
58611
  /** Current measured relative humidity (0..100). Null when not reported. */
58212
- currentHumidity: number().min(0).max(100).nullable(),
58612
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58213
58613
  /** Target relative humidity (0..100). Null when no setpoint surface. */
58214
- targetHumidity: number().min(0).max(100).nullable(),
58614
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58215
58615
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
58216
58616
  * device has no mode surface. */
58217
58617
  mode: string().nullable(),
@@ -58221,9 +58621,9 @@ object({
58221
58621
  * `idle` / `off`). Null when the device doesn't report it. */
58222
58622
  action: string().nullable(),
58223
58623
  /** HA `min_humidity` attribute. Null → UI uses 0. */
58224
- minHumidity: number().nullable(),
58624
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
58225
58625
  /** HA `max_humidity` attribute. Null → UI uses 100. */
58226
- maxHumidity: number().nullable(),
58626
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
58227
58627
  /** Ms epoch when the slice was last updated. */
58228
58628
  lastChangedAt: number()
58229
58629
  });
@@ -58247,22 +58647,13 @@ DeviceType.Humidifier, method(object({
58247
58647
  auth: "protected"
58248
58648
  });
58249
58649
  object({
58250
- /** Current relative humidity, 0..100. */
58251
- percent: number().min(0).max(100).meta({
58252
- unit: "%",
58650
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
58651
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
58652
+ precision: 0,
58253
58653
  label: "Humidity"
58254
- }),
58654
+ })),
58255
58655
  /** Ms epoch when the slice was last updated. */
58256
- lastFetchedAt: number(),
58257
- /** Live display unit from the upstream source (e.g. HA
58258
- * `attributes.unit_of_measurement`). The UI prefers this over the
58259
- * role's canonical unit. Absent → fall back to the canonical unit. */
58260
- unit: string().optional(),
58261
- /** Suggested decimal places for numeric display.
58262
- * Populated live from the upstream source when provided (e.g. HA
58263
- * `attributes.suggested_display_precision`). Falls back to
58264
- * auto-formatting when absent. */
58265
- precision: number().int().min(0).max(10).optional()
58656
+ lastFetchedAt: number()
58266
58657
  });
58267
58658
  DeviceType.Sensor, DeviceType.Thermostat;
58268
58659
  object({
@@ -58270,7 +58661,7 @@ object({
58270
58661
  * entity exposes no `entity_picture` (yet). */
58271
58662
  url: string().nullable(),
58272
58663
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
58273
- lastUpdated: number().nullable()
58664
+ lastUpdated: number().nullable().meta(readingMeta("none"))
58274
58665
  });
58275
58666
  DeviceType.Image;
58276
58667
  /**
@@ -58326,19 +58717,19 @@ var NormalizedRangeSchema = object({
58326
58717
  });
58327
58718
  object({
58328
58719
  /** Normalized 0–100. */
58329
- brightness: number().optional(),
58720
+ brightness: number().optional().meta(readingMeta("none")),
58330
58721
  /** Normalized 0–100. */
58331
- contrast: number().optional(),
58722
+ contrast: number().optional().meta(readingMeta("none")),
58332
58723
  /** Normalized 0–100. */
58333
- saturation: number().optional(),
58724
+ saturation: number().optional().meta(readingMeta("none")),
58334
58725
  /** Normalized 0–100. */
58335
- sharpness: number().optional(),
58726
+ sharpness: number().optional().meta(readingMeta("none")),
58336
58727
  mirror: boolean().optional(),
58337
58728
  flip: boolean().optional(),
58338
58729
  rotate: ImageRotateSchema.optional(),
58339
58730
  whiteBalance: WhiteBalanceModeSchema.optional(),
58340
58731
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
58341
- warmth: number().optional(),
58732
+ warmth: number().optional().meta(readingMeta("none")),
58342
58733
  exposureMode: ExposureModeSchema.optional(),
58343
58734
  backlightMode: BacklightModeSchema.optional(),
58344
58735
  lastFetchedAt: number()
@@ -58583,10 +58974,10 @@ var IntercomAbilitySchema = object({
58583
58974
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
58584
58975
  codecs: array(string()),
58585
58976
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
58586
- sampleRate: number(),
58977
+ sampleRate: number().meta(readingMeta("none")),
58587
58978
  duplex: _enum(["half", "full"]),
58588
58979
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
58589
- maxBacklogMs: number()
58980
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
58590
58981
  });
58591
58982
  var IntercomStatusSchema = object({
58592
58983
  /** True while a talk session is open. */
@@ -58697,13 +59088,13 @@ object({
58697
59088
  activity: LawnMowerActivitySchema,
58698
59089
  /** 0..100 battery percentage. Null when the device has no battery
58699
59090
  * reading. */
58700
- batteryLevel: number().min(0).max(100).nullable(),
59091
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58701
59092
  /** 0..100 mowing-completion percentage of the current task, or null when no
58702
59093
  * task is active / progress is unavailable. */
58703
- progressPercent: number().min(0).max(100).nullable(),
59094
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58704
59095
  /** Current device/event code (dynamic — mostly status, sometimes an error),
58705
59096
  * or null when unknown. */
58706
- currentCode: number().nullable(),
59097
+ currentCode: number().nullable().meta(readingMeta("none")),
58707
59098
  /** Human label for {@link currentCode}, or null when undecodable. */
58708
59099
  currentCodeLabel: string().nullable(),
58709
59100
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -59165,7 +59556,7 @@ object({
59165
59556
  state: MediaPlayerStateSchema,
59166
59557
  /** Volume as 0..100 inclusive. Null when the device has no volume
59167
59558
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59168
- volumeLevel: number().min(0).max(100).nullable(),
59559
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59169
59560
  /** Mute toggle distinct from volume=0. Null when no mute surface.
59170
59561
  * Pair with `DeviceFeature.MediaPlayerMute`. */
59171
59562
  isMuted: boolean().nullable(),
@@ -59178,10 +59569,10 @@ object({
59178
59569
  currentMedia: MediaInfoSchema.nullable(),
59179
59570
  /** Current playback position in ms. Null when not seekable or
59180
59571
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59181
- positionMs: number().int().nonnegative().nullable(),
59572
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59182
59573
  /** Total duration of the current media in ms. Null when unknown
59183
59574
  * (live stream) or nothing is playing. */
59184
- durationMs: number().int().nonnegative().nullable(),
59575
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59185
59576
  /** Shuffle toggle. Null when no shuffle surface.
59186
59577
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59187
59578
  shuffle: boolean().nullable(),
@@ -59481,7 +59872,7 @@ object({
59481
59872
  * it, every provider seeded it at boot, and every session left it
59482
59873
  * `detected: true` after its detach.
59483
59874
  */
59484
- autoClearAfterMs: number().nullable()
59875
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
59485
59876
  });
59486
59877
  /**
59487
59878
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -59531,7 +59922,7 @@ var MotionZoneRegionSchema = object({
59531
59922
  });
59532
59923
  object({
59533
59924
  enabled: boolean(),
59534
- sensitivity: number(),
59925
+ sensitivity: number().meta(readingMeta("none")),
59535
59926
  /** Grid region(s). Today exactly one `grid` shape. */
59536
59927
  regions: array(MotionZoneRegionSchema),
59537
59928
  lastFetchedAt: number()
@@ -59835,7 +60226,7 @@ var NavigationStatusSchema = object({
59835
60226
  following: boolean(),
59836
60227
  flash: boolean(),
59837
60228
  lightMode: NavigationLightModeSchema,
59838
- lightLevel: number().min(40).max(100),
60229
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
59839
60230
  /** Ms epoch when the slice was last updated. */
59840
60231
  lastChangedAt: number()
59841
60232
  });
@@ -59881,13 +60272,13 @@ var NetworkLinkStatusSchema = object({
59881
60272
  * one whose reading has not landed must not be drawn at 0 %. Consumers
59882
60273
  * SKIP a null rather than coerce it.
59883
60274
  */
59884
- signalPercent: number().min(0).max(100).nullable(),
60275
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59885
60276
  /** Raw received signal strength in dBm, when the firmware reports one. */
59886
- rssiDbm: number().optional(),
60277
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
59887
60278
  /** Network name of a wireless link, when the firmware reports it. */
59888
60279
  ssid: string().optional(),
59889
60280
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
59890
- lastUpdated: number()
60281
+ lastUpdated: number().meta(readingMeta("none"))
59891
60282
  });
59892
60283
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
59893
60284
  deviceId: number(),
@@ -60206,7 +60597,7 @@ object({
60206
60597
  lastError: string().nullable(),
60207
60598
  /** Number of deliveries currently buffered server-side (rate-limited
60208
60599
  * notifiers may queue). 0 when the provider sends synchronously. */
60209
- queueDepth: number().int().nonnegative(),
60600
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
60210
60601
  /** Per-feature capability matrix — authoritative source for the
60211
60602
  * compose-form field gating. */
60212
60603
  supports: NotifierSupportsSchema
@@ -60256,15 +60647,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
60256
60647
  acceptedAt: number()
60257
60648
  });
60258
60649
  object({
60259
- value: number(),
60260
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
60261
- * Populated live from the upstream source on each state push.
60262
- * Absent for unitless sensors. */
60263
- unit: string().optional(),
60264
- /** Suggested decimal places for numeric display.
60265
- * Populated live from the upstream source when provided.
60266
- * Falls back to auto-formatting when absent. */
60267
- precision: number().int().min(0).max(10).optional(),
60650
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
60651
+ value: number().nullable().meta(readingMeta("device")),
60268
60652
  /** Ms epoch when the slice was last updated. */
60269
60653
  lastFetchedAt: number()
60270
60654
  });
@@ -60554,23 +60938,26 @@ object({
60554
60938
  /** Food currently in the bowl (grams). Null when the device has not
60555
60939
  * reported a reading yet. On dual-hopper models this is the combined
60556
60940
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
60557
- foodLevel: number().nullable(),
60941
+ foodLevel: number().nullable().meta(readingMeta("none")),
60558
60942
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
60559
60943
  * single-hopper models. */
60560
- food1: number().nullable(),
60944
+ food1: number().nullable().meta(readingMeta("none")),
60561
60945
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
60562
60946
  * single-hopper models. */
60563
- food2: number().nullable(),
60947
+ food2: number().nullable().meta(readingMeta("none")),
60564
60948
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
60565
60949
  * (`device_class: problem`, on = low). True when the bowl is empty /
60566
60950
  * below the feeder's low threshold. */
60567
60951
  lowFood: boolean(),
60568
60952
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
60569
60953
  * device has no battery reading. */
60570
- batteryPower: number().min(0).max(100).nullable(),
60571
- /** Days of desiccant life remaining. Null when the model has no
60572
- * desiccant sensor. */
60573
- desiccantLeftDays: number().nullable(),
60954
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60955
+ /**
60956
+ * Desiccant life remaining, stored in seconds (duration base, D944).
60957
+ * Providers convert from the days the feeder reports. Null when the
60958
+ * model has no desiccant sensor.
60959
+ */
60960
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
60574
60961
  /** True while a feed is in progress. */
60575
60962
  feeding: boolean(),
60576
60963
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -60581,7 +60968,7 @@ object({
60581
60968
  * with `errorCode` for consumers that want the raw integer. */
60582
60969
  error: string().nullable(),
60583
60970
  /** Raw device error code (0 / null = no error). */
60584
- errorCode: number().nullable(),
60971
+ errorCode: number().nullable().meta(readingMeta("none")),
60585
60972
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
60586
60973
  isDualHopper: boolean(),
60587
60974
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -60591,7 +60978,7 @@ object({
60591
60978
  /** Play a chime when dispensing. */
60592
60979
  feedSound: boolean(),
60593
60980
  /** Speaker / prompt volume level (device-scaled integer). */
60594
- volume: number(),
60981
+ volume: number().meta(readingMeta("none")),
60595
60982
  /** Ms epoch when the slice was last refreshed from the cloud. */
60596
60983
  lastFetchedAt: number()
60597
60984
  });
@@ -61028,36 +61415,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
61028
61415
  });
61029
61416
  object({
61030
61417
  /** Instantaneous power draw in watts. */
61031
- watts: number().optional().meta({
61032
- unit: "W",
61033
- label: "Power"
61034
- }),
61418
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
61035
61419
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61036
- kwhTotal: number().optional().meta({
61037
- unit: "kWh",
61038
- label: "Energy"
61039
- }),
61420
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61040
61421
  /** Voltage in volts. */
61041
- volts: number().optional(),
61422
+ volts: number().optional().meta(readingMeta("voltage")),
61042
61423
  /** Current in amperes. */
61043
- amps: number().optional(),
61044
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
61045
- va: number().optional(),
61424
+ amps: number().optional().meta(readingMeta("current")),
61425
+ /** Apparent power in volt-amperes. */
61426
+ va: number().optional().meta(readingMeta("apparent-power")),
61046
61427
  /** Power factor (0..1) if reported. */
61047
- powerFactor: number().min(0).max(1).optional(),
61428
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
61048
61429
  /** Ms epoch when the slice was last updated. */
61049
- lastFetchedAt: number(),
61050
- /** Live display unit of the single metric this slice carries (e.g. HA
61051
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
61052
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
61053
- * unit per slice is unambiguous. The UI prefers this over the role's
61054
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
61055
- unit: string().optional(),
61056
- /** Suggested decimal places for numeric display.
61057
- * Populated live from the upstream source when provided (e.g. HA
61058
- * `attributes.suggested_display_precision`). Falls back to
61059
- * auto-formatting when absent. */
61060
- precision: number().int().min(0).max(10).optional()
61430
+ lastFetchedAt: number()
61061
61431
  });
61062
61432
  DeviceType.Sensor;
61063
61433
  /**
@@ -61078,11 +61448,11 @@ DeviceType.Sensor;
61078
61448
  */
61079
61449
  var GpsLocationSchema = object({
61080
61450
  /** Latitude in decimal degrees, -90..90. */
61081
- latitude: number().min(-90).max(90),
61451
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
61082
61452
  /** Longitude in decimal degrees, -180..180. */
61083
- longitude: number().min(-180).max(180),
61453
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
61084
61454
  /** Reported accuracy in meters (lower = better). */
61085
- accuracyMeters: number().nonnegative()
61455
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
61086
61456
  });
61087
61457
  object({
61088
61458
  /** `home` / `not_home` / any user-defined zone name. */
@@ -61094,25 +61464,16 @@ object({
61094
61464
  gps: GpsLocationSchema.nullable(),
61095
61465
  /** Optional battery level of the tracking device (0..100). Null
61096
61466
  * when not reported. */
61097
- batteryPercent: number().min(0).max(100).nullable(),
61467
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61098
61468
  /** Ms epoch when the slice was last updated. */
61099
61469
  lastChangedAt: number()
61100
61470
  });
61101
61471
  DeviceType.Presence;
61102
61472
  object({
61103
- /** Current pressure in hPa. */
61104
- hpa: number(),
61473
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
61474
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
61105
61475
  /** Ms epoch when the slice was last updated. */
61106
- lastFetchedAt: number(),
61107
- /** Live display unit from the upstream source (e.g. HA
61108
- * `attributes.unit_of_measurement`). The UI prefers this over the
61109
- * role's canonical unit. Absent → fall back to the canonical unit. */
61110
- unit: string().optional(),
61111
- /** Suggested decimal places for numeric display.
61112
- * Populated live from the upstream source when provided (e.g. HA
61113
- * `attributes.suggested_display_precision`). Falls back to
61114
- * auto-formatting when absent. */
61115
- precision: number().int().min(0).max(10).optional()
61476
+ lastFetchedAt: number()
61116
61477
  });
61117
61478
  DeviceType.Sensor;
61118
61479
  /**
@@ -61445,8 +61806,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
61445
61806
  }), _void(), { kind: "mutation" });
61446
61807
  var PtzAutotrackSettingsSchema = object({
61447
61808
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
61448
- stopDelaySeconds: number().int().min(0).max(300),
61449
- disappearDelaySeconds: number().int().min(0).max(300)
61809
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
61810
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
61450
61811
  });
61451
61812
  /**
61452
61813
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -61503,7 +61864,7 @@ object({
61503
61864
  /** null = unknown — never coerced to false (D393). */
61504
61865
  running: boolean().nullable(),
61505
61866
  /** Epoch ms the current boolean began; null while `running` is null. */
61506
- since: number().nullable(),
61867
+ since: number().nullable().meta(readingMeta("none")),
61507
61868
  source: RunningStateSourceSchema.meta({ label: "Source" }),
61508
61869
  phase: string().nullable(),
61509
61870
  /** null until a reading has been published (D393). */
@@ -62701,9 +63062,9 @@ var RecordTriggerSchema = _enum([
62701
63062
  */
62702
63063
  var RecordWindowSchema = object({
62703
63064
  trigger: RecordTriggerSchema,
62704
- day: number().int().min(0).max(6),
62705
- startMinute: number().int().min(0).max(1439),
62706
- endMinute: number().int().min(1).max(1440)
63065
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
63066
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
63067
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
62707
63068
  });
62708
63069
  /** Status of one physical volume, as the camera itself describes it. */
62709
63070
  var OnboardStorageVolumeSchema = object({
@@ -62772,10 +63133,10 @@ object({
62772
63133
  /** Master "record to the card at all" switch. */
62773
63134
  enabled: boolean().nullable(),
62774
63135
  overwriteWhenFull: boolean().nullable(),
62775
- preRecordSec: number().nullable(),
62776
- postRecordSec: number().nullable(),
63136
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
63137
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
62777
63138
  /** Length of one recorded file, in minutes. */
62778
- segmentMinutes: number().nullable(),
63139
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
62779
63140
  /** The primary track's weekly windows, flattened. */
62780
63141
  windows: array(RecordWindowSchema),
62781
63142
  /**
@@ -62791,7 +63152,7 @@ object({
62791
63152
  * provider reporting a non-zero count also reports the schedule as not
62792
63153
  * writable.
62793
63154
  */
62794
- unreadableWindows: number(),
63155
+ unreadableWindows: number().meta(readingMeta("count")),
62795
63156
  /**
62796
63157
  * The camera is scheduled to record and has NO usable storage.
62797
63158
  *
@@ -63428,7 +63789,7 @@ var CamStreamDescriptorSchema = object({
63428
63789
  url: string().optional(),
63429
63790
  codec: string().optional(),
63430
63791
  resolution: CamStreamResolutionSchema.optional(),
63431
- fps: number().positive().optional(),
63792
+ fps: number().positive().optional().meta(readingMeta("rate")),
63432
63793
  label: string().optional(),
63433
63794
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
63434
63795
  deviceFeatures: array(string()).optional(),
@@ -63458,18 +63819,18 @@ var StreamProfileSchema = _enum([
63458
63819
  ]);
63459
63820
  /** The current encoding config of one stream profile. */
63460
63821
  var StreamProfileConfigSchema = object({
63461
- width: number(),
63462
- height: number(),
63822
+ width: number().meta(readingMeta("none")),
63823
+ height: number().meta(readingMeta("none")),
63463
63824
  codec: _enum(["h264", "h265"]),
63464
- framerate: number(),
63465
- bitrate: number(),
63825
+ framerate: number().meta(readingMeta("rate")),
63826
+ bitrate: number().meta(readingMeta("bitrate")),
63466
63827
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
63467
63828
  encoderProfile: _enum([
63468
63829
  "high",
63469
63830
  "main",
63470
63831
  "baseline"
63471
63832
  ]).optional(),
63472
- gop: number().optional(),
63833
+ gop: number().optional().meta(readingMeta("none")),
63473
63834
  /**
63474
63835
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
63475
63836
  *
@@ -64112,23 +64473,13 @@ object({
64112
64473
  });
64113
64474
  DeviceType.Sensor;
64114
64475
  object({
64115
- /** Current temperature in Celsius. */
64116
- celsius: number().meta({
64117
- unit: "°C",
64476
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64477
+ celsius: number().nullable().meta(readingMeta("temperature", {
64478
+ precision: 1,
64118
64479
  label: "Temperature"
64119
- }),
64480
+ })),
64120
64481
  /** Ms epoch when the slice was last updated (push or poll). */
64121
- lastFetchedAt: number(),
64122
- /** Live display unit from the upstream source (e.g. HA
64123
- * `attributes.unit_of_measurement`). The UI prefers this over the
64124
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
64125
- * Absent → the UI falls back to the role's canonical unit. */
64126
- unit: string().optional(),
64127
- /** Suggested decimal places for numeric display.
64128
- * Populated live from the upstream source when provided (e.g. HA
64129
- * `attributes.suggested_display_precision`). Falls back to
64130
- * auto-formatting when absent. */
64131
- precision: number().int().min(0).max(10).optional()
64482
+ lastFetchedAt: number()
64132
64483
  });
64133
64484
  DeviceType.Sensor, DeviceType.Thermostat;
64134
64485
  /**
@@ -64380,16 +64731,16 @@ object({
64380
64731
  ]),
64381
64732
  /** 0..100 battery percentage. Null when the device has no battery
64382
64733
  * reading. */
64383
- batteryLevel: number().min(0).max(100).nullable(),
64734
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64384
64735
  /** Current fan-speed token (provider-verbatim). Null when unknown or
64385
64736
  * the vacuum has no speed control. */
64386
64737
  fanSpeed: string().nullable(),
64387
64738
  /** Speed tokens the hardware accepts — drives the UI selector. */
64388
64739
  availableFanSpeeds: array(string()),
64389
64740
  /** 0..100 cleaning-completion percentage of the current task, or null. */
64390
- progressPercent: number().min(0).max(100).nullable(),
64741
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64391
64742
  /** Current error code (0 / null = no error). */
64392
- errorCode: number().nullable(),
64743
+ errorCode: number().nullable().meta(readingMeta("none")),
64393
64744
  /** Human label for {@link errorCode}, or null when none / undecodable. */
64394
64745
  errorLabel: string().nullable(),
64395
64746
  /** Ms epoch when the slice was last updated. */
@@ -64428,7 +64779,7 @@ object({
64428
64779
  ]),
64429
64780
  /** 0 = fully closed, 100 = fully open. Null when the device has no
64430
64781
  * intermediate position surface. */
64431
- position: number().min(0).max(100).nullable(),
64782
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64432
64783
  /** Ms epoch when the slice was last updated. */
64433
64784
  lastChangedAt: number()
64434
64785
  });
@@ -64456,9 +64807,9 @@ object({
64456
64807
  DeviceType.Sensor;
64457
64808
  object({
64458
64809
  /** Current measured temperature. Null when not reported. */
64459
- currentTemp: number().nullable(),
64810
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
64460
64811
  /** Target temperature setpoint. Null when no setpoint surface. */
64461
- targetTemp: number().nullable(),
64812
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
64462
64813
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
64463
64814
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
64464
64815
  * device reports an unknown state. */
@@ -64469,9 +64820,9 @@ object({
64469
64820
  * has no away surface. */
64470
64821
  away: boolean().nullable(),
64471
64822
  /** HA `min_temp` attribute. Null when not reported. */
64472
- minTemp: number().nullable(),
64823
+ minTemp: number().nullable().meta(readingMeta("temperature")),
64473
64824
  /** HA `max_temp` attribute. Null when not reported. */
64474
- maxTemp: number().nullable(),
64825
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
64475
64826
  /** Ms epoch when the slice was last updated. */
64476
64827
  lastChangedAt: number()
64477
64828
  });
@@ -64495,25 +64846,18 @@ DeviceType.WaterHeater, method(object({
64495
64846
  auth: "protected"
64496
64847
  });
64497
64848
  object({
64498
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
64499
- * when no condition has been reported yet. */
64849
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
64500
64850
  condition: string().nullable(),
64501
- /** Current temperature in the reported unit. Null when not provided. */
64502
- temperature: number().nullable(),
64503
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
64504
- temperatureUnit: string().nullable(),
64851
+ /** Current temperature in Celsius. Null when not provided or refused. */
64852
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
64505
64853
  /** Relative humidity (0..100). Null when not provided. */
64506
- humidity: number().min(0).max(100).nullable(),
64507
- /** Barometric pressure in the reported unit. Null when not provided. */
64508
- pressure: number().nullable(),
64509
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
64510
- pressureUnit: string().nullable(),
64511
- /** Wind speed in the reported unit. Null when not provided. */
64512
- windSpeed: number().nullable(),
64513
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
64514
- windSpeedUnit: string().nullable(),
64854
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
64855
+ /** Barometric pressure in hPa. Null when not provided or refused. */
64856
+ pressure: number().nullable().meta(readingMeta("pressure")),
64857
+ /** Wind speed in metres per second. Null when not provided or refused. */
64858
+ windSpeed: number().nullable().meta(readingMeta("speed")),
64515
64859
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
64516
- windBearing: number().nullable(),
64860
+ windBearing: number().nullable().meta(readingMeta("angle")),
64517
64861
  /** Ms epoch when the slice was last updated. */
64518
64862
  lastFetchedAt: number()
64519
64863
  });
@@ -65425,6 +65769,13 @@ Object.freeze({
65425
65769
  access: "view",
65426
65770
  role: "admin"
65427
65771
  },
65772
+ "addons.getPackageDocs": {
65773
+ capName: "addons",
65774
+ capScope: "system",
65775
+ addonId: null,
65776
+ access: "view",
65777
+ role: "admin"
65778
+ },
65428
65779
  "addons.getVersions": {
65429
65780
  capName: "addons",
65430
65781
  capScope: "system",
@@ -68533,6 +68884,20 @@ Object.freeze({
68533
68884
  access: "view",
68534
68885
  role: "viewer"
68535
68886
  },
68887
+ "groupCommands.getStatus": {
68888
+ capName: "group-commands",
68889
+ capScope: "device",
68890
+ addonId: null,
68891
+ access: "view",
68892
+ role: "viewer"
68893
+ },
68894
+ "groupStatus.getStatus": {
68895
+ capName: "group-status",
68896
+ capScope: "device",
68897
+ addonId: null,
68898
+ access: "view",
68899
+ role: "viewer"
68900
+ },
68536
68901
  "heatingValve.getStatus": {
68537
68902
  capName: "heating-valve",
68538
68903
  capScope: "device",
@@ -78355,6 +78720,37 @@ var SENSOR_CAP_VOCABULARY = {
78355
78720
  icon: "flame"
78356
78721
  }]
78357
78722
  },
78723
+ "group-commands": {
78724
+ scope: "device",
78725
+ noun: t("Group command", "Comando di gruppo")
78726
+ },
78727
+ "group-status": {
78728
+ scope: "device",
78729
+ noun: t("Group", "Gruppo"),
78730
+ state: [
78731
+ {
78732
+ kind: "numeric",
78733
+ field: "included",
78734
+ quantity: t("the members reporting", "i membri che rispondono"),
78735
+ label: t("Reporting", "Rispondono"),
78736
+ icon: "users"
78737
+ },
78738
+ {
78739
+ kind: "numeric",
78740
+ field: "excluded",
78741
+ quantity: t("the members left out", "i membri esclusi"),
78742
+ label: t("Excluded", "Esclusi"),
78743
+ icon: "users"
78744
+ },
78745
+ {
78746
+ kind: "numeric",
78747
+ field: "total",
78748
+ quantity: t("the member count", "il numero di membri"),
78749
+ label: t("Members", "Membri"),
78750
+ icon: "sigma"
78751
+ }
78752
+ ]
78753
+ },
78358
78754
  "humidity-sensor": {
78359
78755
  scope: "device",
78360
78756
  noun: t("Humidity", "Umidità"),
@@ -78388,7 +78784,6 @@ var SENSOR_CAP_VOCABULARY = {
78388
78784
  field: "value",
78389
78785
  quantity: t("the reading", "il valore"),
78390
78786
  label: t("Reading", "Valore"),
78391
- unitField: "unit",
78392
78787
  icon: "gauge"
78393
78788
  }]
78394
78789
  },
@@ -78524,7 +78919,7 @@ var SENSOR_CAP_VOCABULARY = {
78524
78919
  field: "temperature",
78525
78920
  quantity: t("the outdoor temperature", "la temperatura esterna"),
78526
78921
  label: t("Temperature", "Temperatura"),
78527
- unitField: "temperatureUnit",
78922
+ unit: "°C",
78528
78923
  icon: "thermometer"
78529
78924
  },
78530
78925
  {
@@ -78540,7 +78935,7 @@ var SENSOR_CAP_VOCABULARY = {
78540
78935
  field: "windSpeed",
78541
78936
  quantity: t("the wind speed", "la velocità del vento"),
78542
78937
  label: t("Wind", "Vento"),
78543
- unitField: "windSpeedUnit",
78938
+ unit: "m/s",
78544
78939
  icon: "wind"
78545
78940
  },
78546
78941
  {
@@ -78548,7 +78943,7 @@ var SENSOR_CAP_VOCABULARY = {
78548
78943
  field: "pressure",
78549
78944
  quantity: t("the air pressure", "la pressione atmosferica"),
78550
78945
  label: t("Pressure", "Pressione"),
78551
- unitField: "pressureUnit",
78946
+ unit: "hPa",
78552
78947
  icon: "gauge"
78553
78948
  }
78554
78949
  ]
@@ -79821,6 +80216,16 @@ Object.freeze({
79821
80216
  form: "single",
79822
80217
  optional: false
79823
80218
  }],
80219
+ "groupCommands.getStatus": [{
80220
+ name: "deviceId",
80221
+ form: "single",
80222
+ optional: false
80223
+ }],
80224
+ "groupStatus.getStatus": [{
80225
+ name: "deviceId",
80226
+ form: "single",
80227
+ optional: false
80228
+ }],
79824
80229
  "heatingValve.getStatus": [{
79825
80230
  name: "deviceId",
79826
80231
  form: "single",
@@ -81685,6 +82090,11 @@ var PulseOverrideDraftSchema = object({
81685
82090
  targetId: string().min(1),
81686
82091
  enabled: boolean(),
81687
82092
  debounceMs: number().nonnegative(),
82093
+ edges: array(_enum([
82094
+ "rising",
82095
+ "button",
82096
+ "native-event"
82097
+ ])).min(1).optional(),
81688
82098
  sources: array(OverrideSourceSchema).min(1)
81689
82099
  });
81690
82100
  var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
@@ -81800,6 +82210,8 @@ new Map(Object.entries({
81800
82210
  "detection-blind": "device-events",
81801
82211
  "motion-noisy": "device-events",
81802
82212
  "motion-calm": "device-events",
82213
+ "package-llm-silent": "device-events",
82214
+ "package-llm-answered": "device-events",
81803
82215
  "recording-stalled": "device-events",
81804
82216
  "recording-healthy": "device-events",
81805
82217
  "recording-degraded": "device-events",
@@ -81883,6 +82295,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
81883
82295
  label: "Motion without a subject",
81884
82296
  kinds: ["motion-noisy", "motion-calm"]
81885
82297
  },
82298
+ {
82299
+ id: "package-llm",
82300
+ family: "device-events",
82301
+ label: "Package confirm",
82302
+ kinds: ["package-llm-silent", "package-llm-answered"]
82303
+ },
81886
82304
  {
81887
82305
  id: "recording",
81888
82306
  family: "device-events",
@@ -82064,6 +82482,8 @@ var DEVICE_EVENTS = [
82064
82482
  "detection-blind",
82065
82483
  "motion-noisy",
82066
82484
  "motion-calm",
82485
+ "package-llm-silent",
82486
+ "package-llm-answered",
82067
82487
  "recording-stalled",
82068
82488
  "recording-healthy",
82069
82489
  "recording-degraded",
@@ -82332,6 +82752,15 @@ new Map([
82332
82752
  snapshotLabel: "Cameras with constant motion now",
82333
82753
  snapshotSection: null
82334
82754
  },
82755
+ {
82756
+ id: "package-llm-confirm",
82757
+ family: "device-events",
82758
+ label: "Package confirm (not answering / answering again)",
82759
+ bad: ["package-llm-silent"],
82760
+ recovery: ["package-llm-answered"],
82761
+ snapshotLabel: "Cameras whose package confirm is not answering",
82762
+ snapshotSection: null
82763
+ },
82335
82764
  {
82336
82765
  id: "recording-health",
82337
82766
  family: "device-events",