@camstack/addon-mqtt-broker 1.2.145 → 1.2.146

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.
@@ -39,7 +39,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
40
40
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
41
41
  //#endregion
42
- //#region ../types/dist/event-category-DHXxB8t9.mjs
42
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
43
43
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
44
44
  EventCategory["SystemBoot"] = "system.boot";
45
45
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -365,6 +365,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
365
365
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
366
366
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
367
367
  /**
368
+ * A camera's package LLM confirm has failed closed three times in a row
369
+ * (D931, 2026-10-26). One edge per camera until
370
+ * {@link PipelinePackageLlmAnswered}.
371
+ */
372
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
373
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
374
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
375
+ /**
368
376
  * Per-camera pipeline config was mutated by the orchestrator
369
377
  * (a settings change via `setAgentInferenceDevices` /
370
378
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5607,7 +5615,7 @@ var ZodIssueCode = {
5607
5615
  var ZodFirstPartyTypeKind;
5608
5616
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5609
5617
  //#endregion
5610
- //#region ../types/dist/sleep-BErAxhul.mjs
5618
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5611
5619
  /**
5612
5620
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5613
5621
  * window to float samples (D455).
@@ -8349,6 +8357,99 @@ function defineEntryKind(name, spec) {
8349
8357
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8350
8358
  return defined;
8351
8359
  }
8360
+ var READING_QUANTITIES = [
8361
+ "temperature",
8362
+ "pressure",
8363
+ "speed",
8364
+ "precipitation",
8365
+ "precipitation-rate",
8366
+ "length",
8367
+ "area",
8368
+ "illuminance",
8369
+ "irradiance",
8370
+ "power",
8371
+ "energy",
8372
+ "voltage",
8373
+ "current",
8374
+ "apparent-power",
8375
+ "humidity",
8376
+ "percentage",
8377
+ "concentration-volume",
8378
+ "concentration-mass",
8379
+ "concentration-ppb",
8380
+ "volume",
8381
+ "duration",
8382
+ "duration-ms",
8383
+ "information",
8384
+ "sound-level",
8385
+ "signal-level",
8386
+ "colour-temperature",
8387
+ "angle",
8388
+ "bitrate",
8389
+ "rate",
8390
+ "mass",
8391
+ "index",
8392
+ "ratio",
8393
+ "count",
8394
+ "none",
8395
+ "device"
8396
+ ];
8397
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8398
+ new Set(READING_QUANTITIES);
8399
+ /**
8400
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8401
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8402
+ */
8403
+ var QUANTITY_BASE = {
8404
+ temperature: "°C",
8405
+ pressure: "hPa",
8406
+ speed: "m/s",
8407
+ precipitation: "mm",
8408
+ "precipitation-rate": "mm/h",
8409
+ length: "m",
8410
+ area: "m²",
8411
+ illuminance: "lx",
8412
+ irradiance: "W/m²",
8413
+ power: "W",
8414
+ energy: "kWh",
8415
+ voltage: "V",
8416
+ current: "A",
8417
+ "apparent-power": "VA",
8418
+ humidity: "%",
8419
+ percentage: "%",
8420
+ "concentration-volume": "ppm",
8421
+ "concentration-mass": "µg/m³",
8422
+ "concentration-ppb": "ppb",
8423
+ volume: "L",
8424
+ duration: "s",
8425
+ "duration-ms": "ms",
8426
+ information: "B",
8427
+ "sound-level": "dBFS",
8428
+ "signal-level": "dBm",
8429
+ "colour-temperature": "mired",
8430
+ angle: "°",
8431
+ bitrate: "kbps",
8432
+ rate: "fps",
8433
+ mass: "kg",
8434
+ index: "none",
8435
+ ratio: "none",
8436
+ count: "none",
8437
+ none: "none",
8438
+ device: "device"
8439
+ };
8440
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8441
+ function baseUnitOf(quantity) {
8442
+ const symbol = QUANTITY_BASE[quantity];
8443
+ if (symbol === "none" || symbol === "device") return void 0;
8444
+ return symbol;
8445
+ }
8446
+ function readingMeta(quantity, extras) {
8447
+ return {
8448
+ quantity,
8449
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8450
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8451
+ };
8452
+ }
8352
8453
  /** Shared Zod schemas used across streaming capabilities. */
8353
8454
  var CamProfileSchema = _enum([
8354
8455
  "high",
@@ -8376,8 +8477,8 @@ var CamStreamKindSchema = _enum([
8376
8477
  "derived"
8377
8478
  ]);
8378
8479
  var CamStreamResolutionSchema = object({
8379
- width: number().int().positive(),
8380
- height: number().int().positive()
8480
+ width: number().int().positive().meta(readingMeta("none")),
8481
+ height: number().int().positive().meta(readingMeta("none"))
8381
8482
  });
8382
8483
  var CameraStreamSchema = object({
8383
8484
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8891,6 +8992,8 @@ var CAP_NAMES = Object.freeze([
8891
8992
  "filesystem-browse",
8892
8993
  "flood",
8893
8994
  "gas",
8995
+ "group-commands",
8996
+ "group-status",
8894
8997
  "heating-valve",
8895
8998
  "humidifier",
8896
8999
  "humidity-sensor",
@@ -9020,14 +9123,6 @@ Object.freeze({
9020
9123
  {
9021
9124
  "path": "aqi",
9022
9125
  "valueType": "number"
9023
- },
9024
- {
9025
- "path": "unit",
9026
- "valueType": "string"
9027
- },
9028
- {
9029
- "path": "precision",
9030
- "valueType": "number"
9031
9126
  }
9032
9127
  ],
9033
9128
  "alarm-panel": [
@@ -9063,20 +9158,10 @@ Object.freeze({
9063
9158
  "valueType": "boolean"
9064
9159
  }
9065
9160
  ],
9066
- "ambient-light-sensor": [
9067
- {
9068
- "path": "lux",
9069
- "valueType": "number"
9070
- },
9071
- {
9072
- "path": "unit",
9073
- "valueType": "string"
9074
- },
9075
- {
9076
- "path": "precision",
9077
- "valueType": "number"
9078
- }
9079
- ],
9161
+ "ambient-light-sensor": [{
9162
+ "path": "lux",
9163
+ "valueType": "number"
9164
+ }],
9080
9165
  "audio-metrics": [
9081
9166
  {
9082
9167
  "path": "ts",
@@ -9156,6 +9241,10 @@ Object.freeze({
9156
9241
  "path": "lastUpdated",
9157
9242
  "valueType": "number"
9158
9243
  },
9244
+ {
9245
+ "path": "volts",
9246
+ "valueType": "number"
9247
+ },
9159
9248
  {
9160
9249
  "path": "lastContactAt",
9161
9250
  "valueType": "number"
@@ -9398,10 +9487,6 @@ Object.freeze({
9398
9487
  "datetime"
9399
9488
  ]
9400
9489
  },
9401
- {
9402
- "path": "unit",
9403
- "valueType": "string"
9404
- },
9405
9490
  {
9406
9491
  "path": "min",
9407
9492
  "valueType": "number"
@@ -9413,19 +9498,6 @@ Object.freeze({
9413
9498
  {
9414
9499
  "path": "step",
9415
9500
  "valueType": "number"
9416
- },
9417
- {
9418
- "path": "precision",
9419
- "valueType": "number"
9420
- },
9421
- {
9422
- "path": "format",
9423
- "valueType": "enum",
9424
- "enumValues": [
9425
- "date",
9426
- "time",
9427
- "datetime"
9428
- ]
9429
9501
  }
9430
9502
  ],
9431
9503
  "cover": [
@@ -9666,13 +9738,8 @@ Object.freeze({
9666
9738
  "path": "value",
9667
9739
  "valueType": "string"
9668
9740
  }, {
9669
- "path": "format",
9670
- "valueType": "enum",
9671
- "enumValues": [
9672
- "date",
9673
- "time",
9674
- "datetime"
9675
- ]
9741
+ "path": "timestamp",
9742
+ "valueType": "boolean"
9676
9743
  }],
9677
9744
  "event-emitter": [
9678
9745
  {
@@ -9745,6 +9812,20 @@ Object.freeze({
9745
9812
  "path": "detected",
9746
9813
  "valueType": "boolean"
9747
9814
  }],
9815
+ "group-status": [
9816
+ {
9817
+ "path": "included",
9818
+ "valueType": "number"
9819
+ },
9820
+ {
9821
+ "path": "excluded",
9822
+ "valueType": "number"
9823
+ },
9824
+ {
9825
+ "path": "total",
9826
+ "valueType": "number"
9827
+ }
9828
+ ],
9748
9829
  "heating-valve": [{
9749
9830
  "path": "position",
9750
9831
  "valueType": "number"
@@ -9779,20 +9860,10 @@ Object.freeze({
9779
9860
  "valueType": "number"
9780
9861
  }
9781
9862
  ],
9782
- "humidity-sensor": [
9783
- {
9784
- "path": "percent",
9785
- "valueType": "number"
9786
- },
9787
- {
9788
- "path": "unit",
9789
- "valueType": "string"
9790
- },
9791
- {
9792
- "path": "precision",
9793
- "valueType": "number"
9794
- }
9795
- ],
9863
+ "humidity-sensor": [{
9864
+ "path": "percent",
9865
+ "valueType": "number"
9866
+ }],
9796
9867
  "image": [{
9797
9868
  "path": "url",
9798
9869
  "valueType": "string"
@@ -10131,20 +10202,10 @@ Object.freeze({
10131
10202
  "valueType": "boolean"
10132
10203
  }
10133
10204
  ],
10134
- "numeric-sensor": [
10135
- {
10136
- "path": "value",
10137
- "valueType": "number"
10138
- },
10139
- {
10140
- "path": "unit",
10141
- "valueType": "string"
10142
- },
10143
- {
10144
- "path": "precision",
10145
- "valueType": "number"
10146
- }
10147
- ],
10205
+ "numeric-sensor": [{
10206
+ "path": "value",
10207
+ "valueType": "number"
10208
+ }],
10148
10209
  "pet-feeder": [
10149
10210
  {
10150
10211
  "path": "foodLevel",
@@ -10236,14 +10297,6 @@ Object.freeze({
10236
10297
  {
10237
10298
  "path": "powerFactor",
10238
10299
  "valueType": "number"
10239
- },
10240
- {
10241
- "path": "unit",
10242
- "valueType": "string"
10243
- },
10244
- {
10245
- "path": "precision",
10246
- "valueType": "number"
10247
10300
  }
10248
10301
  ],
10249
10302
  "presence": [
@@ -10272,20 +10325,10 @@ Object.freeze({
10272
10325
  "valueType": "number"
10273
10326
  }
10274
10327
  ],
10275
- "pressure-sensor": [
10276
- {
10277
- "path": "hpa",
10278
- "valueType": "number"
10279
- },
10280
- {
10281
- "path": "unit",
10282
- "valueType": "string"
10283
- },
10284
- {
10285
- "path": "precision",
10286
- "valueType": "number"
10287
- }
10288
- ],
10328
+ "pressure-sensor": [{
10329
+ "path": "hpa",
10330
+ "valueType": "number"
10331
+ }],
10289
10332
  "privacy-mask": [{
10290
10333
  "path": "enabled",
10291
10334
  "valueType": "boolean"
@@ -10596,20 +10639,10 @@ Object.freeze({
10596
10639
  "path": "tampered",
10597
10640
  "valueType": "boolean"
10598
10641
  }],
10599
- "temperature-sensor": [
10600
- {
10601
- "path": "celsius",
10602
- "valueType": "number"
10603
- },
10604
- {
10605
- "path": "unit",
10606
- "valueType": "string"
10607
- },
10608
- {
10609
- "path": "precision",
10610
- "valueType": "number"
10611
- }
10612
- ],
10642
+ "temperature-sensor": [{
10643
+ "path": "celsius",
10644
+ "valueType": "number"
10645
+ }],
10613
10646
  "update": [
10614
10647
  {
10615
10648
  "path": "currentVersion",
@@ -10720,10 +10753,6 @@ Object.freeze({
10720
10753
  "path": "temperature",
10721
10754
  "valueType": "number"
10722
10755
  },
10723
- {
10724
- "path": "temperatureUnit",
10725
- "valueType": "string"
10726
- },
10727
10756
  {
10728
10757
  "path": "humidity",
10729
10758
  "valueType": "number"
@@ -10732,18 +10761,10 @@ Object.freeze({
10732
10761
  "path": "pressure",
10733
10762
  "valueType": "number"
10734
10763
  },
10735
- {
10736
- "path": "pressureUnit",
10737
- "valueType": "string"
10738
- },
10739
10764
  {
10740
10765
  "path": "windSpeed",
10741
10766
  "valueType": "number"
10742
10767
  },
10743
- {
10744
- "path": "windSpeedUnit",
10745
- "valueType": "string"
10746
- },
10747
10768
  {
10748
10769
  "path": "windBearing",
10749
10770
  "valueType": "number"
@@ -11136,7 +11157,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11136
11157
  return DeviceRole;
11137
11158
  }({});
11138
11159
  //#endregion
11139
- //#region ../types/dist/composition-kEl9mevu.mjs
11160
+ //#region ../types/dist/composition-OhpuespA.mjs
11140
11161
  /**
11141
11162
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11142
11163
  *
@@ -11891,15 +11912,74 @@ function validateExpressionSource(src) {
11891
11912
  }
11892
11913
  return null;
11893
11914
  }
11894
- /** A composed device's stableId is this prefix plus the block id. */
11895
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11896
- /** Longest snippet a `code` source may carry (slice 4). */
11897
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11915
+ /**
11916
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11917
+ * id. Split out so helper schemas can name it without a cycle through
11918
+ * `composition.ts` (D659).
11919
+ */
11898
11920
  var CompositionSourceRefSchema = object({
11899
11921
  /** The owning addon; `stableId` is unique only within it. */
11900
11922
  addonId: string().min(1),
11901
11923
  stableId: string().min(1)
11902
11924
  });
11925
+ var GROUP_TEMPLATE_IDS = [
11926
+ "temperature",
11927
+ "humidity",
11928
+ "pressure",
11929
+ "illuminance",
11930
+ "power",
11931
+ "energy",
11932
+ "battery",
11933
+ "numeric",
11934
+ "motion",
11935
+ "contact",
11936
+ "leak",
11937
+ "smoke",
11938
+ "occupancy",
11939
+ "binary",
11940
+ "switch",
11941
+ "light"
11942
+ ];
11943
+ var NUMERIC_AGGREGATES = [
11944
+ "mean",
11945
+ "median",
11946
+ "min",
11947
+ "max",
11948
+ "sum"
11949
+ ];
11950
+ var BINARY_AGGREGATES = ["any", "all"];
11951
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11952
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11953
+ var AGGREGATE_REDUCERS = [
11954
+ "mean",
11955
+ "median",
11956
+ "min",
11957
+ "max",
11958
+ "sum",
11959
+ "any",
11960
+ "all"
11961
+ ];
11962
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11963
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11964
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11965
+ /** One member. The field is stored so occupancy can name motion or binary. */
11966
+ var HelperMemberSchema = object({
11967
+ source: CompositionSourceRefSchema,
11968
+ cap: string().min(1),
11969
+ fieldPath: string().min(1)
11970
+ });
11971
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
11972
+ kind: literal("group"),
11973
+ templateId: GroupTemplateIdSchema,
11974
+ aggregate: AggregateReducerSchema,
11975
+ members: array(HelperMemberSchema).min(1).max(64),
11976
+ /** Actuator groups only. Readings leave this unset. */
11977
+ active: ActiveCriterionSchema.optional()
11978
+ })]);
11979
+ /** A composed device's stableId is this prefix plus the block id. */
11980
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11981
+ /** Longest snippet a `code` source may carry (slice 4). */
11982
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11903
11983
  /** One field of one capability of one source device. */
11904
11984
  var CompositionFieldReadSchema = object({
11905
11985
  source: CompositionSourceRefSchema,
@@ -11943,7 +12023,33 @@ var CompositionCodeSourceSchema = object({
11943
12023
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11944
12024
  CompositionFromSourceSchema,
11945
12025
  CompositionExpressionSourceSchema,
11946
- CompositionCodeSourceSchema
12026
+ CompositionCodeSourceSchema,
12027
+ object({
12028
+ kind: literal("aggregate"),
12029
+ reducer: _enum([
12030
+ "mean",
12031
+ "median",
12032
+ "min",
12033
+ "max",
12034
+ "sum",
12035
+ "any",
12036
+ "all"
12037
+ ]),
12038
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
12039
+ /** `value` is the reading; the others are the member counts. */
12040
+ publish: _enum([
12041
+ "value",
12042
+ "included",
12043
+ "excluded",
12044
+ "total"
12045
+ ]),
12046
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
12047
+ empty: _enum([
12048
+ "null",
12049
+ "omit",
12050
+ "offline"
12051
+ ])
12052
+ })
11947
12053
  ]);
11948
12054
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11949
12055
  kind: literal("forward"),
@@ -11989,7 +12095,13 @@ var CompositionSchema = object({
11989
12095
  kind: literal("existing"),
11990
12096
  device: CompositionSourceRefSchema
11991
12097
  })]),
11992
- features: array(CompositionFeatureSchema).min(1).max(16)
12098
+ features: array(CompositionFeatureSchema).min(1).max(16),
12099
+ /**
12100
+ * Present when the block is a helper template. Absent on a free
12101
+ * composition (the room example, device 4603). The editor reads this,
12102
+ * never the compiled features.
12103
+ */
12104
+ helper: HelperSpecSchema.optional()
11993
12105
  }).superRefine((composition, ctx) => {
11994
12106
  const seen = /* @__PURE__ */ new Set();
11995
12107
  composition.features.forEach((feature, index) => {
@@ -18140,7 +18252,11 @@ var CompositionProblemSchema = object({
18140
18252
  "item-field-reserved",
18141
18253
  "target-missing",
18142
18254
  "target-unreadable",
18143
- "not-replaceable"
18255
+ "not-replaceable",
18256
+ "native-capability-replace",
18257
+ "mixed-quantity",
18258
+ "helper-mismatch",
18259
+ "member-constraint"
18144
18260
  ]),
18145
18261
  path: string(),
18146
18262
  message: string()
@@ -18161,7 +18277,8 @@ var CompositionUnavailableModeSchema = _enum([
18161
18277
  "null",
18162
18278
  "offline",
18163
18279
  "unknown",
18164
- "release"
18280
+ "release",
18281
+ "omit"
18165
18282
  ]);
18166
18283
  /**
18167
18284
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -18198,9 +18315,11 @@ var CompositionFieldPlanSchema = object({
18198
18315
  reads: array(CompositionFieldReadSchema)
18199
18316
  });
18200
18317
  /**
18201
- * What a feature does to its target (D663): `new` builds a capability on a new
18202
- * device; on an existing device it `add`s a capability the device lacks, or
18203
- * `replace`s named fields of one it has natively.
18318
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
18319
+ * new device; on an existing device it `add`s a capability the device lacks.
18320
+ * Replacing a capability the device already serves is refused
18321
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
18322
+ * wire for a claim the hub already holds.
18204
18323
  */
18205
18324
  var CompositionFeatureModeSchema = _enum([
18206
18325
  "new",
@@ -18373,6 +18492,275 @@ Object.freeze({
18373
18492
  result: ["number", "null"]
18374
18493
  }
18375
18494
  });
18495
+ /**
18496
+ * One catalog row per base reading a group helper can build (D944 §3).
18497
+ * Data, not a form default written into the block.
18498
+ */
18499
+ var NOW = {
18500
+ fieldPath: "lastFetchedAt",
18501
+ expr: "now"
18502
+ };
18503
+ var CHANGED = {
18504
+ fieldPath: "lastChangedAt",
18505
+ expr: "now"
18506
+ };
18507
+ function numeric(input) {
18508
+ return {
18509
+ ...input,
18510
+ family: "numeric",
18511
+ deviceType: DeviceType.Sensor,
18512
+ memberDeviceType: DeviceType.Sensor,
18513
+ commandCaps: [],
18514
+ baseUnit: baseUnitOf(input.quantity),
18515
+ aggregates: NUMERIC_AGGREGATES,
18516
+ accept: [{
18517
+ cap: input.outputCap,
18518
+ fieldPath: input.outputField
18519
+ }]
18520
+ };
18521
+ }
18522
+ function binary(input) {
18523
+ return {
18524
+ ...input,
18525
+ family: "binary",
18526
+ deviceType: DeviceType.Sensor,
18527
+ memberDeviceType: DeviceType.Sensor,
18528
+ commandCaps: [],
18529
+ quantity: "none",
18530
+ baseUnit: void 0,
18531
+ aggregates: BINARY_AGGREGATES,
18532
+ empty: "offline"
18533
+ };
18534
+ }
18535
+ var GROUP_TEMPLATES = [
18536
+ numeric({
18537
+ id: "temperature",
18538
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18539
+ role: DeviceRole.TemperatureSensor,
18540
+ outputCap: "temperature-sensor",
18541
+ outputField: "celsius",
18542
+ quantity: "temperature",
18543
+ defaultAggregate: "mean",
18544
+ empty: "null",
18545
+ companions: [NOW]
18546
+ }),
18547
+ numeric({
18548
+ id: "humidity",
18549
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18550
+ role: DeviceRole.HumiditySensor,
18551
+ outputCap: "humidity-sensor",
18552
+ outputField: "percent",
18553
+ quantity: "humidity",
18554
+ defaultAggregate: "mean",
18555
+ empty: "null",
18556
+ companions: [NOW]
18557
+ }),
18558
+ numeric({
18559
+ id: "pressure",
18560
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18561
+ role: DeviceRole.PressureSensor,
18562
+ outputCap: "pressure-sensor",
18563
+ outputField: "hpa",
18564
+ quantity: "pressure",
18565
+ defaultAggregate: "mean",
18566
+ empty: "null",
18567
+ companions: [NOW]
18568
+ }),
18569
+ numeric({
18570
+ id: "illuminance",
18571
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18572
+ role: DeviceRole.AmbientLightSensor,
18573
+ outputCap: "ambient-light-sensor",
18574
+ outputField: "lux",
18575
+ quantity: "illuminance",
18576
+ defaultAggregate: "mean",
18577
+ empty: "null",
18578
+ companions: [NOW]
18579
+ }),
18580
+ numeric({
18581
+ id: "power",
18582
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18583
+ role: DeviceRole.PowerSensor,
18584
+ outputCap: "power-meter",
18585
+ outputField: "watts",
18586
+ quantity: "power",
18587
+ defaultAggregate: "sum",
18588
+ empty: "omit",
18589
+ companions: [NOW]
18590
+ }),
18591
+ numeric({
18592
+ id: "energy",
18593
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18594
+ role: DeviceRole.EnergySensor,
18595
+ outputCap: "power-meter",
18596
+ outputField: "kwhTotal",
18597
+ quantity: "energy",
18598
+ defaultAggregate: "sum",
18599
+ empty: "omit",
18600
+ companions: [NOW]
18601
+ }),
18602
+ numeric({
18603
+ id: "battery",
18604
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18605
+ role: DeviceRole.BatterySensor,
18606
+ outputCap: "battery",
18607
+ outputField: "percentage",
18608
+ quantity: "percentage",
18609
+ defaultAggregate: "mean",
18610
+ empty: "null",
18611
+ companions: [
18612
+ {
18613
+ fieldPath: "charging",
18614
+ expr: "\"unknown\""
18615
+ },
18616
+ {
18617
+ fieldPath: "sleeping",
18618
+ expr: "false"
18619
+ },
18620
+ {
18621
+ fieldPath: "lastUpdated",
18622
+ expr: "now"
18623
+ }
18624
+ ]
18625
+ }),
18626
+ numeric({
18627
+ id: "numeric",
18628
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18629
+ role: DeviceRole.NumericSensor,
18630
+ outputCap: "numeric-sensor",
18631
+ outputField: "value",
18632
+ quantity: "device",
18633
+ defaultAggregate: "mean",
18634
+ empty: "null",
18635
+ companions: [NOW]
18636
+ }),
18637
+ binary({
18638
+ id: "motion",
18639
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18640
+ role: DeviceRole.MotionSensor,
18641
+ outputCap: "motion",
18642
+ outputField: "detected",
18643
+ defaultAggregate: "any",
18644
+ accept: [{
18645
+ cap: "motion",
18646
+ fieldPath: "detected"
18647
+ }],
18648
+ companions: [{
18649
+ fieldPath: "lastDetectedAt",
18650
+ expr: "null"
18651
+ }, {
18652
+ fieldPath: "autoClearAfterMs",
18653
+ expr: "null"
18654
+ }]
18655
+ }),
18656
+ binary({
18657
+ id: "contact",
18658
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18659
+ role: DeviceRole.ContactSensor,
18660
+ outputCap: "contact",
18661
+ outputField: "entryOpen",
18662
+ defaultAggregate: "any",
18663
+ accept: [{
18664
+ cap: "contact",
18665
+ fieldPath: "entryOpen"
18666
+ }],
18667
+ companions: [CHANGED]
18668
+ }),
18669
+ binary({
18670
+ id: "leak",
18671
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18672
+ role: DeviceRole.LeakSensor,
18673
+ outputCap: "flood",
18674
+ outputField: "flooded",
18675
+ defaultAggregate: "any",
18676
+ accept: [{
18677
+ cap: "flood",
18678
+ fieldPath: "flooded"
18679
+ }],
18680
+ companions: [CHANGED]
18681
+ }),
18682
+ binary({
18683
+ id: "smoke",
18684
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18685
+ role: DeviceRole.SmokeSensor,
18686
+ outputCap: "smoke",
18687
+ outputField: "detected",
18688
+ defaultAggregate: "any",
18689
+ accept: [{
18690
+ cap: "smoke",
18691
+ fieldPath: "detected"
18692
+ }],
18693
+ companions: [CHANGED]
18694
+ }),
18695
+ binary({
18696
+ id: "occupancy",
18697
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18698
+ role: DeviceRole.BinarySensor,
18699
+ outputCap: "binary",
18700
+ outputField: "on",
18701
+ defaultAggregate: "any",
18702
+ accept: [{
18703
+ cap: "motion",
18704
+ fieldPath: "detected"
18705
+ }, {
18706
+ cap: "binary",
18707
+ fieldPath: "on"
18708
+ }],
18709
+ companions: [CHANGED]
18710
+ }),
18711
+ binary({
18712
+ id: "binary",
18713
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18714
+ role: DeviceRole.BinarySensor,
18715
+ outputCap: "binary",
18716
+ outputField: "on",
18717
+ defaultAggregate: "any",
18718
+ accept: [{
18719
+ cap: "binary",
18720
+ fieldPath: "on"
18721
+ }],
18722
+ companions: [CHANGED]
18723
+ }),
18724
+ actuator({
18725
+ id: "switch",
18726
+ 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.",
18727
+ deviceType: DeviceType.Switch,
18728
+ outputCap: "switch",
18729
+ commandCaps: ["switch"]
18730
+ }),
18731
+ actuator({
18732
+ id: "light",
18733
+ 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.",
18734
+ deviceType: DeviceType.Light,
18735
+ outputCap: "switch",
18736
+ commandCaps: [
18737
+ "switch",
18738
+ "brightness",
18739
+ "color"
18740
+ ]
18741
+ })
18742
+ ];
18743
+ function actuator(input) {
18744
+ return {
18745
+ ...input,
18746
+ family: "actuator",
18747
+ role: void 0,
18748
+ memberDeviceType: input.deviceType,
18749
+ outputField: "on",
18750
+ quantity: "none",
18751
+ baseUnit: void 0,
18752
+ aggregates: BINARY_AGGREGATES,
18753
+ defaultAggregate: "any",
18754
+ empty: "offline",
18755
+ accept: [{
18756
+ cap: "switch",
18757
+ fieldPath: "on"
18758
+ }],
18759
+ companions: [CHANGED]
18760
+ };
18761
+ }
18762
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18763
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18376
18764
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18377
18765
  /**
18378
18766
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -20086,12 +20474,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
20086
20474
  * providers detect renames by matching `uniqueId` and patch `id`
20087
20475
  * in-place so dispatch keeps working.
20088
20476
  *
20089
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
20090
- * `precision`) live in the cap STATUS SLICE so they flow live with every
20091
- * state push and are the single source of truth. The `device_class` tag is
20092
- * an HA-specific input that is consumed at the discovery boundary
20093
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
20094
- * stored after that point.
20477
+ * **Display unit and precision are NOT stored here.** They are a read-time
20478
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20479
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20480
+ * neither is repeated on each sample. The `device_class` tag is an
20481
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20482
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
20095
20483
  *
20096
20484
  * The `raw` escape hatch carries provider-specific attributes that don't
20097
20485
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -20105,6 +20493,14 @@ var SourceInfoSchema = object({
20105
20493
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
20106
20494
  * Used to detect rename when `id` changes. */
20107
20495
  uniqueId: string().optional(),
20496
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20497
+ quantity: ReadingQuantitySchema.optional(),
20498
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20499
+ dateTimeShape: _enum([
20500
+ "date",
20501
+ "time",
20502
+ "datetime"
20503
+ ]).optional(),
20108
20504
  /** Provider-specific extras that don't fit the structured slots.
20109
20505
  * Whitelist what goes here per provider — do NOT dump entire HA
20110
20506
  * attribute blobs (would explode the meta JSON column). */
@@ -41017,7 +41413,8 @@ method(object({
41017
41413
  "enum"
41018
41414
  ]),
41019
41415
  enumValues: array(string()).optional(),
41020
- item: boolean().optional()
41416
+ item: boolean().optional(),
41417
+ quantity: ReadingQuantitySchema.optional()
41021
41418
  })).readonly(),
41022
41419
  itemArray: object({
41023
41420
  path: string(),
@@ -44836,8 +45233,8 @@ var MaskPolygonShapeSchema = object({
44836
45233
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44837
45234
  var MaskGridShapeSchema = object({
44838
45235
  kind: literal("grid"),
44839
- gridWidth: number(),
44840
- gridHeight: number(),
45236
+ gridWidth: number().meta(readingMeta("none")),
45237
+ gridHeight: number().meta(readingMeta("none")),
44841
45238
  cells: array(boolean())
44842
45239
  });
44843
45240
  discriminatedUnion("kind", [
@@ -44949,6 +45346,8 @@ var NcSystemEventKindSchema = _enum([
44949
45346
  "detection-blind",
44950
45347
  "motion-noisy",
44951
45348
  "motion-calm",
45349
+ "package-llm-silent",
45350
+ "package-llm-answered",
44952
45351
  "recording-stalled",
44953
45352
  "recording-healthy",
44954
45353
  "recording-degraded",
@@ -55963,6 +56362,28 @@ var PackageVersionInfoSchema = object({
55963
56362
  deprecated: string().optional(),
55964
56363
  distTags: array(string()).readonly()
55965
56364
  });
56365
+ /**
56366
+ * One published document. `ok` is markdown from the registry. `missing` means
56367
+ * the registry answered and the file was not there. `error` is a registry or
56368
+ * tarball failure — never an empty string standing in for either.
56369
+ */
56370
+ var PackageDocBodySchema = discriminatedUnion("status", [
56371
+ object({
56372
+ status: literal("ok"),
56373
+ markdown: string()
56374
+ }),
56375
+ object({ status: literal("missing") }),
56376
+ object({
56377
+ status: literal("error"),
56378
+ message: string()
56379
+ })
56380
+ ]);
56381
+ var PackageDocsSchema = object({
56382
+ name: string(),
56383
+ version: string(),
56384
+ readme: PackageDocBodySchema,
56385
+ changelog: PackageDocBodySchema
56386
+ });
55966
56387
  var SearchResultSchema = object({
55967
56388
  name: string(),
55968
56389
  version: string(),
@@ -56181,7 +56602,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56181
56602
  }), object({ success: literal(true) }), {
56182
56603
  kind: "mutation",
56183
56604
  auth: "admin"
56184
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
56605
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
56606
+ name: string().min(1),
56607
+ version: string().min(1).optional()
56608
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
56185
56609
  kind: "mutation",
56186
56610
  auth: "admin"
56187
56611
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -56231,46 +56655,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56231
56655
  }), LogStreamEntrySchema, { kind: "subscription" });
56232
56656
  object({
56233
56657
  /** Carbon dioxide concentration in ppm. */
56234
- co2Ppm: number().min(0).optional(),
56658
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
56235
56659
  /** Total volatile organic compounds in ppb. */
56236
- vocPpb: number().min(0).optional(),
56660
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56237
56661
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56238
- pm25: number().min(0).optional(),
56662
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56239
56663
  /** Particulate matter ≤ 10 μm in µg/m³. */
56240
- pm10: number().min(0).optional(),
56664
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
56241
56665
  /** Composite AQI value (typically 0..500). */
56242
- aqi: number().optional(),
56666
+ aqi: number().optional().meta(readingMeta("index")),
56243
56667
  /** Ms epoch when the slice was last updated. */
56244
- lastFetchedAt: number(),
56245
- /** Live display unit of the single metric this slice carries (e.g. HA
56246
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
56247
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
56248
- * per slice is unambiguous. */
56249
- unit: string().optional(),
56250
- /** Suggested decimal places for numeric display.
56251
- * Populated live from the upstream source when provided (e.g. HA
56252
- * `attributes.suggested_display_precision`). Falls back to
56253
- * auto-formatting when absent. */
56254
- precision: number().int().min(0).max(10).optional()
56668
+ lastFetchedAt: number()
56255
56669
  });
56256
56670
  DeviceType.Sensor;
56257
56671
  object({
56258
- /** Current illuminance in lux (lx). */
56259
- lux: number().min(0).meta({
56260
- unit: "lx",
56672
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56673
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
56674
+ precision: 0,
56261
56675
  label: "Illuminance"
56262
- }),
56676
+ })),
56263
56677
  /** Ms epoch when the slice was last updated. */
56264
- lastFetchedAt: number(),
56265
- /** Live display unit from the upstream source (e.g. HA
56266
- * `attributes.unit_of_measurement`). The UI prefers this over the
56267
- * role's canonical unit. Absent → fall back to the canonical unit. */
56268
- unit: string().optional(),
56269
- /** Suggested decimal places for numeric display.
56270
- * Populated live from the upstream source when provided (e.g. HA
56271
- * `attributes.suggested_display_precision`). Falls back to
56272
- * auto-formatting when absent. */
56273
- precision: number().int().min(0).max(10).optional()
56678
+ lastFetchedAt: number()
56274
56679
  });
56275
56680
  DeviceType.Sensor;
56276
56681
  /**
@@ -56415,10 +56820,7 @@ var BatteryStatusSchema = object({
56415
56820
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
56416
56821
  * declines to band a non-finite reading, which is the correct shape.
56417
56822
  */
56418
- percentage: number().min(0).max(100).nullable().meta({
56419
- unit: "%",
56420
- label: "Charge"
56421
- }),
56823
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
56422
56824
  /**
56423
56825
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
56424
56826
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -56445,7 +56847,14 @@ var BatteryStatusSchema = object({
56445
56847
  */
56446
56848
  sleeping: boolean().meta({ label: "Sleeping" }),
56447
56849
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
56448
- lastUpdated: number(),
56850
+ lastUpdated: number().meta(readingMeta("none")),
56851
+ /**
56852
+ * Battery voltage in volts, for a cell the firmware reports as voltage
56853
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
56854
+ * that only report charge. A second reading on this cap, not a second
56855
+ * `numeric-sensor` on the same device.
56856
+ */
56857
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
56449
56858
  /**
56450
56859
  * Ms epoch of the last time the device PROVED it was reachable — a
56451
56860
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -56505,7 +56914,7 @@ object({
56505
56914
  DeviceType.Sensor;
56506
56915
  object({
56507
56916
  /** Current level as 0..100 inclusive. Firmware-reported. */
56508
- percentage: number().min(0).max(100),
56917
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
56509
56918
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
56510
56919
  lastChangedAt: number()
56511
56920
  });
@@ -56890,21 +57299,21 @@ object({
56890
57299
  /** Single setpoint in Celsius. Used by single-target modes
56891
57300
  * (heat / cool / dry). Null when the device is in a range mode
56892
57301
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
56893
- target: number().nullable(),
57302
+ target: number().nullable().meta(readingMeta("temperature")),
56894
57303
  /** Upper bound of the dual setpoint in Celsius. Populated only
56895
57304
  * in `heat_cool` mode. */
56896
- targetHigh: number().nullable(),
57305
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
56897
57306
  /** Lower bound of the dual setpoint in Celsius. Populated only
56898
57307
  * in `heat_cool` mode. */
56899
- targetLow: number().nullable(),
57308
+ targetLow: number().nullable().meta(readingMeta("temperature")),
56900
57309
  /** Target relative humidity (0..100). Null when the device has no
56901
57310
  * humidity control. */
56902
- targetHumidity: number().min(0).max(100).nullable(),
57311
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56903
57312
  /** Current measured temperature in Celsius. Read-only — pushed by
56904
57313
  * the device's internal sensor. */
56905
- currentTemp: number().nullable(),
57314
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
56906
57315
  /** Current measured relative humidity (0..100). Read-only. */
56907
- currentHumidity: number().min(0).max(100).nullable(),
57316
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56908
57317
  /** Vertical louver swing on/off. Null when the device has no vertical
56909
57318
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
56910
57319
  swingVertical: boolean().nullable(),
@@ -56912,11 +57321,11 @@ object({
56912
57321
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
56913
57322
  swingHorizontal: boolean().nullable(),
56914
57323
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
56915
- minTemp: number().nullable().optional(),
57324
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
56916
57325
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
56917
- maxTemp: number().nullable().optional(),
57326
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
56918
57327
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
56919
- tempStep: number().positive().nullable().optional(),
57328
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
56920
57329
  /** Ms epoch when the slice was last updated (push or command). */
56921
57330
  lastFetchedAt: number()
56922
57331
  });
@@ -56995,17 +57404,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
56995
57404
  * modes to expose in the picker without a slice fetch.
56996
57405
  */
56997
57406
  var RgbTripletSchema = object({
56998
- r: number().int().min(0).max(255),
56999
- g: number().int().min(0).max(255),
57000
- b: number().int().min(0).max(255)
57407
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
57408
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
57409
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
57001
57410
  });
57002
57411
  var HsvTripletSchema = object({
57003
57412
  /** Hue in degrees, 0..360. */
57004
- h: number().min(0).max(360),
57413
+ h: number().min(0).max(360).meta(readingMeta("none")),
57005
57414
  /** Saturation as 0..100 inclusive. */
57006
- s: number().min(0).max(100),
57415
+ s: number().min(0).max(100).meta(readingMeta("none")),
57007
57416
  /** Value/brightness as 0..100 inclusive. */
57008
- v: number().min(0).max(100)
57417
+ v: number().min(0).max(100).meta(readingMeta("none"))
57009
57418
  });
57010
57419
  var ColorInputSchema = discriminatedUnion("mode", [
57011
57420
  object({
@@ -57034,7 +57443,7 @@ object({
57034
57443
  /** Populated when `mode === 'hsv'`. */
57035
57444
  hsv: HsvTripletSchema.optional(),
57036
57445
  /** Populated when `mode === 'mired'`. */
57037
- mireds: number().int().optional(),
57446
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
57038
57447
  /** Ms epoch of the last operator-driven change. */
57039
57448
  lastChangedAt: number()
57040
57449
  });
@@ -57158,18 +57567,12 @@ _enum([
57158
57567
  "empty",
57159
57568
  "full"
57160
57569
  ]);
57161
- /** Units a remaining-life counter is reported in. Closed: the panel renders
57162
- * each. */
57163
- var ConsumableRemainingUnitSchema = _enum([
57164
- "days",
57165
- "hours",
57166
- "cycles"
57167
- ]);
57168
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
57169
- * days). `value` null = unknown (D393). */
57570
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
57571
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
57572
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
57170
57573
  var ConsumableRemainingSchema = object({
57171
- value: number().min(0).nullable(),
57172
- unit: ConsumableRemainingUnitSchema
57574
+ value: number().min(0).nullable().meta(readingMeta("device")),
57575
+ kind: ConsumableRemainingKindSchema
57173
57576
  });
57174
57577
  var ConsumablesStatusSchema = object({
57175
57578
  items: array(object({
@@ -57178,7 +57581,7 @@ var ConsumablesStatusSchema = object({
57178
57581
  /** Display name. */
57179
57582
  label: string().min(1),
57180
57583
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
57181
- level: number().min(0).max(100).nullable(),
57584
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57182
57585
  /** The provider's discrete verdict when known (binary mode). */
57183
57586
  status: ConsumableStatusSchema.nullable(),
57184
57587
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -57195,7 +57598,7 @@ var ConsumablesStatusSchema = object({
57195
57598
  })),
57196
57599
  lastChangedAt: number()
57197
57600
  });
57198
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57601
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57199
57602
  Object.values(DeviceType), method(object({
57200
57603
  deviceId: number().int().nonnegative(),
57201
57604
  key: string().min(1)
@@ -57220,39 +57623,17 @@ object({
57220
57623
  /** Current value. Numeric kind → number; select/text/datetime → string.
57221
57624
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
57222
57625
  * or time-only — HA accepts all three forms). */
57223
- value: union([number(), string()]),
57626
+ value: union([number().meta(readingMeta("device")), string()]),
57224
57627
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
57225
57628
  options: array(string()),
57226
57629
  /** Ms epoch when the slice was last updated. */
57227
57630
  lastChangedAt: number(),
57228
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
57229
- * Populated live from HA `attributes.unit_of_measurement` on each push.
57230
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57231
- unit: string().optional(),
57232
- /** Minimum allowed value. Populated live from HA `attributes.min`.
57233
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57234
- min: number().optional(),
57235
- /** Maximum allowed value. Populated live from HA `attributes.max`.
57236
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57237
- max: number().optional(),
57238
- /** Step increment. Populated live from HA `attributes.step`.
57239
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57240
- step: number().optional(),
57241
- /** Suggested decimal places for numeric display. Populated live from HA
57242
- * `attributes.suggested_display_precision`, or derived from `step` when the
57243
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
57244
- * other kinds. Falls back to auto-formatting when absent. */
57245
- precision: number().int().min(0).max(10).optional(),
57246
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
57247
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
57248
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
57249
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
57250
- * `has_date`/`has_time` attributes on each push. */
57251
- format: _enum([
57252
- "date",
57253
- "time",
57254
- "datetime"
57255
- ]).optional()
57631
+ /** Minimum allowed value, in the device quantity's base unit. */
57632
+ min: number().optional().meta(readingMeta("device")),
57633
+ /** Maximum allowed value, in the device quantity's base unit. */
57634
+ max: number().optional().meta(readingMeta("device")),
57635
+ /** Step increment, in the device quantity's base unit. */
57636
+ step: number().optional().meta(readingMeta("device"))
57256
57637
  });
57257
57638
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
57258
57639
  object({
@@ -57290,9 +57671,9 @@ object({
57290
57671
  ]),
57291
57672
  /** 0 = fully closed, 100 = fully open. Null when the device has
57292
57673
  * no intermediate position surface. */
57293
- position: number().min(0).max(100).nullable(),
57674
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57294
57675
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
57295
- tiltPosition: number().min(0).max(100).nullable(),
57676
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57296
57677
  /** Ms epoch when the slice was last updated. */
57297
57678
  lastChangedAt: number()
57298
57679
  });
@@ -57350,9 +57731,9 @@ var NormalizedRangeSchema$1 = object({
57350
57731
  object({
57351
57732
  mode: DayNightModeSchema,
57352
57733
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
57353
- sensitivity: number().optional(),
57734
+ sensitivity: number().optional().meta(readingMeta("none")),
57354
57735
  /** Delay before the IR-cut filter flips, in seconds. */
57355
- switchDelaySec: number().optional(),
57736
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
57356
57737
  lastFetchedAt: number()
57357
57738
  });
57358
57739
  /**
@@ -57507,11 +57888,11 @@ object({
57507
57888
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
57508
57889
  availablePrograms: array(string()),
57509
57890
  /** Seconds remaining on the active cycle, or null when not running / not known. */
57510
- remainingSeconds: number().int().nonnegative().nullable(),
57891
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57511
57892
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
57512
- delayedStartSeconds: number().int().nonnegative().nullable(),
57893
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57513
57894
  /** Cycle completion 0..100, or null when not applicable. */
57514
- progressPercent: number().min(0).max(100).nullable(),
57895
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57515
57896
  /** True when the door is reported open; null when the door state is not known. */
57516
57897
  doorOpen: boolean().nullable(),
57517
57898
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -57535,7 +57916,7 @@ object({
57535
57916
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
57536
57917
  machineCareDue: boolean().nullable(),
57537
57918
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
57538
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
57919
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
57539
57920
  /** Option states: null when the appliance does not expose the option or has not said. */
57540
57921
  halfLoad: boolean().nullable(),
57541
57922
  hygienePlus: boolean().nullable(),
@@ -57550,7 +57931,7 @@ object({
57550
57931
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
57551
57932
  activeEvents: array(string()),
57552
57933
  /** Numeric error code when the appliance reports one; null otherwise. */
57553
- errorCode: number().nullable(),
57934
+ errorCode: number().nullable().meta(readingMeta("none")),
57554
57935
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
57555
57936
  errorLabel: string().nullable(),
57556
57937
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -57696,24 +58077,14 @@ object({
57696
58077
  /** Ms epoch of the last press. null = never observed since this provider started. */
57697
58078
  lastPressedAt: number().nullable(),
57698
58079
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
57699
- pressCountSinceStart: number()
58080
+ pressCountSinceStart: number().meta(readingMeta("count"))
57700
58081
  });
57701
58082
  object({
57702
58083
  deviceId: number(),
57703
58084
  timestamp: number()
57704
58085
  });
57705
58086
  DeviceType.Button, DeviceType.Camera;
57706
- /**
57707
- * Enum-state sensor — a string value picked from a finite option set.
57708
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
57709
- * states, weather conditions, contact-source channels, mode strings).
57710
- * The option set is stable across observations; it is persisted in the
57711
- * device config blob (via the control slice at adoption) and is NOT
57712
- * stored in `sourceInfo`.
57713
- */
57714
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
57715
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
57716
- var EnumSensorDateTimeFormatSchema = _enum([
58087
+ _enum([
57717
58088
  "date",
57718
58089
  "time",
57719
58090
  "datetime"
@@ -57721,12 +58092,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
57721
58092
  object({
57722
58093
  value: string(),
57723
58094
  /**
57724
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
57725
- * as a locale date/time/datetime; absent for plain enum sensors. HA
57726
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
57727
- * a time-only sensor → `'time'`.
58095
+ * Set when `value` is a timestamp rather than an enum state, so history
58096
+ * does not learn ISO strings as states. The display shape (date / time /
58097
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
57728
58098
  */
57729
- format: EnumSensorDateTimeFormatSchema.optional(),
58099
+ timestamp: boolean().optional(),
57730
58100
  /** Ms epoch when the slice was last updated. */
57731
58101
  lastFetchedAt: number()
57732
58102
  });
@@ -57745,13 +58115,13 @@ var EventFireSchema = object({
57745
58115
  deviceId: number(),
57746
58116
  eventType: string(),
57747
58117
  data: record(string(), unknown()).nullable(),
57748
- timestamp: number(),
57749
- seq: number()
58118
+ timestamp: number().meta(readingMeta("none")),
58119
+ seq: number().meta(readingMeta("count"))
57750
58120
  });
57751
58121
  object({
57752
58122
  eventTypes: array(string()),
57753
58123
  lastEvent: EventFireSchema.nullable(),
57754
- eventCountSinceStart: number()
58124
+ eventCountSinceStart: number().meta(readingMeta("count"))
57755
58125
  });
57756
58126
  DeviceType.EventEmitter;
57757
58127
  var IdentitySchema = object({
@@ -58140,10 +58510,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
58140
58510
  object({
58141
58511
  /** Active speed as 0..100 inclusive. Null when the device has no
58142
58512
  * speed surface (single-speed fan). */
58143
- percentage: number().min(0).max(100).nullable(),
58513
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58144
58514
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
58145
58515
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
58146
- percentageStep: number().positive().optional(),
58516
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
58147
58517
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
58148
58518
  * — empty string when no preset is active or supported. */
58149
58519
  preset: string(),
@@ -58225,7 +58595,7 @@ var FeatureProbeStatusSchema = object({
58225
58595
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
58226
58596
  model: string().nullable(),
58227
58597
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
58228
- channelCount: number().nullable(),
58598
+ channelCount: number().nullable().meta(readingMeta("count")),
58229
58599
  /**
58230
58600
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
58231
58601
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -58257,9 +58627,39 @@ object({
58257
58627
  lastChangedAt: number()
58258
58628
  });
58259
58629
  DeviceType.Sensor;
58630
+ object({
58631
+ /** One row per member that has been commanded. A later command replaces that member's row. */
58632
+ results: array(object({
58633
+ deviceId: number().int().nonnegative(),
58634
+ cap: string().min(1),
58635
+ method: string().min(1),
58636
+ ok: boolean(),
58637
+ error: string().optional(),
58638
+ /** Ms epoch of the attempt. */
58639
+ at: number()
58640
+ })) });
58641
+ DeviceType.Switch, DeviceType.Light;
58642
+ object({
58643
+ /** Members whose current value entered the aggregate. */
58644
+ included: number().int().nonnegative().meta(readingMeta("count", {
58645
+ precision: 0,
58646
+ label: "Reporting"
58647
+ })),
58648
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
58649
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
58650
+ precision: 0,
58651
+ label: "Excluded"
58652
+ })),
58653
+ /** `included` + `excluded`. */
58654
+ total: number().int().nonnegative().meta(readingMeta("count", {
58655
+ precision: 0,
58656
+ label: "Members"
58657
+ }))
58658
+ });
58659
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
58260
58660
  object({
58261
58661
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
58262
- position: number().min(0).max(100).nullable(),
58662
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58263
58663
  /** Ms epoch when the slice was last updated (push or poll). */
58264
58664
  lastFetchedAt: number()
58265
58665
  });
@@ -58268,9 +58668,9 @@ object({
58268
58668
  /** Whether the humidifier is currently on. */
58269
58669
  on: boolean(),
58270
58670
  /** Current measured relative humidity (0..100). Null when not reported. */
58271
- currentHumidity: number().min(0).max(100).nullable(),
58671
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58272
58672
  /** Target relative humidity (0..100). Null when no setpoint surface. */
58273
- targetHumidity: number().min(0).max(100).nullable(),
58673
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58274
58674
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
58275
58675
  * device has no mode surface. */
58276
58676
  mode: string().nullable(),
@@ -58280,9 +58680,9 @@ object({
58280
58680
  * `idle` / `off`). Null when the device doesn't report it. */
58281
58681
  action: string().nullable(),
58282
58682
  /** HA `min_humidity` attribute. Null → UI uses 0. */
58283
- minHumidity: number().nullable(),
58683
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
58284
58684
  /** HA `max_humidity` attribute. Null → UI uses 100. */
58285
- maxHumidity: number().nullable(),
58685
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
58286
58686
  /** Ms epoch when the slice was last updated. */
58287
58687
  lastChangedAt: number()
58288
58688
  });
@@ -58306,22 +58706,13 @@ DeviceType.Humidifier, method(object({
58306
58706
  auth: "protected"
58307
58707
  });
58308
58708
  object({
58309
- /** Current relative humidity, 0..100. */
58310
- percent: number().min(0).max(100).meta({
58311
- unit: "%",
58709
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
58710
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
58711
+ precision: 0,
58312
58712
  label: "Humidity"
58313
- }),
58713
+ })),
58314
58714
  /** Ms epoch when the slice was last updated. */
58315
- lastFetchedAt: number(),
58316
- /** Live display unit from the upstream source (e.g. HA
58317
- * `attributes.unit_of_measurement`). The UI prefers this over the
58318
- * role's canonical unit. Absent → fall back to the canonical unit. */
58319
- unit: string().optional(),
58320
- /** Suggested decimal places for numeric display.
58321
- * Populated live from the upstream source when provided (e.g. HA
58322
- * `attributes.suggested_display_precision`). Falls back to
58323
- * auto-formatting when absent. */
58324
- precision: number().int().min(0).max(10).optional()
58715
+ lastFetchedAt: number()
58325
58716
  });
58326
58717
  DeviceType.Sensor, DeviceType.Thermostat;
58327
58718
  object({
@@ -58329,7 +58720,7 @@ object({
58329
58720
  * entity exposes no `entity_picture` (yet). */
58330
58721
  url: string().nullable(),
58331
58722
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
58332
- lastUpdated: number().nullable()
58723
+ lastUpdated: number().nullable().meta(readingMeta("none"))
58333
58724
  });
58334
58725
  DeviceType.Image;
58335
58726
  /**
@@ -58385,19 +58776,19 @@ var NormalizedRangeSchema = object({
58385
58776
  });
58386
58777
  object({
58387
58778
  /** Normalized 0–100. */
58388
- brightness: number().optional(),
58779
+ brightness: number().optional().meta(readingMeta("none")),
58389
58780
  /** Normalized 0–100. */
58390
- contrast: number().optional(),
58781
+ contrast: number().optional().meta(readingMeta("none")),
58391
58782
  /** Normalized 0–100. */
58392
- saturation: number().optional(),
58783
+ saturation: number().optional().meta(readingMeta("none")),
58393
58784
  /** Normalized 0–100. */
58394
- sharpness: number().optional(),
58785
+ sharpness: number().optional().meta(readingMeta("none")),
58395
58786
  mirror: boolean().optional(),
58396
58787
  flip: boolean().optional(),
58397
58788
  rotate: ImageRotateSchema.optional(),
58398
58789
  whiteBalance: WhiteBalanceModeSchema.optional(),
58399
58790
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
58400
- warmth: number().optional(),
58791
+ warmth: number().optional().meta(readingMeta("none")),
58401
58792
  exposureMode: ExposureModeSchema.optional(),
58402
58793
  backlightMode: BacklightModeSchema.optional(),
58403
58794
  lastFetchedAt: number()
@@ -58642,10 +59033,10 @@ var IntercomAbilitySchema = object({
58642
59033
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
58643
59034
  codecs: array(string()),
58644
59035
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
58645
- sampleRate: number(),
59036
+ sampleRate: number().meta(readingMeta("none")),
58646
59037
  duplex: _enum(["half", "full"]),
58647
59038
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
58648
- maxBacklogMs: number()
59039
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
58649
59040
  });
58650
59041
  var IntercomStatusSchema = object({
58651
59042
  /** True while a talk session is open. */
@@ -58756,13 +59147,13 @@ object({
58756
59147
  activity: LawnMowerActivitySchema,
58757
59148
  /** 0..100 battery percentage. Null when the device has no battery
58758
59149
  * reading. */
58759
- batteryLevel: number().min(0).max(100).nullable(),
59150
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58760
59151
  /** 0..100 mowing-completion percentage of the current task, or null when no
58761
59152
  * task is active / progress is unavailable. */
58762
- progressPercent: number().min(0).max(100).nullable(),
59153
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58763
59154
  /** Current device/event code (dynamic — mostly status, sometimes an error),
58764
59155
  * or null when unknown. */
58765
- currentCode: number().nullable(),
59156
+ currentCode: number().nullable().meta(readingMeta("none")),
58766
59157
  /** Human label for {@link currentCode}, or null when undecodable. */
58767
59158
  currentCodeLabel: string().nullable(),
58768
59159
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -59224,7 +59615,7 @@ object({
59224
59615
  state: MediaPlayerStateSchema,
59225
59616
  /** Volume as 0..100 inclusive. Null when the device has no volume
59226
59617
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59227
- volumeLevel: number().min(0).max(100).nullable(),
59618
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59228
59619
  /** Mute toggle distinct from volume=0. Null when no mute surface.
59229
59620
  * Pair with `DeviceFeature.MediaPlayerMute`. */
59230
59621
  isMuted: boolean().nullable(),
@@ -59237,10 +59628,10 @@ object({
59237
59628
  currentMedia: MediaInfoSchema.nullable(),
59238
59629
  /** Current playback position in ms. Null when not seekable or
59239
59630
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59240
- positionMs: number().int().nonnegative().nullable(),
59631
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59241
59632
  /** Total duration of the current media in ms. Null when unknown
59242
59633
  * (live stream) or nothing is playing. */
59243
- durationMs: number().int().nonnegative().nullable(),
59634
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59244
59635
  /** Shuffle toggle. Null when no shuffle surface.
59245
59636
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59246
59637
  shuffle: boolean().nullable(),
@@ -59540,7 +59931,7 @@ object({
59540
59931
  * it, every provider seeded it at boot, and every session left it
59541
59932
  * `detected: true` after its detach.
59542
59933
  */
59543
- autoClearAfterMs: number().nullable()
59934
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
59544
59935
  });
59545
59936
  /**
59546
59937
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -59590,7 +59981,7 @@ var MotionZoneRegionSchema = object({
59590
59981
  });
59591
59982
  object({
59592
59983
  enabled: boolean(),
59593
- sensitivity: number(),
59984
+ sensitivity: number().meta(readingMeta("none")),
59594
59985
  /** Grid region(s). Today exactly one `grid` shape. */
59595
59986
  regions: array(MotionZoneRegionSchema),
59596
59987
  lastFetchedAt: number()
@@ -59894,7 +60285,7 @@ var NavigationStatusSchema = object({
59894
60285
  following: boolean(),
59895
60286
  flash: boolean(),
59896
60287
  lightMode: NavigationLightModeSchema,
59897
- lightLevel: number().min(40).max(100),
60288
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
59898
60289
  /** Ms epoch when the slice was last updated. */
59899
60290
  lastChangedAt: number()
59900
60291
  });
@@ -59940,13 +60331,13 @@ var NetworkLinkStatusSchema = object({
59940
60331
  * one whose reading has not landed must not be drawn at 0 %. Consumers
59941
60332
  * SKIP a null rather than coerce it.
59942
60333
  */
59943
- signalPercent: number().min(0).max(100).nullable(),
60334
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59944
60335
  /** Raw received signal strength in dBm, when the firmware reports one. */
59945
- rssiDbm: number().optional(),
60336
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
59946
60337
  /** Network name of a wireless link, when the firmware reports it. */
59947
60338
  ssid: string().optional(),
59948
60339
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
59949
- lastUpdated: number()
60340
+ lastUpdated: number().meta(readingMeta("none"))
59950
60341
  });
59951
60342
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
59952
60343
  deviceId: number(),
@@ -60265,7 +60656,7 @@ object({
60265
60656
  lastError: string().nullable(),
60266
60657
  /** Number of deliveries currently buffered server-side (rate-limited
60267
60658
  * notifiers may queue). 0 when the provider sends synchronously. */
60268
- queueDepth: number().int().nonnegative(),
60659
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
60269
60660
  /** Per-feature capability matrix — authoritative source for the
60270
60661
  * compose-form field gating. */
60271
60662
  supports: NotifierSupportsSchema
@@ -60315,15 +60706,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
60315
60706
  acceptedAt: number()
60316
60707
  });
60317
60708
  object({
60318
- value: number(),
60319
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
60320
- * Populated live from the upstream source on each state push.
60321
- * Absent for unitless sensors. */
60322
- unit: string().optional(),
60323
- /** Suggested decimal places for numeric display.
60324
- * Populated live from the upstream source when provided.
60325
- * Falls back to auto-formatting when absent. */
60326
- precision: number().int().min(0).max(10).optional(),
60709
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
60710
+ value: number().nullable().meta(readingMeta("device")),
60327
60711
  /** Ms epoch when the slice was last updated. */
60328
60712
  lastFetchedAt: number()
60329
60713
  });
@@ -60613,23 +60997,26 @@ object({
60613
60997
  /** Food currently in the bowl (grams). Null when the device has not
60614
60998
  * reported a reading yet. On dual-hopper models this is the combined
60615
60999
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
60616
- foodLevel: number().nullable(),
61000
+ foodLevel: number().nullable().meta(readingMeta("none")),
60617
61001
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
60618
61002
  * single-hopper models. */
60619
- food1: number().nullable(),
61003
+ food1: number().nullable().meta(readingMeta("none")),
60620
61004
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
60621
61005
  * single-hopper models. */
60622
- food2: number().nullable(),
61006
+ food2: number().nullable().meta(readingMeta("none")),
60623
61007
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
60624
61008
  * (`device_class: problem`, on = low). True when the bowl is empty /
60625
61009
  * below the feeder's low threshold. */
60626
61010
  lowFood: boolean(),
60627
61011
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
60628
61012
  * device has no battery reading. */
60629
- batteryPower: number().min(0).max(100).nullable(),
60630
- /** Days of desiccant life remaining. Null when the model has no
60631
- * desiccant sensor. */
60632
- desiccantLeftDays: number().nullable(),
61013
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61014
+ /**
61015
+ * Desiccant life remaining, stored in seconds (duration base, D944).
61016
+ * Providers convert from the days the feeder reports. Null when the
61017
+ * model has no desiccant sensor.
61018
+ */
61019
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
60633
61020
  /** True while a feed is in progress. */
60634
61021
  feeding: boolean(),
60635
61022
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -60640,7 +61027,7 @@ object({
60640
61027
  * with `errorCode` for consumers that want the raw integer. */
60641
61028
  error: string().nullable(),
60642
61029
  /** Raw device error code (0 / null = no error). */
60643
- errorCode: number().nullable(),
61030
+ errorCode: number().nullable().meta(readingMeta("none")),
60644
61031
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
60645
61032
  isDualHopper: boolean(),
60646
61033
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -60650,7 +61037,7 @@ object({
60650
61037
  /** Play a chime when dispensing. */
60651
61038
  feedSound: boolean(),
60652
61039
  /** Speaker / prompt volume level (device-scaled integer). */
60653
- volume: number(),
61040
+ volume: number().meta(readingMeta("none")),
60654
61041
  /** Ms epoch when the slice was last refreshed from the cloud. */
60655
61042
  lastFetchedAt: number()
60656
61043
  });
@@ -61087,36 +61474,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
61087
61474
  });
61088
61475
  object({
61089
61476
  /** Instantaneous power draw in watts. */
61090
- watts: number().optional().meta({
61091
- unit: "W",
61092
- label: "Power"
61093
- }),
61477
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
61094
61478
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61095
- kwhTotal: number().optional().meta({
61096
- unit: "kWh",
61097
- label: "Energy"
61098
- }),
61479
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61099
61480
  /** Voltage in volts. */
61100
- volts: number().optional(),
61481
+ volts: number().optional().meta(readingMeta("voltage")),
61101
61482
  /** Current in amperes. */
61102
- amps: number().optional(),
61103
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
61104
- va: number().optional(),
61483
+ amps: number().optional().meta(readingMeta("current")),
61484
+ /** Apparent power in volt-amperes. */
61485
+ va: number().optional().meta(readingMeta("apparent-power")),
61105
61486
  /** Power factor (0..1) if reported. */
61106
- powerFactor: number().min(0).max(1).optional(),
61487
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
61107
61488
  /** Ms epoch when the slice was last updated. */
61108
- lastFetchedAt: number(),
61109
- /** Live display unit of the single metric this slice carries (e.g. HA
61110
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
61111
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
61112
- * unit per slice is unambiguous. The UI prefers this over the role's
61113
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
61114
- unit: string().optional(),
61115
- /** Suggested decimal places for numeric display.
61116
- * Populated live from the upstream source when provided (e.g. HA
61117
- * `attributes.suggested_display_precision`). Falls back to
61118
- * auto-formatting when absent. */
61119
- precision: number().int().min(0).max(10).optional()
61489
+ lastFetchedAt: number()
61120
61490
  });
61121
61491
  DeviceType.Sensor;
61122
61492
  /**
@@ -61137,11 +61507,11 @@ DeviceType.Sensor;
61137
61507
  */
61138
61508
  var GpsLocationSchema = object({
61139
61509
  /** Latitude in decimal degrees, -90..90. */
61140
- latitude: number().min(-90).max(90),
61510
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
61141
61511
  /** Longitude in decimal degrees, -180..180. */
61142
- longitude: number().min(-180).max(180),
61512
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
61143
61513
  /** Reported accuracy in meters (lower = better). */
61144
- accuracyMeters: number().nonnegative()
61514
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
61145
61515
  });
61146
61516
  object({
61147
61517
  /** `home` / `not_home` / any user-defined zone name. */
@@ -61153,25 +61523,16 @@ object({
61153
61523
  gps: GpsLocationSchema.nullable(),
61154
61524
  /** Optional battery level of the tracking device (0..100). Null
61155
61525
  * when not reported. */
61156
- batteryPercent: number().min(0).max(100).nullable(),
61526
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61157
61527
  /** Ms epoch when the slice was last updated. */
61158
61528
  lastChangedAt: number()
61159
61529
  });
61160
61530
  DeviceType.Presence;
61161
61531
  object({
61162
- /** Current pressure in hPa. */
61163
- hpa: number(),
61532
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
61533
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
61164
61534
  /** Ms epoch when the slice was last updated. */
61165
- lastFetchedAt: number(),
61166
- /** Live display unit from the upstream source (e.g. HA
61167
- * `attributes.unit_of_measurement`). The UI prefers this over the
61168
- * role's canonical unit. Absent → fall back to the canonical unit. */
61169
- unit: string().optional(),
61170
- /** Suggested decimal places for numeric display.
61171
- * Populated live from the upstream source when provided (e.g. HA
61172
- * `attributes.suggested_display_precision`). Falls back to
61173
- * auto-formatting when absent. */
61174
- precision: number().int().min(0).max(10).optional()
61535
+ lastFetchedAt: number()
61175
61536
  });
61176
61537
  DeviceType.Sensor;
61177
61538
  /**
@@ -61504,8 +61865,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
61504
61865
  }), _void(), { kind: "mutation" });
61505
61866
  var PtzAutotrackSettingsSchema = object({
61506
61867
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
61507
- stopDelaySeconds: number().int().min(0).max(300),
61508
- disappearDelaySeconds: number().int().min(0).max(300)
61868
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
61869
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
61509
61870
  });
61510
61871
  /**
61511
61872
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -61562,7 +61923,7 @@ object({
61562
61923
  /** null = unknown — never coerced to false (D393). */
61563
61924
  running: boolean().nullable(),
61564
61925
  /** Epoch ms the current boolean began; null while `running` is null. */
61565
- since: number().nullable(),
61926
+ since: number().nullable().meta(readingMeta("none")),
61566
61927
  source: RunningStateSourceSchema.meta({ label: "Source" }),
61567
61928
  phase: string().nullable(),
61568
61929
  /** null until a reading has been published (D393). */
@@ -62760,9 +63121,9 @@ var RecordTriggerSchema = _enum([
62760
63121
  */
62761
63122
  var RecordWindowSchema = object({
62762
63123
  trigger: RecordTriggerSchema,
62763
- day: number().int().min(0).max(6),
62764
- startMinute: number().int().min(0).max(1439),
62765
- endMinute: number().int().min(1).max(1440)
63124
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
63125
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
63126
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
62766
63127
  });
62767
63128
  /** Status of one physical volume, as the camera itself describes it. */
62768
63129
  var OnboardStorageVolumeSchema = object({
@@ -62831,10 +63192,10 @@ object({
62831
63192
  /** Master "record to the card at all" switch. */
62832
63193
  enabled: boolean().nullable(),
62833
63194
  overwriteWhenFull: boolean().nullable(),
62834
- preRecordSec: number().nullable(),
62835
- postRecordSec: number().nullable(),
63195
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
63196
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
62836
63197
  /** Length of one recorded file, in minutes. */
62837
- segmentMinutes: number().nullable(),
63198
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
62838
63199
  /** The primary track's weekly windows, flattened. */
62839
63200
  windows: array(RecordWindowSchema),
62840
63201
  /**
@@ -62850,7 +63211,7 @@ object({
62850
63211
  * provider reporting a non-zero count also reports the schedule as not
62851
63212
  * writable.
62852
63213
  */
62853
- unreadableWindows: number(),
63214
+ unreadableWindows: number().meta(readingMeta("count")),
62854
63215
  /**
62855
63216
  * The camera is scheduled to record and has NO usable storage.
62856
63217
  *
@@ -63487,7 +63848,7 @@ var CamStreamDescriptorSchema = object({
63487
63848
  url: string().optional(),
63488
63849
  codec: string().optional(),
63489
63850
  resolution: CamStreamResolutionSchema.optional(),
63490
- fps: number().positive().optional(),
63851
+ fps: number().positive().optional().meta(readingMeta("rate")),
63491
63852
  label: string().optional(),
63492
63853
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
63493
63854
  deviceFeatures: array(string()).optional(),
@@ -63517,18 +63878,18 @@ var StreamProfileSchema = _enum([
63517
63878
  ]);
63518
63879
  /** The current encoding config of one stream profile. */
63519
63880
  var StreamProfileConfigSchema = object({
63520
- width: number(),
63521
- height: number(),
63881
+ width: number().meta(readingMeta("none")),
63882
+ height: number().meta(readingMeta("none")),
63522
63883
  codec: _enum(["h264", "h265"]),
63523
- framerate: number(),
63524
- bitrate: number(),
63884
+ framerate: number().meta(readingMeta("rate")),
63885
+ bitrate: number().meta(readingMeta("bitrate")),
63525
63886
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
63526
63887
  encoderProfile: _enum([
63527
63888
  "high",
63528
63889
  "main",
63529
63890
  "baseline"
63530
63891
  ]).optional(),
63531
- gop: number().optional(),
63892
+ gop: number().optional().meta(readingMeta("none")),
63532
63893
  /**
63533
63894
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
63534
63895
  *
@@ -64171,23 +64532,13 @@ object({
64171
64532
  });
64172
64533
  DeviceType.Sensor;
64173
64534
  object({
64174
- /** Current temperature in Celsius. */
64175
- celsius: number().meta({
64176
- unit: "°C",
64535
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64536
+ celsius: number().nullable().meta(readingMeta("temperature", {
64537
+ precision: 1,
64177
64538
  label: "Temperature"
64178
- }),
64539
+ })),
64179
64540
  /** Ms epoch when the slice was last updated (push or poll). */
64180
- lastFetchedAt: number(),
64181
- /** Live display unit from the upstream source (e.g. HA
64182
- * `attributes.unit_of_measurement`). The UI prefers this over the
64183
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
64184
- * Absent → the UI falls back to the role's canonical unit. */
64185
- unit: string().optional(),
64186
- /** Suggested decimal places for numeric display.
64187
- * Populated live from the upstream source when provided (e.g. HA
64188
- * `attributes.suggested_display_precision`). Falls back to
64189
- * auto-formatting when absent. */
64190
- precision: number().int().min(0).max(10).optional()
64541
+ lastFetchedAt: number()
64191
64542
  });
64192
64543
  DeviceType.Sensor, DeviceType.Thermostat;
64193
64544
  /**
@@ -64439,16 +64790,16 @@ object({
64439
64790
  ]),
64440
64791
  /** 0..100 battery percentage. Null when the device has no battery
64441
64792
  * reading. */
64442
- batteryLevel: number().min(0).max(100).nullable(),
64793
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64443
64794
  /** Current fan-speed token (provider-verbatim). Null when unknown or
64444
64795
  * the vacuum has no speed control. */
64445
64796
  fanSpeed: string().nullable(),
64446
64797
  /** Speed tokens the hardware accepts — drives the UI selector. */
64447
64798
  availableFanSpeeds: array(string()),
64448
64799
  /** 0..100 cleaning-completion percentage of the current task, or null. */
64449
- progressPercent: number().min(0).max(100).nullable(),
64800
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64450
64801
  /** Current error code (0 / null = no error). */
64451
- errorCode: number().nullable(),
64802
+ errorCode: number().nullable().meta(readingMeta("none")),
64452
64803
  /** Human label for {@link errorCode}, or null when none / undecodable. */
64453
64804
  errorLabel: string().nullable(),
64454
64805
  /** Ms epoch when the slice was last updated. */
@@ -64487,7 +64838,7 @@ object({
64487
64838
  ]),
64488
64839
  /** 0 = fully closed, 100 = fully open. Null when the device has no
64489
64840
  * intermediate position surface. */
64490
- position: number().min(0).max(100).nullable(),
64841
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64491
64842
  /** Ms epoch when the slice was last updated. */
64492
64843
  lastChangedAt: number()
64493
64844
  });
@@ -64515,9 +64866,9 @@ object({
64515
64866
  DeviceType.Sensor;
64516
64867
  object({
64517
64868
  /** Current measured temperature. Null when not reported. */
64518
- currentTemp: number().nullable(),
64869
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
64519
64870
  /** Target temperature setpoint. Null when no setpoint surface. */
64520
- targetTemp: number().nullable(),
64871
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
64521
64872
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
64522
64873
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
64523
64874
  * device reports an unknown state. */
@@ -64528,9 +64879,9 @@ object({
64528
64879
  * has no away surface. */
64529
64880
  away: boolean().nullable(),
64530
64881
  /** HA `min_temp` attribute. Null when not reported. */
64531
- minTemp: number().nullable(),
64882
+ minTemp: number().nullable().meta(readingMeta("temperature")),
64532
64883
  /** HA `max_temp` attribute. Null when not reported. */
64533
- maxTemp: number().nullable(),
64884
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
64534
64885
  /** Ms epoch when the slice was last updated. */
64535
64886
  lastChangedAt: number()
64536
64887
  });
@@ -64554,25 +64905,18 @@ DeviceType.WaterHeater, method(object({
64554
64905
  auth: "protected"
64555
64906
  });
64556
64907
  object({
64557
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
64558
- * when no condition has been reported yet. */
64908
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
64559
64909
  condition: string().nullable(),
64560
- /** Current temperature in the reported unit. Null when not provided. */
64561
- temperature: number().nullable(),
64562
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
64563
- temperatureUnit: string().nullable(),
64910
+ /** Current temperature in Celsius. Null when not provided or refused. */
64911
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
64564
64912
  /** Relative humidity (0..100). Null when not provided. */
64565
- humidity: number().min(0).max(100).nullable(),
64566
- /** Barometric pressure in the reported unit. Null when not provided. */
64567
- pressure: number().nullable(),
64568
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
64569
- pressureUnit: string().nullable(),
64570
- /** Wind speed in the reported unit. Null when not provided. */
64571
- windSpeed: number().nullable(),
64572
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
64573
- windSpeedUnit: string().nullable(),
64913
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
64914
+ /** Barometric pressure in hPa. Null when not provided or refused. */
64915
+ pressure: number().nullable().meta(readingMeta("pressure")),
64916
+ /** Wind speed in metres per second. Null when not provided or refused. */
64917
+ windSpeed: number().nullable().meta(readingMeta("speed")),
64574
64918
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
64575
- windBearing: number().nullable(),
64919
+ windBearing: number().nullable().meta(readingMeta("angle")),
64576
64920
  /** Ms epoch when the slice was last updated. */
64577
64921
  lastFetchedAt: number()
64578
64922
  });
@@ -65484,6 +65828,13 @@ Object.freeze({
65484
65828
  access: "view",
65485
65829
  role: "admin"
65486
65830
  },
65831
+ "addons.getPackageDocs": {
65832
+ capName: "addons",
65833
+ capScope: "system",
65834
+ addonId: null,
65835
+ access: "view",
65836
+ role: "admin"
65837
+ },
65487
65838
  "addons.getVersions": {
65488
65839
  capName: "addons",
65489
65840
  capScope: "system",
@@ -68592,6 +68943,20 @@ Object.freeze({
68592
68943
  access: "view",
68593
68944
  role: "viewer"
68594
68945
  },
68946
+ "groupCommands.getStatus": {
68947
+ capName: "group-commands",
68948
+ capScope: "device",
68949
+ addonId: null,
68950
+ access: "view",
68951
+ role: "viewer"
68952
+ },
68953
+ "groupStatus.getStatus": {
68954
+ capName: "group-status",
68955
+ capScope: "device",
68956
+ addonId: null,
68957
+ access: "view",
68958
+ role: "viewer"
68959
+ },
68595
68960
  "heatingValve.getStatus": {
68596
68961
  capName: "heating-valve",
68597
68962
  capScope: "device",
@@ -78414,6 +78779,37 @@ var SENSOR_CAP_VOCABULARY = {
78414
78779
  icon: "flame"
78415
78780
  }]
78416
78781
  },
78782
+ "group-commands": {
78783
+ scope: "device",
78784
+ noun: t("Group command", "Comando di gruppo")
78785
+ },
78786
+ "group-status": {
78787
+ scope: "device",
78788
+ noun: t("Group", "Gruppo"),
78789
+ state: [
78790
+ {
78791
+ kind: "numeric",
78792
+ field: "included",
78793
+ quantity: t("the members reporting", "i membri che rispondono"),
78794
+ label: t("Reporting", "Rispondono"),
78795
+ icon: "users"
78796
+ },
78797
+ {
78798
+ kind: "numeric",
78799
+ field: "excluded",
78800
+ quantity: t("the members left out", "i membri esclusi"),
78801
+ label: t("Excluded", "Esclusi"),
78802
+ icon: "users"
78803
+ },
78804
+ {
78805
+ kind: "numeric",
78806
+ field: "total",
78807
+ quantity: t("the member count", "il numero di membri"),
78808
+ label: t("Members", "Membri"),
78809
+ icon: "sigma"
78810
+ }
78811
+ ]
78812
+ },
78417
78813
  "humidity-sensor": {
78418
78814
  scope: "device",
78419
78815
  noun: t("Humidity", "Umidità"),
@@ -78447,7 +78843,6 @@ var SENSOR_CAP_VOCABULARY = {
78447
78843
  field: "value",
78448
78844
  quantity: t("the reading", "il valore"),
78449
78845
  label: t("Reading", "Valore"),
78450
- unitField: "unit",
78451
78846
  icon: "gauge"
78452
78847
  }]
78453
78848
  },
@@ -78583,7 +78978,7 @@ var SENSOR_CAP_VOCABULARY = {
78583
78978
  field: "temperature",
78584
78979
  quantity: t("the outdoor temperature", "la temperatura esterna"),
78585
78980
  label: t("Temperature", "Temperatura"),
78586
- unitField: "temperatureUnit",
78981
+ unit: "°C",
78587
78982
  icon: "thermometer"
78588
78983
  },
78589
78984
  {
@@ -78599,7 +78994,7 @@ var SENSOR_CAP_VOCABULARY = {
78599
78994
  field: "windSpeed",
78600
78995
  quantity: t("the wind speed", "la velocità del vento"),
78601
78996
  label: t("Wind", "Vento"),
78602
- unitField: "windSpeedUnit",
78997
+ unit: "m/s",
78603
78998
  icon: "wind"
78604
78999
  },
78605
79000
  {
@@ -78607,7 +79002,7 @@ var SENSOR_CAP_VOCABULARY = {
78607
79002
  field: "pressure",
78608
79003
  quantity: t("the air pressure", "la pressione atmosferica"),
78609
79004
  label: t("Pressure", "Pressione"),
78610
- unitField: "pressureUnit",
79005
+ unit: "hPa",
78611
79006
  icon: "gauge"
78612
79007
  }
78613
79008
  ]
@@ -79880,6 +80275,16 @@ Object.freeze({
79880
80275
  form: "single",
79881
80276
  optional: false
79882
80277
  }],
80278
+ "groupCommands.getStatus": [{
80279
+ name: "deviceId",
80280
+ form: "single",
80281
+ optional: false
80282
+ }],
80283
+ "groupStatus.getStatus": [{
80284
+ name: "deviceId",
80285
+ form: "single",
80286
+ optional: false
80287
+ }],
79883
80288
  "heatingValve.getStatus": [{
79884
80289
  name: "deviceId",
79885
80290
  form: "single",
@@ -81864,6 +82269,8 @@ new Map(Object.entries({
81864
82269
  "detection-blind": "device-events",
81865
82270
  "motion-noisy": "device-events",
81866
82271
  "motion-calm": "device-events",
82272
+ "package-llm-silent": "device-events",
82273
+ "package-llm-answered": "device-events",
81867
82274
  "recording-stalled": "device-events",
81868
82275
  "recording-healthy": "device-events",
81869
82276
  "recording-degraded": "device-events",
@@ -81947,6 +82354,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
81947
82354
  label: "Motion without a subject",
81948
82355
  kinds: ["motion-noisy", "motion-calm"]
81949
82356
  },
82357
+ {
82358
+ id: "package-llm",
82359
+ family: "device-events",
82360
+ label: "Package confirm",
82361
+ kinds: ["package-llm-silent", "package-llm-answered"]
82362
+ },
81950
82363
  {
81951
82364
  id: "recording",
81952
82365
  family: "device-events",
@@ -82128,6 +82541,8 @@ var DEVICE_EVENTS = [
82128
82541
  "detection-blind",
82129
82542
  "motion-noisy",
82130
82543
  "motion-calm",
82544
+ "package-llm-silent",
82545
+ "package-llm-answered",
82131
82546
  "recording-stalled",
82132
82547
  "recording-healthy",
82133
82548
  "recording-degraded",
@@ -82396,6 +82811,15 @@ new Map([
82396
82811
  snapshotLabel: "Cameras with constant motion now",
82397
82812
  snapshotSection: null
82398
82813
  },
82814
+ {
82815
+ id: "package-llm-confirm",
82816
+ family: "device-events",
82817
+ label: "Package confirm (not answering / answering again)",
82818
+ bad: ["package-llm-silent"],
82819
+ recovery: ["package-llm-answered"],
82820
+ snapshotLabel: "Cameras whose package confirm is not answering",
82821
+ snapshotSection: null
82822
+ },
82399
82823
  {
82400
82824
  id: "recording-health",
82401
82825
  family: "device-events",