@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.
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { spawn } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
5
- //#region ../types/dist/event-category-DHXxB8t9.mjs
5
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
6
6
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
7
7
  EventCategory["SystemBoot"] = "system.boot";
8
8
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -328,6 +328,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
328
328
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
329
329
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
330
330
  /**
331
+ * A camera's package LLM confirm has failed closed three times in a row
332
+ * (D931, 2026-10-26). One edge per camera until
333
+ * {@link PipelinePackageLlmAnswered}.
334
+ */
335
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
336
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
337
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
338
+ /**
331
339
  * Per-camera pipeline config was mutated by the orchestrator
332
340
  * (a settings change via `setAgentInferenceDevices` /
333
341
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5570,7 +5578,7 @@ var ZodIssueCode = {
5570
5578
  var ZodFirstPartyTypeKind;
5571
5579
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5572
5580
  //#endregion
5573
- //#region ../types/dist/sleep-BErAxhul.mjs
5581
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5574
5582
  /**
5575
5583
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5576
5584
  * window to float samples (D455).
@@ -8322,6 +8330,99 @@ function defineEntryKind(name, spec) {
8322
8330
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8323
8331
  return defined;
8324
8332
  }
8333
+ var READING_QUANTITIES = [
8334
+ "temperature",
8335
+ "pressure",
8336
+ "speed",
8337
+ "precipitation",
8338
+ "precipitation-rate",
8339
+ "length",
8340
+ "area",
8341
+ "illuminance",
8342
+ "irradiance",
8343
+ "power",
8344
+ "energy",
8345
+ "voltage",
8346
+ "current",
8347
+ "apparent-power",
8348
+ "humidity",
8349
+ "percentage",
8350
+ "concentration-volume",
8351
+ "concentration-mass",
8352
+ "concentration-ppb",
8353
+ "volume",
8354
+ "duration",
8355
+ "duration-ms",
8356
+ "information",
8357
+ "sound-level",
8358
+ "signal-level",
8359
+ "colour-temperature",
8360
+ "angle",
8361
+ "bitrate",
8362
+ "rate",
8363
+ "mass",
8364
+ "index",
8365
+ "ratio",
8366
+ "count",
8367
+ "none",
8368
+ "device"
8369
+ ];
8370
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8371
+ new Set(READING_QUANTITIES);
8372
+ /**
8373
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8374
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8375
+ */
8376
+ var QUANTITY_BASE = {
8377
+ temperature: "°C",
8378
+ pressure: "hPa",
8379
+ speed: "m/s",
8380
+ precipitation: "mm",
8381
+ "precipitation-rate": "mm/h",
8382
+ length: "m",
8383
+ area: "m²",
8384
+ illuminance: "lx",
8385
+ irradiance: "W/m²",
8386
+ power: "W",
8387
+ energy: "kWh",
8388
+ voltage: "V",
8389
+ current: "A",
8390
+ "apparent-power": "VA",
8391
+ humidity: "%",
8392
+ percentage: "%",
8393
+ "concentration-volume": "ppm",
8394
+ "concentration-mass": "µg/m³",
8395
+ "concentration-ppb": "ppb",
8396
+ volume: "L",
8397
+ duration: "s",
8398
+ "duration-ms": "ms",
8399
+ information: "B",
8400
+ "sound-level": "dBFS",
8401
+ "signal-level": "dBm",
8402
+ "colour-temperature": "mired",
8403
+ angle: "°",
8404
+ bitrate: "kbps",
8405
+ rate: "fps",
8406
+ mass: "kg",
8407
+ index: "none",
8408
+ ratio: "none",
8409
+ count: "none",
8410
+ none: "none",
8411
+ device: "device"
8412
+ };
8413
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8414
+ function baseUnitOf(quantity) {
8415
+ const symbol = QUANTITY_BASE[quantity];
8416
+ if (symbol === "none" || symbol === "device") return void 0;
8417
+ return symbol;
8418
+ }
8419
+ function readingMeta(quantity, extras) {
8420
+ return {
8421
+ quantity,
8422
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8423
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8424
+ };
8425
+ }
8325
8426
  /** Shared Zod schemas used across streaming capabilities. */
8326
8427
  var CamProfileSchema = _enum([
8327
8428
  "high",
@@ -8349,8 +8450,8 @@ var CamStreamKindSchema = _enum([
8349
8450
  "derived"
8350
8451
  ]);
8351
8452
  var CamStreamResolutionSchema = object({
8352
- width: number().int().positive(),
8353
- height: number().int().positive()
8453
+ width: number().int().positive().meta(readingMeta("none")),
8454
+ height: number().int().positive().meta(readingMeta("none"))
8354
8455
  });
8355
8456
  var CameraStreamSchema = object({
8356
8457
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8886,6 +8987,8 @@ var CAP_NAMES = Object.freeze([
8886
8987
  "filesystem-browse",
8887
8988
  "flood",
8888
8989
  "gas",
8990
+ "group-commands",
8991
+ "group-status",
8889
8992
  "heating-valve",
8890
8993
  "humidifier",
8891
8994
  "humidity-sensor",
@@ -9015,14 +9118,6 @@ Object.freeze({
9015
9118
  {
9016
9119
  "path": "aqi",
9017
9120
  "valueType": "number"
9018
- },
9019
- {
9020
- "path": "unit",
9021
- "valueType": "string"
9022
- },
9023
- {
9024
- "path": "precision",
9025
- "valueType": "number"
9026
9121
  }
9027
9122
  ],
9028
9123
  "alarm-panel": [
@@ -9058,20 +9153,10 @@ Object.freeze({
9058
9153
  "valueType": "boolean"
9059
9154
  }
9060
9155
  ],
9061
- "ambient-light-sensor": [
9062
- {
9063
- "path": "lux",
9064
- "valueType": "number"
9065
- },
9066
- {
9067
- "path": "unit",
9068
- "valueType": "string"
9069
- },
9070
- {
9071
- "path": "precision",
9072
- "valueType": "number"
9073
- }
9074
- ],
9156
+ "ambient-light-sensor": [{
9157
+ "path": "lux",
9158
+ "valueType": "number"
9159
+ }],
9075
9160
  "audio-metrics": [
9076
9161
  {
9077
9162
  "path": "ts",
@@ -9151,6 +9236,10 @@ Object.freeze({
9151
9236
  "path": "lastUpdated",
9152
9237
  "valueType": "number"
9153
9238
  },
9239
+ {
9240
+ "path": "volts",
9241
+ "valueType": "number"
9242
+ },
9154
9243
  {
9155
9244
  "path": "lastContactAt",
9156
9245
  "valueType": "number"
@@ -9393,10 +9482,6 @@ Object.freeze({
9393
9482
  "datetime"
9394
9483
  ]
9395
9484
  },
9396
- {
9397
- "path": "unit",
9398
- "valueType": "string"
9399
- },
9400
9485
  {
9401
9486
  "path": "min",
9402
9487
  "valueType": "number"
@@ -9408,19 +9493,6 @@ Object.freeze({
9408
9493
  {
9409
9494
  "path": "step",
9410
9495
  "valueType": "number"
9411
- },
9412
- {
9413
- "path": "precision",
9414
- "valueType": "number"
9415
- },
9416
- {
9417
- "path": "format",
9418
- "valueType": "enum",
9419
- "enumValues": [
9420
- "date",
9421
- "time",
9422
- "datetime"
9423
- ]
9424
9496
  }
9425
9497
  ],
9426
9498
  "cover": [
@@ -9661,13 +9733,8 @@ Object.freeze({
9661
9733
  "path": "value",
9662
9734
  "valueType": "string"
9663
9735
  }, {
9664
- "path": "format",
9665
- "valueType": "enum",
9666
- "enumValues": [
9667
- "date",
9668
- "time",
9669
- "datetime"
9670
- ]
9736
+ "path": "timestamp",
9737
+ "valueType": "boolean"
9671
9738
  }],
9672
9739
  "event-emitter": [
9673
9740
  {
@@ -9740,6 +9807,20 @@ Object.freeze({
9740
9807
  "path": "detected",
9741
9808
  "valueType": "boolean"
9742
9809
  }],
9810
+ "group-status": [
9811
+ {
9812
+ "path": "included",
9813
+ "valueType": "number"
9814
+ },
9815
+ {
9816
+ "path": "excluded",
9817
+ "valueType": "number"
9818
+ },
9819
+ {
9820
+ "path": "total",
9821
+ "valueType": "number"
9822
+ }
9823
+ ],
9743
9824
  "heating-valve": [{
9744
9825
  "path": "position",
9745
9826
  "valueType": "number"
@@ -9774,20 +9855,10 @@ Object.freeze({
9774
9855
  "valueType": "number"
9775
9856
  }
9776
9857
  ],
9777
- "humidity-sensor": [
9778
- {
9779
- "path": "percent",
9780
- "valueType": "number"
9781
- },
9782
- {
9783
- "path": "unit",
9784
- "valueType": "string"
9785
- },
9786
- {
9787
- "path": "precision",
9788
- "valueType": "number"
9789
- }
9790
- ],
9858
+ "humidity-sensor": [{
9859
+ "path": "percent",
9860
+ "valueType": "number"
9861
+ }],
9791
9862
  "image": [{
9792
9863
  "path": "url",
9793
9864
  "valueType": "string"
@@ -10126,20 +10197,10 @@ Object.freeze({
10126
10197
  "valueType": "boolean"
10127
10198
  }
10128
10199
  ],
10129
- "numeric-sensor": [
10130
- {
10131
- "path": "value",
10132
- "valueType": "number"
10133
- },
10134
- {
10135
- "path": "unit",
10136
- "valueType": "string"
10137
- },
10138
- {
10139
- "path": "precision",
10140
- "valueType": "number"
10141
- }
10142
- ],
10200
+ "numeric-sensor": [{
10201
+ "path": "value",
10202
+ "valueType": "number"
10203
+ }],
10143
10204
  "pet-feeder": [
10144
10205
  {
10145
10206
  "path": "foodLevel",
@@ -10231,14 +10292,6 @@ Object.freeze({
10231
10292
  {
10232
10293
  "path": "powerFactor",
10233
10294
  "valueType": "number"
10234
- },
10235
- {
10236
- "path": "unit",
10237
- "valueType": "string"
10238
- },
10239
- {
10240
- "path": "precision",
10241
- "valueType": "number"
10242
10295
  }
10243
10296
  ],
10244
10297
  "presence": [
@@ -10267,20 +10320,10 @@ Object.freeze({
10267
10320
  "valueType": "number"
10268
10321
  }
10269
10322
  ],
10270
- "pressure-sensor": [
10271
- {
10272
- "path": "hpa",
10273
- "valueType": "number"
10274
- },
10275
- {
10276
- "path": "unit",
10277
- "valueType": "string"
10278
- },
10279
- {
10280
- "path": "precision",
10281
- "valueType": "number"
10282
- }
10283
- ],
10323
+ "pressure-sensor": [{
10324
+ "path": "hpa",
10325
+ "valueType": "number"
10326
+ }],
10284
10327
  "privacy-mask": [{
10285
10328
  "path": "enabled",
10286
10329
  "valueType": "boolean"
@@ -10591,20 +10634,10 @@ Object.freeze({
10591
10634
  "path": "tampered",
10592
10635
  "valueType": "boolean"
10593
10636
  }],
10594
- "temperature-sensor": [
10595
- {
10596
- "path": "celsius",
10597
- "valueType": "number"
10598
- },
10599
- {
10600
- "path": "unit",
10601
- "valueType": "string"
10602
- },
10603
- {
10604
- "path": "precision",
10605
- "valueType": "number"
10606
- }
10607
- ],
10637
+ "temperature-sensor": [{
10638
+ "path": "celsius",
10639
+ "valueType": "number"
10640
+ }],
10608
10641
  "update": [
10609
10642
  {
10610
10643
  "path": "currentVersion",
@@ -10715,10 +10748,6 @@ Object.freeze({
10715
10748
  "path": "temperature",
10716
10749
  "valueType": "number"
10717
10750
  },
10718
- {
10719
- "path": "temperatureUnit",
10720
- "valueType": "string"
10721
- },
10722
10751
  {
10723
10752
  "path": "humidity",
10724
10753
  "valueType": "number"
@@ -10727,18 +10756,10 @@ Object.freeze({
10727
10756
  "path": "pressure",
10728
10757
  "valueType": "number"
10729
10758
  },
10730
- {
10731
- "path": "pressureUnit",
10732
- "valueType": "string"
10733
- },
10734
10759
  {
10735
10760
  "path": "windSpeed",
10736
10761
  "valueType": "number"
10737
10762
  },
10738
- {
10739
- "path": "windSpeedUnit",
10740
- "valueType": "string"
10741
- },
10742
10763
  {
10743
10764
  "path": "windBearing",
10744
10765
  "valueType": "number"
@@ -11131,7 +11152,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11131
11152
  return DeviceRole;
11132
11153
  }({});
11133
11154
  //#endregion
11134
- //#region ../types/dist/composition-kEl9mevu.mjs
11155
+ //#region ../types/dist/composition-OhpuespA.mjs
11135
11156
  /**
11136
11157
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11137
11158
  *
@@ -11886,15 +11907,74 @@ function validateExpressionSource(src) {
11886
11907
  }
11887
11908
  return null;
11888
11909
  }
11889
- /** A composed device's stableId is this prefix plus the block id. */
11890
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11891
- /** Longest snippet a `code` source may carry (slice 4). */
11892
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11910
+ /**
11911
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11912
+ * id. Split out so helper schemas can name it without a cycle through
11913
+ * `composition.ts` (D659).
11914
+ */
11893
11915
  var CompositionSourceRefSchema = object({
11894
11916
  /** The owning addon; `stableId` is unique only within it. */
11895
11917
  addonId: string().min(1),
11896
11918
  stableId: string().min(1)
11897
11919
  });
11920
+ var GROUP_TEMPLATE_IDS = [
11921
+ "temperature",
11922
+ "humidity",
11923
+ "pressure",
11924
+ "illuminance",
11925
+ "power",
11926
+ "energy",
11927
+ "battery",
11928
+ "numeric",
11929
+ "motion",
11930
+ "contact",
11931
+ "leak",
11932
+ "smoke",
11933
+ "occupancy",
11934
+ "binary",
11935
+ "switch",
11936
+ "light"
11937
+ ];
11938
+ var NUMERIC_AGGREGATES = [
11939
+ "mean",
11940
+ "median",
11941
+ "min",
11942
+ "max",
11943
+ "sum"
11944
+ ];
11945
+ var BINARY_AGGREGATES = ["any", "all"];
11946
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11947
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11948
+ var AGGREGATE_REDUCERS = [
11949
+ "mean",
11950
+ "median",
11951
+ "min",
11952
+ "max",
11953
+ "sum",
11954
+ "any",
11955
+ "all"
11956
+ ];
11957
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11958
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11959
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11960
+ /** One member. The field is stored so occupancy can name motion or binary. */
11961
+ var HelperMemberSchema = object({
11962
+ source: CompositionSourceRefSchema,
11963
+ cap: string().min(1),
11964
+ fieldPath: string().min(1)
11965
+ });
11966
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
11967
+ kind: literal("group"),
11968
+ templateId: GroupTemplateIdSchema,
11969
+ aggregate: AggregateReducerSchema,
11970
+ members: array(HelperMemberSchema).min(1).max(64),
11971
+ /** Actuator groups only. Readings leave this unset. */
11972
+ active: ActiveCriterionSchema.optional()
11973
+ })]);
11974
+ /** A composed device's stableId is this prefix plus the block id. */
11975
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11976
+ /** Longest snippet a `code` source may carry (slice 4). */
11977
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11898
11978
  /** One field of one capability of one source device. */
11899
11979
  var CompositionFieldReadSchema = object({
11900
11980
  source: CompositionSourceRefSchema,
@@ -11938,7 +12018,33 @@ var CompositionCodeSourceSchema = object({
11938
12018
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11939
12019
  CompositionFromSourceSchema,
11940
12020
  CompositionExpressionSourceSchema,
11941
- CompositionCodeSourceSchema
12021
+ CompositionCodeSourceSchema,
12022
+ object({
12023
+ kind: literal("aggregate"),
12024
+ reducer: _enum([
12025
+ "mean",
12026
+ "median",
12027
+ "min",
12028
+ "max",
12029
+ "sum",
12030
+ "any",
12031
+ "all"
12032
+ ]),
12033
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
12034
+ /** `value` is the reading; the others are the member counts. */
12035
+ publish: _enum([
12036
+ "value",
12037
+ "included",
12038
+ "excluded",
12039
+ "total"
12040
+ ]),
12041
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
12042
+ empty: _enum([
12043
+ "null",
12044
+ "omit",
12045
+ "offline"
12046
+ ])
12047
+ })
11942
12048
  ]);
11943
12049
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11944
12050
  kind: literal("forward"),
@@ -11984,7 +12090,13 @@ var CompositionSchema = object({
11984
12090
  kind: literal("existing"),
11985
12091
  device: CompositionSourceRefSchema
11986
12092
  })]),
11987
- features: array(CompositionFeatureSchema).min(1).max(16)
12093
+ features: array(CompositionFeatureSchema).min(1).max(16),
12094
+ /**
12095
+ * Present when the block is a helper template. Absent on a free
12096
+ * composition (the room example, device 4603). The editor reads this,
12097
+ * never the compiled features.
12098
+ */
12099
+ helper: HelperSpecSchema.optional()
11988
12100
  }).superRefine((composition, ctx) => {
11989
12101
  const seen = /* @__PURE__ */ new Set();
11990
12102
  composition.features.forEach((feature, index) => {
@@ -18086,7 +18198,11 @@ var CompositionProblemSchema = object({
18086
18198
  "item-field-reserved",
18087
18199
  "target-missing",
18088
18200
  "target-unreadable",
18089
- "not-replaceable"
18201
+ "not-replaceable",
18202
+ "native-capability-replace",
18203
+ "mixed-quantity",
18204
+ "helper-mismatch",
18205
+ "member-constraint"
18090
18206
  ]),
18091
18207
  path: string(),
18092
18208
  message: string()
@@ -18107,7 +18223,8 @@ var CompositionUnavailableModeSchema = _enum([
18107
18223
  "null",
18108
18224
  "offline",
18109
18225
  "unknown",
18110
- "release"
18226
+ "release",
18227
+ "omit"
18111
18228
  ]);
18112
18229
  /**
18113
18230
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -18144,9 +18261,11 @@ var CompositionFieldPlanSchema = object({
18144
18261
  reads: array(CompositionFieldReadSchema)
18145
18262
  });
18146
18263
  /**
18147
- * What a feature does to its target (D663): `new` builds a capability on a new
18148
- * device; on an existing device it `add`s a capability the device lacks, or
18149
- * `replace`s named fields of one it has natively.
18264
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
18265
+ * new device; on an existing device it `add`s a capability the device lacks.
18266
+ * Replacing a capability the device already serves is refused
18267
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
18268
+ * wire for a claim the hub already holds.
18150
18269
  */
18151
18270
  var CompositionFeatureModeSchema = _enum([
18152
18271
  "new",
@@ -18319,6 +18438,275 @@ Object.freeze({
18319
18438
  result: ["number", "null"]
18320
18439
  }
18321
18440
  });
18441
+ /**
18442
+ * One catalog row per base reading a group helper can build (D944 §3).
18443
+ * Data, not a form default written into the block.
18444
+ */
18445
+ var NOW = {
18446
+ fieldPath: "lastFetchedAt",
18447
+ expr: "now"
18448
+ };
18449
+ var CHANGED = {
18450
+ fieldPath: "lastChangedAt",
18451
+ expr: "now"
18452
+ };
18453
+ function numeric(input) {
18454
+ return {
18455
+ ...input,
18456
+ family: "numeric",
18457
+ deviceType: DeviceType.Sensor,
18458
+ memberDeviceType: DeviceType.Sensor,
18459
+ commandCaps: [],
18460
+ baseUnit: baseUnitOf(input.quantity),
18461
+ aggregates: NUMERIC_AGGREGATES,
18462
+ accept: [{
18463
+ cap: input.outputCap,
18464
+ fieldPath: input.outputField
18465
+ }]
18466
+ };
18467
+ }
18468
+ function binary(input) {
18469
+ return {
18470
+ ...input,
18471
+ family: "binary",
18472
+ deviceType: DeviceType.Sensor,
18473
+ memberDeviceType: DeviceType.Sensor,
18474
+ commandCaps: [],
18475
+ quantity: "none",
18476
+ baseUnit: void 0,
18477
+ aggregates: BINARY_AGGREGATES,
18478
+ empty: "offline"
18479
+ };
18480
+ }
18481
+ var GROUP_TEMPLATES = [
18482
+ numeric({
18483
+ id: "temperature",
18484
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18485
+ role: DeviceRole.TemperatureSensor,
18486
+ outputCap: "temperature-sensor",
18487
+ outputField: "celsius",
18488
+ quantity: "temperature",
18489
+ defaultAggregate: "mean",
18490
+ empty: "null",
18491
+ companions: [NOW]
18492
+ }),
18493
+ numeric({
18494
+ id: "humidity",
18495
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18496
+ role: DeviceRole.HumiditySensor,
18497
+ outputCap: "humidity-sensor",
18498
+ outputField: "percent",
18499
+ quantity: "humidity",
18500
+ defaultAggregate: "mean",
18501
+ empty: "null",
18502
+ companions: [NOW]
18503
+ }),
18504
+ numeric({
18505
+ id: "pressure",
18506
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18507
+ role: DeviceRole.PressureSensor,
18508
+ outputCap: "pressure-sensor",
18509
+ outputField: "hpa",
18510
+ quantity: "pressure",
18511
+ defaultAggregate: "mean",
18512
+ empty: "null",
18513
+ companions: [NOW]
18514
+ }),
18515
+ numeric({
18516
+ id: "illuminance",
18517
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18518
+ role: DeviceRole.AmbientLightSensor,
18519
+ outputCap: "ambient-light-sensor",
18520
+ outputField: "lux",
18521
+ quantity: "illuminance",
18522
+ defaultAggregate: "mean",
18523
+ empty: "null",
18524
+ companions: [NOW]
18525
+ }),
18526
+ numeric({
18527
+ id: "power",
18528
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18529
+ role: DeviceRole.PowerSensor,
18530
+ outputCap: "power-meter",
18531
+ outputField: "watts",
18532
+ quantity: "power",
18533
+ defaultAggregate: "sum",
18534
+ empty: "omit",
18535
+ companions: [NOW]
18536
+ }),
18537
+ numeric({
18538
+ id: "energy",
18539
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18540
+ role: DeviceRole.EnergySensor,
18541
+ outputCap: "power-meter",
18542
+ outputField: "kwhTotal",
18543
+ quantity: "energy",
18544
+ defaultAggregate: "sum",
18545
+ empty: "omit",
18546
+ companions: [NOW]
18547
+ }),
18548
+ numeric({
18549
+ id: "battery",
18550
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18551
+ role: DeviceRole.BatterySensor,
18552
+ outputCap: "battery",
18553
+ outputField: "percentage",
18554
+ quantity: "percentage",
18555
+ defaultAggregate: "mean",
18556
+ empty: "null",
18557
+ companions: [
18558
+ {
18559
+ fieldPath: "charging",
18560
+ expr: "\"unknown\""
18561
+ },
18562
+ {
18563
+ fieldPath: "sleeping",
18564
+ expr: "false"
18565
+ },
18566
+ {
18567
+ fieldPath: "lastUpdated",
18568
+ expr: "now"
18569
+ }
18570
+ ]
18571
+ }),
18572
+ numeric({
18573
+ id: "numeric",
18574
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18575
+ role: DeviceRole.NumericSensor,
18576
+ outputCap: "numeric-sensor",
18577
+ outputField: "value",
18578
+ quantity: "device",
18579
+ defaultAggregate: "mean",
18580
+ empty: "null",
18581
+ companions: [NOW]
18582
+ }),
18583
+ binary({
18584
+ id: "motion",
18585
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18586
+ role: DeviceRole.MotionSensor,
18587
+ outputCap: "motion",
18588
+ outputField: "detected",
18589
+ defaultAggregate: "any",
18590
+ accept: [{
18591
+ cap: "motion",
18592
+ fieldPath: "detected"
18593
+ }],
18594
+ companions: [{
18595
+ fieldPath: "lastDetectedAt",
18596
+ expr: "null"
18597
+ }, {
18598
+ fieldPath: "autoClearAfterMs",
18599
+ expr: "null"
18600
+ }]
18601
+ }),
18602
+ binary({
18603
+ id: "contact",
18604
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18605
+ role: DeviceRole.ContactSensor,
18606
+ outputCap: "contact",
18607
+ outputField: "entryOpen",
18608
+ defaultAggregate: "any",
18609
+ accept: [{
18610
+ cap: "contact",
18611
+ fieldPath: "entryOpen"
18612
+ }],
18613
+ companions: [CHANGED]
18614
+ }),
18615
+ binary({
18616
+ id: "leak",
18617
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18618
+ role: DeviceRole.LeakSensor,
18619
+ outputCap: "flood",
18620
+ outputField: "flooded",
18621
+ defaultAggregate: "any",
18622
+ accept: [{
18623
+ cap: "flood",
18624
+ fieldPath: "flooded"
18625
+ }],
18626
+ companions: [CHANGED]
18627
+ }),
18628
+ binary({
18629
+ id: "smoke",
18630
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18631
+ role: DeviceRole.SmokeSensor,
18632
+ outputCap: "smoke",
18633
+ outputField: "detected",
18634
+ defaultAggregate: "any",
18635
+ accept: [{
18636
+ cap: "smoke",
18637
+ fieldPath: "detected"
18638
+ }],
18639
+ companions: [CHANGED]
18640
+ }),
18641
+ binary({
18642
+ id: "occupancy",
18643
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18644
+ role: DeviceRole.BinarySensor,
18645
+ outputCap: "binary",
18646
+ outputField: "on",
18647
+ defaultAggregate: "any",
18648
+ accept: [{
18649
+ cap: "motion",
18650
+ fieldPath: "detected"
18651
+ }, {
18652
+ cap: "binary",
18653
+ fieldPath: "on"
18654
+ }],
18655
+ companions: [CHANGED]
18656
+ }),
18657
+ binary({
18658
+ id: "binary",
18659
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18660
+ role: DeviceRole.BinarySensor,
18661
+ outputCap: "binary",
18662
+ outputField: "on",
18663
+ defaultAggregate: "any",
18664
+ accept: [{
18665
+ cap: "binary",
18666
+ fieldPath: "on"
18667
+ }],
18668
+ companions: [CHANGED]
18669
+ }),
18670
+ actuator({
18671
+ id: "switch",
18672
+ 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.",
18673
+ deviceType: DeviceType.Switch,
18674
+ outputCap: "switch",
18675
+ commandCaps: ["switch"]
18676
+ }),
18677
+ actuator({
18678
+ id: "light",
18679
+ 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.",
18680
+ deviceType: DeviceType.Light,
18681
+ outputCap: "switch",
18682
+ commandCaps: [
18683
+ "switch",
18684
+ "brightness",
18685
+ "color"
18686
+ ]
18687
+ })
18688
+ ];
18689
+ function actuator(input) {
18690
+ return {
18691
+ ...input,
18692
+ family: "actuator",
18693
+ role: void 0,
18694
+ memberDeviceType: input.deviceType,
18695
+ outputField: "on",
18696
+ quantity: "none",
18697
+ baseUnit: void 0,
18698
+ aggregates: BINARY_AGGREGATES,
18699
+ defaultAggregate: "any",
18700
+ empty: "offline",
18701
+ accept: [{
18702
+ cap: "switch",
18703
+ fieldPath: "on"
18704
+ }],
18705
+ companions: [CHANGED]
18706
+ };
18707
+ }
18708
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18709
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18322
18710
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18323
18711
  /**
18324
18712
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -20032,12 +20420,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
20032
20420
  * providers detect renames by matching `uniqueId` and patch `id`
20033
20421
  * in-place so dispatch keeps working.
20034
20422
  *
20035
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
20036
- * `precision`) live in the cap STATUS SLICE so they flow live with every
20037
- * state push and are the single source of truth. The `device_class` tag is
20038
- * an HA-specific input that is consumed at the discovery boundary
20039
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
20040
- * stored after that point.
20423
+ * **Display unit and precision are NOT stored here.** They are a read-time
20424
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20425
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20426
+ * neither is repeated on each sample. The `device_class` tag is an
20427
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20428
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
20041
20429
  *
20042
20430
  * The `raw` escape hatch carries provider-specific attributes that don't
20043
20431
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -20051,6 +20439,14 @@ var SourceInfoSchema = object({
20051
20439
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
20052
20440
  * Used to detect rename when `id` changes. */
20053
20441
  uniqueId: string().optional(),
20442
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20443
+ quantity: ReadingQuantitySchema.optional(),
20444
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20445
+ dateTimeShape: _enum([
20446
+ "date",
20447
+ "time",
20448
+ "datetime"
20449
+ ]).optional(),
20054
20450
  /** Provider-specific extras that don't fit the structured slots.
20055
20451
  * Whitelist what goes here per provider — do NOT dump entire HA
20056
20452
  * attribute blobs (would explode the meta JSON column). */
@@ -40963,7 +41359,8 @@ method(object({
40963
41359
  "enum"
40964
41360
  ]),
40965
41361
  enumValues: array(string()).optional(),
40966
- item: boolean().optional()
41362
+ item: boolean().optional(),
41363
+ quantity: ReadingQuantitySchema.optional()
40967
41364
  })).readonly(),
40968
41365
  itemArray: object({
40969
41366
  path: string(),
@@ -44763,8 +45160,8 @@ var MaskPolygonShapeSchema = object({
44763
45160
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44764
45161
  var MaskGridShapeSchema = object({
44765
45162
  kind: literal("grid"),
44766
- gridWidth: number(),
44767
- gridHeight: number(),
45163
+ gridWidth: number().meta(readingMeta("none")),
45164
+ gridHeight: number().meta(readingMeta("none")),
44768
45165
  cells: array(boolean())
44769
45166
  });
44770
45167
  discriminatedUnion("kind", [
@@ -44876,6 +45273,8 @@ var NcSystemEventKindSchema = _enum([
44876
45273
  "detection-blind",
44877
45274
  "motion-noisy",
44878
45275
  "motion-calm",
45276
+ "package-llm-silent",
45277
+ "package-llm-answered",
44879
45278
  "recording-stalled",
44880
45279
  "recording-healthy",
44881
45280
  "recording-degraded",
@@ -55890,6 +56289,28 @@ var PackageVersionInfoSchema = object({
55890
56289
  deprecated: string().optional(),
55891
56290
  distTags: array(string()).readonly()
55892
56291
  });
56292
+ /**
56293
+ * One published document. `ok` is markdown from the registry. `missing` means
56294
+ * the registry answered and the file was not there. `error` is a registry or
56295
+ * tarball failure — never an empty string standing in for either.
56296
+ */
56297
+ var PackageDocBodySchema = discriminatedUnion("status", [
56298
+ object({
56299
+ status: literal("ok"),
56300
+ markdown: string()
56301
+ }),
56302
+ object({ status: literal("missing") }),
56303
+ object({
56304
+ status: literal("error"),
56305
+ message: string()
56306
+ })
56307
+ ]);
56308
+ var PackageDocsSchema = object({
56309
+ name: string(),
56310
+ version: string(),
56311
+ readme: PackageDocBodySchema,
56312
+ changelog: PackageDocBodySchema
56313
+ });
55893
56314
  var SearchResultSchema = object({
55894
56315
  name: string(),
55895
56316
  version: string(),
@@ -56108,7 +56529,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56108
56529
  }), object({ success: literal(true) }), {
56109
56530
  kind: "mutation",
56110
56531
  auth: "admin"
56111
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
56532
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
56533
+ name: string().min(1),
56534
+ version: string().min(1).optional()
56535
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
56112
56536
  kind: "mutation",
56113
56537
  auth: "admin"
56114
56538
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -56158,46 +56582,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56158
56582
  }), LogStreamEntrySchema, { kind: "subscription" });
56159
56583
  object({
56160
56584
  /** Carbon dioxide concentration in ppm. */
56161
- co2Ppm: number().min(0).optional(),
56585
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
56162
56586
  /** Total volatile organic compounds in ppb. */
56163
- vocPpb: number().min(0).optional(),
56587
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56164
56588
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56165
- pm25: number().min(0).optional(),
56589
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56166
56590
  /** Particulate matter ≤ 10 μm in µg/m³. */
56167
- pm10: number().min(0).optional(),
56591
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
56168
56592
  /** Composite AQI value (typically 0..500). */
56169
- aqi: number().optional(),
56593
+ aqi: number().optional().meta(readingMeta("index")),
56170
56594
  /** Ms epoch when the slice was last updated. */
56171
- lastFetchedAt: number(),
56172
- /** Live display unit of the single metric this slice carries (e.g. HA
56173
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
56174
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
56175
- * per slice is unambiguous. */
56176
- unit: string().optional(),
56177
- /** Suggested decimal places for numeric display.
56178
- * Populated live from the upstream source when provided (e.g. HA
56179
- * `attributes.suggested_display_precision`). Falls back to
56180
- * auto-formatting when absent. */
56181
- precision: number().int().min(0).max(10).optional()
56595
+ lastFetchedAt: number()
56182
56596
  });
56183
56597
  DeviceType.Sensor;
56184
56598
  object({
56185
- /** Current illuminance in lux (lx). */
56186
- lux: number().min(0).meta({
56187
- unit: "lx",
56599
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56600
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
56601
+ precision: 0,
56188
56602
  label: "Illuminance"
56189
- }),
56603
+ })),
56190
56604
  /** Ms epoch when the slice was last updated. */
56191
- lastFetchedAt: number(),
56192
- /** Live display unit from the upstream source (e.g. HA
56193
- * `attributes.unit_of_measurement`). The UI prefers this over the
56194
- * role's canonical unit. Absent → fall back to the canonical unit. */
56195
- unit: string().optional(),
56196
- /** Suggested decimal places for numeric display.
56197
- * Populated live from the upstream source when provided (e.g. HA
56198
- * `attributes.suggested_display_precision`). Falls back to
56199
- * auto-formatting when absent. */
56200
- precision: number().int().min(0).max(10).optional()
56605
+ lastFetchedAt: number()
56201
56606
  });
56202
56607
  DeviceType.Sensor;
56203
56608
  /**
@@ -56342,10 +56747,7 @@ var BatteryStatusSchema = object({
56342
56747
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
56343
56748
  * declines to band a non-finite reading, which is the correct shape.
56344
56749
  */
56345
- percentage: number().min(0).max(100).nullable().meta({
56346
- unit: "%",
56347
- label: "Charge"
56348
- }),
56750
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
56349
56751
  /**
56350
56752
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
56351
56753
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -56372,7 +56774,14 @@ var BatteryStatusSchema = object({
56372
56774
  */
56373
56775
  sleeping: boolean().meta({ label: "Sleeping" }),
56374
56776
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
56375
- lastUpdated: number(),
56777
+ lastUpdated: number().meta(readingMeta("none")),
56778
+ /**
56779
+ * Battery voltage in volts, for a cell the firmware reports as voltage
56780
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
56781
+ * that only report charge. A second reading on this cap, not a second
56782
+ * `numeric-sensor` on the same device.
56783
+ */
56784
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
56376
56785
  /**
56377
56786
  * Ms epoch of the last time the device PROVED it was reachable — a
56378
56787
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -56432,7 +56841,7 @@ object({
56432
56841
  DeviceType.Sensor;
56433
56842
  object({
56434
56843
  /** Current level as 0..100 inclusive. Firmware-reported. */
56435
- percentage: number().min(0).max(100),
56844
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
56436
56845
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
56437
56846
  lastChangedAt: number()
56438
56847
  });
@@ -56827,21 +57236,21 @@ object({
56827
57236
  /** Single setpoint in Celsius. Used by single-target modes
56828
57237
  * (heat / cool / dry). Null when the device is in a range mode
56829
57238
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
56830
- target: number().nullable(),
57239
+ target: number().nullable().meta(readingMeta("temperature")),
56831
57240
  /** Upper bound of the dual setpoint in Celsius. Populated only
56832
57241
  * in `heat_cool` mode. */
56833
- targetHigh: number().nullable(),
57242
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
56834
57243
  /** Lower bound of the dual setpoint in Celsius. Populated only
56835
57244
  * in `heat_cool` mode. */
56836
- targetLow: number().nullable(),
57245
+ targetLow: number().nullable().meta(readingMeta("temperature")),
56837
57246
  /** Target relative humidity (0..100). Null when the device has no
56838
57247
  * humidity control. */
56839
- targetHumidity: number().min(0).max(100).nullable(),
57248
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56840
57249
  /** Current measured temperature in Celsius. Read-only — pushed by
56841
57250
  * the device's internal sensor. */
56842
- currentTemp: number().nullable(),
57251
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
56843
57252
  /** Current measured relative humidity (0..100). Read-only. */
56844
- currentHumidity: number().min(0).max(100).nullable(),
57253
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56845
57254
  /** Vertical louver swing on/off. Null when the device has no vertical
56846
57255
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
56847
57256
  swingVertical: boolean().nullable(),
@@ -56849,11 +57258,11 @@ object({
56849
57258
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
56850
57259
  swingHorizontal: boolean().nullable(),
56851
57260
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
56852
- minTemp: number().nullable().optional(),
57261
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
56853
57262
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
56854
- maxTemp: number().nullable().optional(),
57263
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
56855
57264
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
56856
- tempStep: number().positive().nullable().optional(),
57265
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
56857
57266
  /** Ms epoch when the slice was last updated (push or command). */
56858
57267
  lastFetchedAt: number()
56859
57268
  });
@@ -56932,17 +57341,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
56932
57341
  * modes to expose in the picker without a slice fetch.
56933
57342
  */
56934
57343
  var RgbTripletSchema = object({
56935
- r: number().int().min(0).max(255),
56936
- g: number().int().min(0).max(255),
56937
- b: number().int().min(0).max(255)
57344
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
57345
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
57346
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
56938
57347
  });
56939
57348
  var HsvTripletSchema = object({
56940
57349
  /** Hue in degrees, 0..360. */
56941
- h: number().min(0).max(360),
57350
+ h: number().min(0).max(360).meta(readingMeta("none")),
56942
57351
  /** Saturation as 0..100 inclusive. */
56943
- s: number().min(0).max(100),
57352
+ s: number().min(0).max(100).meta(readingMeta("none")),
56944
57353
  /** Value/brightness as 0..100 inclusive. */
56945
- v: number().min(0).max(100)
57354
+ v: number().min(0).max(100).meta(readingMeta("none"))
56946
57355
  });
56947
57356
  var ColorInputSchema = discriminatedUnion("mode", [
56948
57357
  object({
@@ -56971,7 +57380,7 @@ object({
56971
57380
  /** Populated when `mode === 'hsv'`. */
56972
57381
  hsv: HsvTripletSchema.optional(),
56973
57382
  /** Populated when `mode === 'mired'`. */
56974
- mireds: number().int().optional(),
57383
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
56975
57384
  /** Ms epoch of the last operator-driven change. */
56976
57385
  lastChangedAt: number()
56977
57386
  });
@@ -57095,18 +57504,12 @@ _enum([
57095
57504
  "empty",
57096
57505
  "full"
57097
57506
  ]);
57098
- /** Units a remaining-life counter is reported in. Closed: the panel renders
57099
- * each. */
57100
- var ConsumableRemainingUnitSchema = _enum([
57101
- "days",
57102
- "hours",
57103
- "cycles"
57104
- ]);
57105
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
57106
- * days). `value` null = unknown (D393). */
57507
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
57508
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
57509
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
57107
57510
  var ConsumableRemainingSchema = object({
57108
- value: number().min(0).nullable(),
57109
- unit: ConsumableRemainingUnitSchema
57511
+ value: number().min(0).nullable().meta(readingMeta("device")),
57512
+ kind: ConsumableRemainingKindSchema
57110
57513
  });
57111
57514
  var ConsumablesStatusSchema = object({
57112
57515
  items: array(object({
@@ -57115,7 +57518,7 @@ var ConsumablesStatusSchema = object({
57115
57518
  /** Display name. */
57116
57519
  label: string().min(1),
57117
57520
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
57118
- level: number().min(0).max(100).nullable(),
57521
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57119
57522
  /** The provider's discrete verdict when known (binary mode). */
57120
57523
  status: ConsumableStatusSchema.nullable(),
57121
57524
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -57132,7 +57535,7 @@ var ConsumablesStatusSchema = object({
57132
57535
  })),
57133
57536
  lastChangedAt: number()
57134
57537
  });
57135
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57538
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57136
57539
  Object.values(DeviceType), method(object({
57137
57540
  deviceId: number().int().nonnegative(),
57138
57541
  key: string().min(1)
@@ -57157,39 +57560,17 @@ object({
57157
57560
  /** Current value. Numeric kind → number; select/text/datetime → string.
57158
57561
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
57159
57562
  * or time-only — HA accepts all three forms). */
57160
- value: union([number(), string()]),
57563
+ value: union([number().meta(readingMeta("device")), string()]),
57161
57564
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
57162
57565
  options: array(string()),
57163
57566
  /** Ms epoch when the slice was last updated. */
57164
57567
  lastChangedAt: number(),
57165
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
57166
- * Populated live from HA `attributes.unit_of_measurement` on each push.
57167
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57168
- unit: string().optional(),
57169
- /** Minimum allowed value. Populated live from HA `attributes.min`.
57170
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57171
- min: number().optional(),
57172
- /** Maximum allowed value. Populated live from HA `attributes.max`.
57173
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57174
- max: number().optional(),
57175
- /** Step increment. Populated live from HA `attributes.step`.
57176
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57177
- step: number().optional(),
57178
- /** Suggested decimal places for numeric display. Populated live from HA
57179
- * `attributes.suggested_display_precision`, or derived from `step` when the
57180
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
57181
- * other kinds. Falls back to auto-formatting when absent. */
57182
- precision: number().int().min(0).max(10).optional(),
57183
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
57184
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
57185
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
57186
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
57187
- * `has_date`/`has_time` attributes on each push. */
57188
- format: _enum([
57189
- "date",
57190
- "time",
57191
- "datetime"
57192
- ]).optional()
57568
+ /** Minimum allowed value, in the device quantity's base unit. */
57569
+ min: number().optional().meta(readingMeta("device")),
57570
+ /** Maximum allowed value, in the device quantity's base unit. */
57571
+ max: number().optional().meta(readingMeta("device")),
57572
+ /** Step increment, in the device quantity's base unit. */
57573
+ step: number().optional().meta(readingMeta("device"))
57193
57574
  });
57194
57575
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
57195
57576
  object({
@@ -57227,9 +57608,9 @@ object({
57227
57608
  ]),
57228
57609
  /** 0 = fully closed, 100 = fully open. Null when the device has
57229
57610
  * no intermediate position surface. */
57230
- position: number().min(0).max(100).nullable(),
57611
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57231
57612
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
57232
- tiltPosition: number().min(0).max(100).nullable(),
57613
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57233
57614
  /** Ms epoch when the slice was last updated. */
57234
57615
  lastChangedAt: number()
57235
57616
  });
@@ -57287,9 +57668,9 @@ var NormalizedRangeSchema$1 = object({
57287
57668
  object({
57288
57669
  mode: DayNightModeSchema,
57289
57670
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
57290
- sensitivity: number().optional(),
57671
+ sensitivity: number().optional().meta(readingMeta("none")),
57291
57672
  /** Delay before the IR-cut filter flips, in seconds. */
57292
- switchDelaySec: number().optional(),
57673
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
57293
57674
  lastFetchedAt: number()
57294
57675
  });
57295
57676
  /**
@@ -57444,11 +57825,11 @@ object({
57444
57825
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
57445
57826
  availablePrograms: array(string()),
57446
57827
  /** Seconds remaining on the active cycle, or null when not running / not known. */
57447
- remainingSeconds: number().int().nonnegative().nullable(),
57828
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57448
57829
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
57449
- delayedStartSeconds: number().int().nonnegative().nullable(),
57830
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57450
57831
  /** Cycle completion 0..100, or null when not applicable. */
57451
- progressPercent: number().min(0).max(100).nullable(),
57832
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57452
57833
  /** True when the door is reported open; null when the door state is not known. */
57453
57834
  doorOpen: boolean().nullable(),
57454
57835
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -57472,7 +57853,7 @@ object({
57472
57853
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
57473
57854
  machineCareDue: boolean().nullable(),
57474
57855
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
57475
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
57856
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
57476
57857
  /** Option states: null when the appliance does not expose the option or has not said. */
57477
57858
  halfLoad: boolean().nullable(),
57478
57859
  hygienePlus: boolean().nullable(),
@@ -57487,7 +57868,7 @@ object({
57487
57868
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
57488
57869
  activeEvents: array(string()),
57489
57870
  /** Numeric error code when the appliance reports one; null otherwise. */
57490
- errorCode: number().nullable(),
57871
+ errorCode: number().nullable().meta(readingMeta("none")),
57491
57872
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
57492
57873
  errorLabel: string().nullable(),
57493
57874
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -57633,24 +58014,14 @@ object({
57633
58014
  /** Ms epoch of the last press. null = never observed since this provider started. */
57634
58015
  lastPressedAt: number().nullable(),
57635
58016
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
57636
- pressCountSinceStart: number()
58017
+ pressCountSinceStart: number().meta(readingMeta("count"))
57637
58018
  });
57638
58019
  object({
57639
58020
  deviceId: number(),
57640
58021
  timestamp: number()
57641
58022
  });
57642
58023
  DeviceType.Button, DeviceType.Camera;
57643
- /**
57644
- * Enum-state sensor — a string value picked from a finite option set.
57645
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
57646
- * states, weather conditions, contact-source channels, mode strings).
57647
- * The option set is stable across observations; it is persisted in the
57648
- * device config blob (via the control slice at adoption) and is NOT
57649
- * stored in `sourceInfo`.
57650
- */
57651
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
57652
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
57653
- var EnumSensorDateTimeFormatSchema = _enum([
58024
+ _enum([
57654
58025
  "date",
57655
58026
  "time",
57656
58027
  "datetime"
@@ -57658,12 +58029,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
57658
58029
  object({
57659
58030
  value: string(),
57660
58031
  /**
57661
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
57662
- * as a locale date/time/datetime; absent for plain enum sensors. HA
57663
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
57664
- * a time-only sensor → `'time'`.
58032
+ * Set when `value` is a timestamp rather than an enum state, so history
58033
+ * does not learn ISO strings as states. The display shape (date / time /
58034
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
57665
58035
  */
57666
- format: EnumSensorDateTimeFormatSchema.optional(),
58036
+ timestamp: boolean().optional(),
57667
58037
  /** Ms epoch when the slice was last updated. */
57668
58038
  lastFetchedAt: number()
57669
58039
  });
@@ -57682,13 +58052,13 @@ var EventFireSchema = object({
57682
58052
  deviceId: number(),
57683
58053
  eventType: string(),
57684
58054
  data: record(string(), unknown()).nullable(),
57685
- timestamp: number(),
57686
- seq: number()
58055
+ timestamp: number().meta(readingMeta("none")),
58056
+ seq: number().meta(readingMeta("count"))
57687
58057
  });
57688
58058
  object({
57689
58059
  eventTypes: array(string()),
57690
58060
  lastEvent: EventFireSchema.nullable(),
57691
- eventCountSinceStart: number()
58061
+ eventCountSinceStart: number().meta(readingMeta("count"))
57692
58062
  });
57693
58063
  DeviceType.EventEmitter;
57694
58064
  var IdentitySchema = object({
@@ -58077,10 +58447,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
58077
58447
  object({
58078
58448
  /** Active speed as 0..100 inclusive. Null when the device has no
58079
58449
  * speed surface (single-speed fan). */
58080
- percentage: number().min(0).max(100).nullable(),
58450
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58081
58451
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
58082
58452
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
58083
- percentageStep: number().positive().optional(),
58453
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
58084
58454
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
58085
58455
  * — empty string when no preset is active or supported. */
58086
58456
  preset: string(),
@@ -58162,7 +58532,7 @@ var FeatureProbeStatusSchema = object({
58162
58532
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
58163
58533
  model: string().nullable(),
58164
58534
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
58165
- channelCount: number().nullable(),
58535
+ channelCount: number().nullable().meta(readingMeta("count")),
58166
58536
  /**
58167
58537
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
58168
58538
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -58194,9 +58564,39 @@ object({
58194
58564
  lastChangedAt: number()
58195
58565
  });
58196
58566
  DeviceType.Sensor;
58567
+ object({
58568
+ /** One row per member that has been commanded. A later command replaces that member's row. */
58569
+ results: array(object({
58570
+ deviceId: number().int().nonnegative(),
58571
+ cap: string().min(1),
58572
+ method: string().min(1),
58573
+ ok: boolean(),
58574
+ error: string().optional(),
58575
+ /** Ms epoch of the attempt. */
58576
+ at: number()
58577
+ })) });
58578
+ DeviceType.Switch, DeviceType.Light;
58579
+ object({
58580
+ /** Members whose current value entered the aggregate. */
58581
+ included: number().int().nonnegative().meta(readingMeta("count", {
58582
+ precision: 0,
58583
+ label: "Reporting"
58584
+ })),
58585
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
58586
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
58587
+ precision: 0,
58588
+ label: "Excluded"
58589
+ })),
58590
+ /** `included` + `excluded`. */
58591
+ total: number().int().nonnegative().meta(readingMeta("count", {
58592
+ precision: 0,
58593
+ label: "Members"
58594
+ }))
58595
+ });
58596
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
58197
58597
  object({
58198
58598
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
58199
- position: number().min(0).max(100).nullable(),
58599
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58200
58600
  /** Ms epoch when the slice was last updated (push or poll). */
58201
58601
  lastFetchedAt: number()
58202
58602
  });
@@ -58205,9 +58605,9 @@ object({
58205
58605
  /** Whether the humidifier is currently on. */
58206
58606
  on: boolean(),
58207
58607
  /** Current measured relative humidity (0..100). Null when not reported. */
58208
- currentHumidity: number().min(0).max(100).nullable(),
58608
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58209
58609
  /** Target relative humidity (0..100). Null when no setpoint surface. */
58210
- targetHumidity: number().min(0).max(100).nullable(),
58610
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58211
58611
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
58212
58612
  * device has no mode surface. */
58213
58613
  mode: string().nullable(),
@@ -58217,9 +58617,9 @@ object({
58217
58617
  * `idle` / `off`). Null when the device doesn't report it. */
58218
58618
  action: string().nullable(),
58219
58619
  /** HA `min_humidity` attribute. Null → UI uses 0. */
58220
- minHumidity: number().nullable(),
58620
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
58221
58621
  /** HA `max_humidity` attribute. Null → UI uses 100. */
58222
- maxHumidity: number().nullable(),
58622
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
58223
58623
  /** Ms epoch when the slice was last updated. */
58224
58624
  lastChangedAt: number()
58225
58625
  });
@@ -58243,22 +58643,13 @@ DeviceType.Humidifier, method(object({
58243
58643
  auth: "protected"
58244
58644
  });
58245
58645
  object({
58246
- /** Current relative humidity, 0..100. */
58247
- percent: number().min(0).max(100).meta({
58248
- unit: "%",
58646
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
58647
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
58648
+ precision: 0,
58249
58649
  label: "Humidity"
58250
- }),
58650
+ })),
58251
58651
  /** Ms epoch when the slice was last updated. */
58252
- lastFetchedAt: number(),
58253
- /** Live display unit from the upstream source (e.g. HA
58254
- * `attributes.unit_of_measurement`). The UI prefers this over the
58255
- * role's canonical unit. Absent → fall back to the canonical unit. */
58256
- unit: string().optional(),
58257
- /** Suggested decimal places for numeric display.
58258
- * Populated live from the upstream source when provided (e.g. HA
58259
- * `attributes.suggested_display_precision`). Falls back to
58260
- * auto-formatting when absent. */
58261
- precision: number().int().min(0).max(10).optional()
58652
+ lastFetchedAt: number()
58262
58653
  });
58263
58654
  DeviceType.Sensor, DeviceType.Thermostat;
58264
58655
  object({
@@ -58266,7 +58657,7 @@ object({
58266
58657
  * entity exposes no `entity_picture` (yet). */
58267
58658
  url: string().nullable(),
58268
58659
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
58269
- lastUpdated: number().nullable()
58660
+ lastUpdated: number().nullable().meta(readingMeta("none"))
58270
58661
  });
58271
58662
  DeviceType.Image;
58272
58663
  /**
@@ -58322,19 +58713,19 @@ var NormalizedRangeSchema = object({
58322
58713
  });
58323
58714
  object({
58324
58715
  /** Normalized 0–100. */
58325
- brightness: number().optional(),
58716
+ brightness: number().optional().meta(readingMeta("none")),
58326
58717
  /** Normalized 0–100. */
58327
- contrast: number().optional(),
58718
+ contrast: number().optional().meta(readingMeta("none")),
58328
58719
  /** Normalized 0–100. */
58329
- saturation: number().optional(),
58720
+ saturation: number().optional().meta(readingMeta("none")),
58330
58721
  /** Normalized 0–100. */
58331
- sharpness: number().optional(),
58722
+ sharpness: number().optional().meta(readingMeta("none")),
58332
58723
  mirror: boolean().optional(),
58333
58724
  flip: boolean().optional(),
58334
58725
  rotate: ImageRotateSchema.optional(),
58335
58726
  whiteBalance: WhiteBalanceModeSchema.optional(),
58336
58727
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
58337
- warmth: number().optional(),
58728
+ warmth: number().optional().meta(readingMeta("none")),
58338
58729
  exposureMode: ExposureModeSchema.optional(),
58339
58730
  backlightMode: BacklightModeSchema.optional(),
58340
58731
  lastFetchedAt: number()
@@ -58579,10 +58970,10 @@ var IntercomAbilitySchema = object({
58579
58970
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
58580
58971
  codecs: array(string()),
58581
58972
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
58582
- sampleRate: number(),
58973
+ sampleRate: number().meta(readingMeta("none")),
58583
58974
  duplex: _enum(["half", "full"]),
58584
58975
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
58585
- maxBacklogMs: number()
58976
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
58586
58977
  });
58587
58978
  var IntercomStatusSchema = object({
58588
58979
  /** True while a talk session is open. */
@@ -58693,13 +59084,13 @@ object({
58693
59084
  activity: LawnMowerActivitySchema,
58694
59085
  /** 0..100 battery percentage. Null when the device has no battery
58695
59086
  * reading. */
58696
- batteryLevel: number().min(0).max(100).nullable(),
59087
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58697
59088
  /** 0..100 mowing-completion percentage of the current task, or null when no
58698
59089
  * task is active / progress is unavailable. */
58699
- progressPercent: number().min(0).max(100).nullable(),
59090
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58700
59091
  /** Current device/event code (dynamic — mostly status, sometimes an error),
58701
59092
  * or null when unknown. */
58702
- currentCode: number().nullable(),
59093
+ currentCode: number().nullable().meta(readingMeta("none")),
58703
59094
  /** Human label for {@link currentCode}, or null when undecodable. */
58704
59095
  currentCodeLabel: string().nullable(),
58705
59096
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -59161,7 +59552,7 @@ object({
59161
59552
  state: MediaPlayerStateSchema,
59162
59553
  /** Volume as 0..100 inclusive. Null when the device has no volume
59163
59554
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59164
- volumeLevel: number().min(0).max(100).nullable(),
59555
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59165
59556
  /** Mute toggle distinct from volume=0. Null when no mute surface.
59166
59557
  * Pair with `DeviceFeature.MediaPlayerMute`. */
59167
59558
  isMuted: boolean().nullable(),
@@ -59174,10 +59565,10 @@ object({
59174
59565
  currentMedia: MediaInfoSchema.nullable(),
59175
59566
  /** Current playback position in ms. Null when not seekable or
59176
59567
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59177
- positionMs: number().int().nonnegative().nullable(),
59568
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59178
59569
  /** Total duration of the current media in ms. Null when unknown
59179
59570
  * (live stream) or nothing is playing. */
59180
- durationMs: number().int().nonnegative().nullable(),
59571
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59181
59572
  /** Shuffle toggle. Null when no shuffle surface.
59182
59573
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59183
59574
  shuffle: boolean().nullable(),
@@ -59477,7 +59868,7 @@ object({
59477
59868
  * it, every provider seeded it at boot, and every session left it
59478
59869
  * `detected: true` after its detach.
59479
59870
  */
59480
- autoClearAfterMs: number().nullable()
59871
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
59481
59872
  });
59482
59873
  /**
59483
59874
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -59527,7 +59918,7 @@ var MotionZoneRegionSchema = object({
59527
59918
  });
59528
59919
  object({
59529
59920
  enabled: boolean(),
59530
- sensitivity: number(),
59921
+ sensitivity: number().meta(readingMeta("none")),
59531
59922
  /** Grid region(s). Today exactly one `grid` shape. */
59532
59923
  regions: array(MotionZoneRegionSchema),
59533
59924
  lastFetchedAt: number()
@@ -59831,7 +60222,7 @@ var NavigationStatusSchema = object({
59831
60222
  following: boolean(),
59832
60223
  flash: boolean(),
59833
60224
  lightMode: NavigationLightModeSchema,
59834
- lightLevel: number().min(40).max(100),
60225
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
59835
60226
  /** Ms epoch when the slice was last updated. */
59836
60227
  lastChangedAt: number()
59837
60228
  });
@@ -59877,13 +60268,13 @@ var NetworkLinkStatusSchema = object({
59877
60268
  * one whose reading has not landed must not be drawn at 0 %. Consumers
59878
60269
  * SKIP a null rather than coerce it.
59879
60270
  */
59880
- signalPercent: number().min(0).max(100).nullable(),
60271
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59881
60272
  /** Raw received signal strength in dBm, when the firmware reports one. */
59882
- rssiDbm: number().optional(),
60273
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
59883
60274
  /** Network name of a wireless link, when the firmware reports it. */
59884
60275
  ssid: string().optional(),
59885
60276
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
59886
- lastUpdated: number()
60277
+ lastUpdated: number().meta(readingMeta("none"))
59887
60278
  });
59888
60279
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
59889
60280
  deviceId: number(),
@@ -60202,7 +60593,7 @@ object({
60202
60593
  lastError: string().nullable(),
60203
60594
  /** Number of deliveries currently buffered server-side (rate-limited
60204
60595
  * notifiers may queue). 0 when the provider sends synchronously. */
60205
- queueDepth: number().int().nonnegative(),
60596
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
60206
60597
  /** Per-feature capability matrix — authoritative source for the
60207
60598
  * compose-form field gating. */
60208
60599
  supports: NotifierSupportsSchema
@@ -60252,15 +60643,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
60252
60643
  acceptedAt: number()
60253
60644
  });
60254
60645
  object({
60255
- value: number(),
60256
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
60257
- * Populated live from the upstream source on each state push.
60258
- * Absent for unitless sensors. */
60259
- unit: string().optional(),
60260
- /** Suggested decimal places for numeric display.
60261
- * Populated live from the upstream source when provided.
60262
- * Falls back to auto-formatting when absent. */
60263
- precision: number().int().min(0).max(10).optional(),
60646
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
60647
+ value: number().nullable().meta(readingMeta("device")),
60264
60648
  /** Ms epoch when the slice was last updated. */
60265
60649
  lastFetchedAt: number()
60266
60650
  });
@@ -60550,23 +60934,26 @@ object({
60550
60934
  /** Food currently in the bowl (grams). Null when the device has not
60551
60935
  * reported a reading yet. On dual-hopper models this is the combined
60552
60936
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
60553
- foodLevel: number().nullable(),
60937
+ foodLevel: number().nullable().meta(readingMeta("none")),
60554
60938
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
60555
60939
  * single-hopper models. */
60556
- food1: number().nullable(),
60940
+ food1: number().nullable().meta(readingMeta("none")),
60557
60941
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
60558
60942
  * single-hopper models. */
60559
- food2: number().nullable(),
60943
+ food2: number().nullable().meta(readingMeta("none")),
60560
60944
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
60561
60945
  * (`device_class: problem`, on = low). True when the bowl is empty /
60562
60946
  * below the feeder's low threshold. */
60563
60947
  lowFood: boolean(),
60564
60948
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
60565
60949
  * device has no battery reading. */
60566
- batteryPower: number().min(0).max(100).nullable(),
60567
- /** Days of desiccant life remaining. Null when the model has no
60568
- * desiccant sensor. */
60569
- desiccantLeftDays: number().nullable(),
60950
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60951
+ /**
60952
+ * Desiccant life remaining, stored in seconds (duration base, D944).
60953
+ * Providers convert from the days the feeder reports. Null when the
60954
+ * model has no desiccant sensor.
60955
+ */
60956
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
60570
60957
  /** True while a feed is in progress. */
60571
60958
  feeding: boolean(),
60572
60959
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -60577,7 +60964,7 @@ object({
60577
60964
  * with `errorCode` for consumers that want the raw integer. */
60578
60965
  error: string().nullable(),
60579
60966
  /** Raw device error code (0 / null = no error). */
60580
- errorCode: number().nullable(),
60967
+ errorCode: number().nullable().meta(readingMeta("none")),
60581
60968
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
60582
60969
  isDualHopper: boolean(),
60583
60970
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -60587,7 +60974,7 @@ object({
60587
60974
  /** Play a chime when dispensing. */
60588
60975
  feedSound: boolean(),
60589
60976
  /** Speaker / prompt volume level (device-scaled integer). */
60590
- volume: number(),
60977
+ volume: number().meta(readingMeta("none")),
60591
60978
  /** Ms epoch when the slice was last refreshed from the cloud. */
60592
60979
  lastFetchedAt: number()
60593
60980
  });
@@ -61024,36 +61411,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
61024
61411
  });
61025
61412
  object({
61026
61413
  /** Instantaneous power draw in watts. */
61027
- watts: number().optional().meta({
61028
- unit: "W",
61029
- label: "Power"
61030
- }),
61414
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
61031
61415
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61032
- kwhTotal: number().optional().meta({
61033
- unit: "kWh",
61034
- label: "Energy"
61035
- }),
61416
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61036
61417
  /** Voltage in volts. */
61037
- volts: number().optional(),
61418
+ volts: number().optional().meta(readingMeta("voltage")),
61038
61419
  /** Current in amperes. */
61039
- amps: number().optional(),
61040
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
61041
- va: number().optional(),
61420
+ amps: number().optional().meta(readingMeta("current")),
61421
+ /** Apparent power in volt-amperes. */
61422
+ va: number().optional().meta(readingMeta("apparent-power")),
61042
61423
  /** Power factor (0..1) if reported. */
61043
- powerFactor: number().min(0).max(1).optional(),
61424
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
61044
61425
  /** Ms epoch when the slice was last updated. */
61045
- lastFetchedAt: number(),
61046
- /** Live display unit of the single metric this slice carries (e.g. HA
61047
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
61048
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
61049
- * unit per slice is unambiguous. The UI prefers this over the role's
61050
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
61051
- unit: string().optional(),
61052
- /** Suggested decimal places for numeric display.
61053
- * Populated live from the upstream source when provided (e.g. HA
61054
- * `attributes.suggested_display_precision`). Falls back to
61055
- * auto-formatting when absent. */
61056
- precision: number().int().min(0).max(10).optional()
61426
+ lastFetchedAt: number()
61057
61427
  });
61058
61428
  DeviceType.Sensor;
61059
61429
  /**
@@ -61074,11 +61444,11 @@ DeviceType.Sensor;
61074
61444
  */
61075
61445
  var GpsLocationSchema = object({
61076
61446
  /** Latitude in decimal degrees, -90..90. */
61077
- latitude: number().min(-90).max(90),
61447
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
61078
61448
  /** Longitude in decimal degrees, -180..180. */
61079
- longitude: number().min(-180).max(180),
61449
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
61080
61450
  /** Reported accuracy in meters (lower = better). */
61081
- accuracyMeters: number().nonnegative()
61451
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
61082
61452
  });
61083
61453
  object({
61084
61454
  /** `home` / `not_home` / any user-defined zone name. */
@@ -61090,25 +61460,16 @@ object({
61090
61460
  gps: GpsLocationSchema.nullable(),
61091
61461
  /** Optional battery level of the tracking device (0..100). Null
61092
61462
  * when not reported. */
61093
- batteryPercent: number().min(0).max(100).nullable(),
61463
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61094
61464
  /** Ms epoch when the slice was last updated. */
61095
61465
  lastChangedAt: number()
61096
61466
  });
61097
61467
  DeviceType.Presence;
61098
61468
  object({
61099
- /** Current pressure in hPa. */
61100
- hpa: number(),
61469
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
61470
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
61101
61471
  /** Ms epoch when the slice was last updated. */
61102
- lastFetchedAt: number(),
61103
- /** Live display unit from the upstream source (e.g. HA
61104
- * `attributes.unit_of_measurement`). The UI prefers this over the
61105
- * role's canonical unit. Absent → fall back to the canonical unit. */
61106
- unit: string().optional(),
61107
- /** Suggested decimal places for numeric display.
61108
- * Populated live from the upstream source when provided (e.g. HA
61109
- * `attributes.suggested_display_precision`). Falls back to
61110
- * auto-formatting when absent. */
61111
- precision: number().int().min(0).max(10).optional()
61472
+ lastFetchedAt: number()
61112
61473
  });
61113
61474
  DeviceType.Sensor;
61114
61475
  /**
@@ -61441,8 +61802,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
61441
61802
  }), _void(), { kind: "mutation" });
61442
61803
  var PtzAutotrackSettingsSchema = object({
61443
61804
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
61444
- stopDelaySeconds: number().int().min(0).max(300),
61445
- disappearDelaySeconds: number().int().min(0).max(300)
61805
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
61806
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
61446
61807
  });
61447
61808
  /**
61448
61809
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -61499,7 +61860,7 @@ object({
61499
61860
  /** null = unknown — never coerced to false (D393). */
61500
61861
  running: boolean().nullable(),
61501
61862
  /** Epoch ms the current boolean began; null while `running` is null. */
61502
- since: number().nullable(),
61863
+ since: number().nullable().meta(readingMeta("none")),
61503
61864
  source: RunningStateSourceSchema.meta({ label: "Source" }),
61504
61865
  phase: string().nullable(),
61505
61866
  /** null until a reading has been published (D393). */
@@ -62697,9 +63058,9 @@ var RecordTriggerSchema = _enum([
62697
63058
  */
62698
63059
  var RecordWindowSchema = object({
62699
63060
  trigger: RecordTriggerSchema,
62700
- day: number().int().min(0).max(6),
62701
- startMinute: number().int().min(0).max(1439),
62702
- endMinute: number().int().min(1).max(1440)
63061
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
63062
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
63063
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
62703
63064
  });
62704
63065
  /** Status of one physical volume, as the camera itself describes it. */
62705
63066
  var OnboardStorageVolumeSchema = object({
@@ -62768,10 +63129,10 @@ object({
62768
63129
  /** Master "record to the card at all" switch. */
62769
63130
  enabled: boolean().nullable(),
62770
63131
  overwriteWhenFull: boolean().nullable(),
62771
- preRecordSec: number().nullable(),
62772
- postRecordSec: number().nullable(),
63132
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
63133
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
62773
63134
  /** Length of one recorded file, in minutes. */
62774
- segmentMinutes: number().nullable(),
63135
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
62775
63136
  /** The primary track's weekly windows, flattened. */
62776
63137
  windows: array(RecordWindowSchema),
62777
63138
  /**
@@ -62787,7 +63148,7 @@ object({
62787
63148
  * provider reporting a non-zero count also reports the schedule as not
62788
63149
  * writable.
62789
63150
  */
62790
- unreadableWindows: number(),
63151
+ unreadableWindows: number().meta(readingMeta("count")),
62791
63152
  /**
62792
63153
  * The camera is scheduled to record and has NO usable storage.
62793
63154
  *
@@ -63424,7 +63785,7 @@ var CamStreamDescriptorSchema = object({
63424
63785
  url: string().optional(),
63425
63786
  codec: string().optional(),
63426
63787
  resolution: CamStreamResolutionSchema.optional(),
63427
- fps: number().positive().optional(),
63788
+ fps: number().positive().optional().meta(readingMeta("rate")),
63428
63789
  label: string().optional(),
63429
63790
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
63430
63791
  deviceFeatures: array(string()).optional(),
@@ -63454,18 +63815,18 @@ var StreamProfileSchema = _enum([
63454
63815
  ]);
63455
63816
  /** The current encoding config of one stream profile. */
63456
63817
  var StreamProfileConfigSchema = object({
63457
- width: number(),
63458
- height: number(),
63818
+ width: number().meta(readingMeta("none")),
63819
+ height: number().meta(readingMeta("none")),
63459
63820
  codec: _enum(["h264", "h265"]),
63460
- framerate: number(),
63461
- bitrate: number(),
63821
+ framerate: number().meta(readingMeta("rate")),
63822
+ bitrate: number().meta(readingMeta("bitrate")),
63462
63823
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
63463
63824
  encoderProfile: _enum([
63464
63825
  "high",
63465
63826
  "main",
63466
63827
  "baseline"
63467
63828
  ]).optional(),
63468
- gop: number().optional(),
63829
+ gop: number().optional().meta(readingMeta("none")),
63469
63830
  /**
63470
63831
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
63471
63832
  *
@@ -64108,23 +64469,13 @@ object({
64108
64469
  });
64109
64470
  DeviceType.Sensor;
64110
64471
  object({
64111
- /** Current temperature in Celsius. */
64112
- celsius: number().meta({
64113
- unit: "°C",
64472
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64473
+ celsius: number().nullable().meta(readingMeta("temperature", {
64474
+ precision: 1,
64114
64475
  label: "Temperature"
64115
- }),
64476
+ })),
64116
64477
  /** Ms epoch when the slice was last updated (push or poll). */
64117
- lastFetchedAt: number(),
64118
- /** Live display unit from the upstream source (e.g. HA
64119
- * `attributes.unit_of_measurement`). The UI prefers this over the
64120
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
64121
- * Absent → the UI falls back to the role's canonical unit. */
64122
- unit: string().optional(),
64123
- /** Suggested decimal places for numeric display.
64124
- * Populated live from the upstream source when provided (e.g. HA
64125
- * `attributes.suggested_display_precision`). Falls back to
64126
- * auto-formatting when absent. */
64127
- precision: number().int().min(0).max(10).optional()
64478
+ lastFetchedAt: number()
64128
64479
  });
64129
64480
  DeviceType.Sensor, DeviceType.Thermostat;
64130
64481
  /**
@@ -64376,16 +64727,16 @@ object({
64376
64727
  ]),
64377
64728
  /** 0..100 battery percentage. Null when the device has no battery
64378
64729
  * reading. */
64379
- batteryLevel: number().min(0).max(100).nullable(),
64730
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64380
64731
  /** Current fan-speed token (provider-verbatim). Null when unknown or
64381
64732
  * the vacuum has no speed control. */
64382
64733
  fanSpeed: string().nullable(),
64383
64734
  /** Speed tokens the hardware accepts — drives the UI selector. */
64384
64735
  availableFanSpeeds: array(string()),
64385
64736
  /** 0..100 cleaning-completion percentage of the current task, or null. */
64386
- progressPercent: number().min(0).max(100).nullable(),
64737
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64387
64738
  /** Current error code (0 / null = no error). */
64388
- errorCode: number().nullable(),
64739
+ errorCode: number().nullable().meta(readingMeta("none")),
64389
64740
  /** Human label for {@link errorCode}, or null when none / undecodable. */
64390
64741
  errorLabel: string().nullable(),
64391
64742
  /** Ms epoch when the slice was last updated. */
@@ -64424,7 +64775,7 @@ object({
64424
64775
  ]),
64425
64776
  /** 0 = fully closed, 100 = fully open. Null when the device has no
64426
64777
  * intermediate position surface. */
64427
- position: number().min(0).max(100).nullable(),
64778
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64428
64779
  /** Ms epoch when the slice was last updated. */
64429
64780
  lastChangedAt: number()
64430
64781
  });
@@ -64452,9 +64803,9 @@ object({
64452
64803
  DeviceType.Sensor;
64453
64804
  object({
64454
64805
  /** Current measured temperature. Null when not reported. */
64455
- currentTemp: number().nullable(),
64806
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
64456
64807
  /** Target temperature setpoint. Null when no setpoint surface. */
64457
- targetTemp: number().nullable(),
64808
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
64458
64809
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
64459
64810
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
64460
64811
  * device reports an unknown state. */
@@ -64465,9 +64816,9 @@ object({
64465
64816
  * has no away surface. */
64466
64817
  away: boolean().nullable(),
64467
64818
  /** HA `min_temp` attribute. Null when not reported. */
64468
- minTemp: number().nullable(),
64819
+ minTemp: number().nullable().meta(readingMeta("temperature")),
64469
64820
  /** HA `max_temp` attribute. Null when not reported. */
64470
- maxTemp: number().nullable(),
64821
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
64471
64822
  /** Ms epoch when the slice was last updated. */
64472
64823
  lastChangedAt: number()
64473
64824
  });
@@ -64491,25 +64842,18 @@ DeviceType.WaterHeater, method(object({
64491
64842
  auth: "protected"
64492
64843
  });
64493
64844
  object({
64494
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
64495
- * when no condition has been reported yet. */
64845
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
64496
64846
  condition: string().nullable(),
64497
- /** Current temperature in the reported unit. Null when not provided. */
64498
- temperature: number().nullable(),
64499
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
64500
- temperatureUnit: string().nullable(),
64847
+ /** Current temperature in Celsius. Null when not provided or refused. */
64848
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
64501
64849
  /** Relative humidity (0..100). Null when not provided. */
64502
- humidity: number().min(0).max(100).nullable(),
64503
- /** Barometric pressure in the reported unit. Null when not provided. */
64504
- pressure: number().nullable(),
64505
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
64506
- pressureUnit: string().nullable(),
64507
- /** Wind speed in the reported unit. Null when not provided. */
64508
- windSpeed: number().nullable(),
64509
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
64510
- windSpeedUnit: string().nullable(),
64850
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
64851
+ /** Barometric pressure in hPa. Null when not provided or refused. */
64852
+ pressure: number().nullable().meta(readingMeta("pressure")),
64853
+ /** Wind speed in metres per second. Null when not provided or refused. */
64854
+ windSpeed: number().nullable().meta(readingMeta("speed")),
64511
64855
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
64512
- windBearing: number().nullable(),
64856
+ windBearing: number().nullable().meta(readingMeta("angle")),
64513
64857
  /** Ms epoch when the slice was last updated. */
64514
64858
  lastFetchedAt: number()
64515
64859
  });
@@ -65421,6 +65765,13 @@ Object.freeze({
65421
65765
  access: "view",
65422
65766
  role: "admin"
65423
65767
  },
65768
+ "addons.getPackageDocs": {
65769
+ capName: "addons",
65770
+ capScope: "system",
65771
+ addonId: null,
65772
+ access: "view",
65773
+ role: "admin"
65774
+ },
65424
65775
  "addons.getVersions": {
65425
65776
  capName: "addons",
65426
65777
  capScope: "system",
@@ -68529,6 +68880,20 @@ Object.freeze({
68529
68880
  access: "view",
68530
68881
  role: "viewer"
68531
68882
  },
68883
+ "groupCommands.getStatus": {
68884
+ capName: "group-commands",
68885
+ capScope: "device",
68886
+ addonId: null,
68887
+ access: "view",
68888
+ role: "viewer"
68889
+ },
68890
+ "groupStatus.getStatus": {
68891
+ capName: "group-status",
68892
+ capScope: "device",
68893
+ addonId: null,
68894
+ access: "view",
68895
+ role: "viewer"
68896
+ },
68532
68897
  "heatingValve.getStatus": {
68533
68898
  capName: "heating-valve",
68534
68899
  capScope: "device",
@@ -78351,6 +78716,37 @@ var SENSOR_CAP_VOCABULARY = {
78351
78716
  icon: "flame"
78352
78717
  }]
78353
78718
  },
78719
+ "group-commands": {
78720
+ scope: "device",
78721
+ noun: t("Group command", "Comando di gruppo")
78722
+ },
78723
+ "group-status": {
78724
+ scope: "device",
78725
+ noun: t("Group", "Gruppo"),
78726
+ state: [
78727
+ {
78728
+ kind: "numeric",
78729
+ field: "included",
78730
+ quantity: t("the members reporting", "i membri che rispondono"),
78731
+ label: t("Reporting", "Rispondono"),
78732
+ icon: "users"
78733
+ },
78734
+ {
78735
+ kind: "numeric",
78736
+ field: "excluded",
78737
+ quantity: t("the members left out", "i membri esclusi"),
78738
+ label: t("Excluded", "Esclusi"),
78739
+ icon: "users"
78740
+ },
78741
+ {
78742
+ kind: "numeric",
78743
+ field: "total",
78744
+ quantity: t("the member count", "il numero di membri"),
78745
+ label: t("Members", "Membri"),
78746
+ icon: "sigma"
78747
+ }
78748
+ ]
78749
+ },
78354
78750
  "humidity-sensor": {
78355
78751
  scope: "device",
78356
78752
  noun: t("Humidity", "Umidità"),
@@ -78384,7 +78780,6 @@ var SENSOR_CAP_VOCABULARY = {
78384
78780
  field: "value",
78385
78781
  quantity: t("the reading", "il valore"),
78386
78782
  label: t("Reading", "Valore"),
78387
- unitField: "unit",
78388
78783
  icon: "gauge"
78389
78784
  }]
78390
78785
  },
@@ -78520,7 +78915,7 @@ var SENSOR_CAP_VOCABULARY = {
78520
78915
  field: "temperature",
78521
78916
  quantity: t("the outdoor temperature", "la temperatura esterna"),
78522
78917
  label: t("Temperature", "Temperatura"),
78523
- unitField: "temperatureUnit",
78918
+ unit: "°C",
78524
78919
  icon: "thermometer"
78525
78920
  },
78526
78921
  {
@@ -78536,7 +78931,7 @@ var SENSOR_CAP_VOCABULARY = {
78536
78931
  field: "windSpeed",
78537
78932
  quantity: t("the wind speed", "la velocità del vento"),
78538
78933
  label: t("Wind", "Vento"),
78539
- unitField: "windSpeedUnit",
78934
+ unit: "m/s",
78540
78935
  icon: "wind"
78541
78936
  },
78542
78937
  {
@@ -78544,7 +78939,7 @@ var SENSOR_CAP_VOCABULARY = {
78544
78939
  field: "pressure",
78545
78940
  quantity: t("the air pressure", "la pressione atmosferica"),
78546
78941
  label: t("Pressure", "Pressione"),
78547
- unitField: "pressureUnit",
78942
+ unit: "hPa",
78548
78943
  icon: "gauge"
78549
78944
  }
78550
78945
  ]
@@ -79817,6 +80212,16 @@ Object.freeze({
79817
80212
  form: "single",
79818
80213
  optional: false
79819
80214
  }],
80215
+ "groupCommands.getStatus": [{
80216
+ name: "deviceId",
80217
+ form: "single",
80218
+ optional: false
80219
+ }],
80220
+ "groupStatus.getStatus": [{
80221
+ name: "deviceId",
80222
+ form: "single",
80223
+ optional: false
80224
+ }],
79820
80225
  "heatingValve.getStatus": [{
79821
80226
  name: "deviceId",
79822
80227
  form: "single",
@@ -81681,6 +82086,11 @@ var PulseOverrideDraftSchema = object({
81681
82086
  targetId: string().min(1),
81682
82087
  enabled: boolean(),
81683
82088
  debounceMs: number().nonnegative(),
82089
+ edges: array(_enum([
82090
+ "rising",
82091
+ "button",
82092
+ "native-event"
82093
+ ])).min(1).optional(),
81684
82094
  sources: array(OverrideSourceSchema).min(1)
81685
82095
  });
81686
82096
  var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
@@ -81796,6 +82206,8 @@ new Map(Object.entries({
81796
82206
  "detection-blind": "device-events",
81797
82207
  "motion-noisy": "device-events",
81798
82208
  "motion-calm": "device-events",
82209
+ "package-llm-silent": "device-events",
82210
+ "package-llm-answered": "device-events",
81799
82211
  "recording-stalled": "device-events",
81800
82212
  "recording-healthy": "device-events",
81801
82213
  "recording-degraded": "device-events",
@@ -81879,6 +82291,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
81879
82291
  label: "Motion without a subject",
81880
82292
  kinds: ["motion-noisy", "motion-calm"]
81881
82293
  },
82294
+ {
82295
+ id: "package-llm",
82296
+ family: "device-events",
82297
+ label: "Package confirm",
82298
+ kinds: ["package-llm-silent", "package-llm-answered"]
82299
+ },
81882
82300
  {
81883
82301
  id: "recording",
81884
82302
  family: "device-events",
@@ -82060,6 +82478,8 @@ var DEVICE_EVENTS = [
82060
82478
  "detection-blind",
82061
82479
  "motion-noisy",
82062
82480
  "motion-calm",
82481
+ "package-llm-silent",
82482
+ "package-llm-answered",
82063
82483
  "recording-stalled",
82064
82484
  "recording-healthy",
82065
82485
  "recording-degraded",
@@ -82328,6 +82748,15 @@ new Map([
82328
82748
  snapshotLabel: "Cameras with constant motion now",
82329
82749
  snapshotSection: null
82330
82750
  },
82751
+ {
82752
+ id: "package-llm-confirm",
82753
+ family: "device-events",
82754
+ label: "Package confirm (not answering / answering again)",
82755
+ bad: ["package-llm-silent"],
82756
+ recovery: ["package-llm-answered"],
82757
+ snapshotLabel: "Cameras whose package confirm is not answering",
82758
+ snapshotSection: null
82759
+ },
82331
82760
  {
82332
82761
  id: "recording-health",
82333
82762
  family: "device-events",