@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.
@@ -46,7 +46,7 @@ let node_fs = require("node:fs");
46
46
  node_fs = __toESM(node_fs);
47
47
  let node_path = require("node:path");
48
48
  node_path = __toESM(node_path);
49
- //#region ../types/dist/event-category-DHXxB8t9.mjs
49
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
50
50
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
51
51
  EventCategory["SystemBoot"] = "system.boot";
52
52
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -372,6 +372,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
372
372
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
373
373
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
374
374
  /**
375
+ * A camera's package LLM confirm has failed closed three times in a row
376
+ * (D931, 2026-10-26). One edge per camera until
377
+ * {@link PipelinePackageLlmAnswered}.
378
+ */
379
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
380
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
381
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
382
+ /**
375
383
  * Per-camera pipeline config was mutated by the orchestrator
376
384
  * (a settings change via `setAgentInferenceDevices` /
377
385
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5614,7 +5622,7 @@ var ZodIssueCode = {
5614
5622
  var ZodFirstPartyTypeKind;
5615
5623
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5616
5624
  //#endregion
5617
- //#region ../types/dist/sleep-BErAxhul.mjs
5625
+ //#region ../types/dist/sleep-bykKrTgu.mjs
5618
5626
  /**
5619
5627
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5620
5628
  * window to float samples (D455).
@@ -8356,6 +8364,99 @@ function defineEntryKind(name, spec) {
8356
8364
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8357
8365
  return defined;
8358
8366
  }
8367
+ var READING_QUANTITIES = [
8368
+ "temperature",
8369
+ "pressure",
8370
+ "speed",
8371
+ "precipitation",
8372
+ "precipitation-rate",
8373
+ "length",
8374
+ "area",
8375
+ "illuminance",
8376
+ "irradiance",
8377
+ "power",
8378
+ "energy",
8379
+ "voltage",
8380
+ "current",
8381
+ "apparent-power",
8382
+ "humidity",
8383
+ "percentage",
8384
+ "concentration-volume",
8385
+ "concentration-mass",
8386
+ "concentration-ppb",
8387
+ "volume",
8388
+ "duration",
8389
+ "duration-ms",
8390
+ "information",
8391
+ "sound-level",
8392
+ "signal-level",
8393
+ "colour-temperature",
8394
+ "angle",
8395
+ "bitrate",
8396
+ "rate",
8397
+ "mass",
8398
+ "index",
8399
+ "ratio",
8400
+ "count",
8401
+ "none",
8402
+ "device"
8403
+ ];
8404
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8405
+ new Set(READING_QUANTITIES);
8406
+ /**
8407
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8408
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8409
+ */
8410
+ var QUANTITY_BASE = {
8411
+ temperature: "°C",
8412
+ pressure: "hPa",
8413
+ speed: "m/s",
8414
+ precipitation: "mm",
8415
+ "precipitation-rate": "mm/h",
8416
+ length: "m",
8417
+ area: "m²",
8418
+ illuminance: "lx",
8419
+ irradiance: "W/m²",
8420
+ power: "W",
8421
+ energy: "kWh",
8422
+ voltage: "V",
8423
+ current: "A",
8424
+ "apparent-power": "VA",
8425
+ humidity: "%",
8426
+ percentage: "%",
8427
+ "concentration-volume": "ppm",
8428
+ "concentration-mass": "µg/m³",
8429
+ "concentration-ppb": "ppb",
8430
+ volume: "L",
8431
+ duration: "s",
8432
+ "duration-ms": "ms",
8433
+ information: "B",
8434
+ "sound-level": "dBFS",
8435
+ "signal-level": "dBm",
8436
+ "colour-temperature": "mired",
8437
+ angle: "°",
8438
+ bitrate: "kbps",
8439
+ rate: "fps",
8440
+ mass: "kg",
8441
+ index: "none",
8442
+ ratio: "none",
8443
+ count: "none",
8444
+ none: "none",
8445
+ device: "device"
8446
+ };
8447
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8448
+ function baseUnitOf(quantity) {
8449
+ const symbol = QUANTITY_BASE[quantity];
8450
+ if (symbol === "none" || symbol === "device") return void 0;
8451
+ return symbol;
8452
+ }
8453
+ function readingMeta(quantity, extras) {
8454
+ return {
8455
+ quantity,
8456
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8457
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8458
+ };
8459
+ }
8359
8460
  /** Shared Zod schemas used across streaming capabilities. */
8360
8461
  var CamProfileSchema = _enum([
8361
8462
  "high",
@@ -8383,8 +8484,8 @@ var CamStreamKindSchema = _enum([
8383
8484
  "derived"
8384
8485
  ]);
8385
8486
  var CamStreamResolutionSchema = object({
8386
- width: number().int().positive(),
8387
- height: number().int().positive()
8487
+ width: number().int().positive().meta(readingMeta("none")),
8488
+ height: number().int().positive().meta(readingMeta("none"))
8388
8489
  });
8389
8490
  var CameraStreamSchema = object({
8390
8491
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -8898,6 +8999,8 @@ var CAP_NAMES = Object.freeze([
8898
8999
  "filesystem-browse",
8899
9000
  "flood",
8900
9001
  "gas",
9002
+ "group-commands",
9003
+ "group-status",
8901
9004
  "heating-valve",
8902
9005
  "humidifier",
8903
9006
  "humidity-sensor",
@@ -9027,14 +9130,6 @@ Object.freeze({
9027
9130
  {
9028
9131
  "path": "aqi",
9029
9132
  "valueType": "number"
9030
- },
9031
- {
9032
- "path": "unit",
9033
- "valueType": "string"
9034
- },
9035
- {
9036
- "path": "precision",
9037
- "valueType": "number"
9038
9133
  }
9039
9134
  ],
9040
9135
  "alarm-panel": [
@@ -9070,20 +9165,10 @@ Object.freeze({
9070
9165
  "valueType": "boolean"
9071
9166
  }
9072
9167
  ],
9073
- "ambient-light-sensor": [
9074
- {
9075
- "path": "lux",
9076
- "valueType": "number"
9077
- },
9078
- {
9079
- "path": "unit",
9080
- "valueType": "string"
9081
- },
9082
- {
9083
- "path": "precision",
9084
- "valueType": "number"
9085
- }
9086
- ],
9168
+ "ambient-light-sensor": [{
9169
+ "path": "lux",
9170
+ "valueType": "number"
9171
+ }],
9087
9172
  "audio-metrics": [
9088
9173
  {
9089
9174
  "path": "ts",
@@ -9163,6 +9248,10 @@ Object.freeze({
9163
9248
  "path": "lastUpdated",
9164
9249
  "valueType": "number"
9165
9250
  },
9251
+ {
9252
+ "path": "volts",
9253
+ "valueType": "number"
9254
+ },
9166
9255
  {
9167
9256
  "path": "lastContactAt",
9168
9257
  "valueType": "number"
@@ -9405,10 +9494,6 @@ Object.freeze({
9405
9494
  "datetime"
9406
9495
  ]
9407
9496
  },
9408
- {
9409
- "path": "unit",
9410
- "valueType": "string"
9411
- },
9412
9497
  {
9413
9498
  "path": "min",
9414
9499
  "valueType": "number"
@@ -9420,19 +9505,6 @@ Object.freeze({
9420
9505
  {
9421
9506
  "path": "step",
9422
9507
  "valueType": "number"
9423
- },
9424
- {
9425
- "path": "precision",
9426
- "valueType": "number"
9427
- },
9428
- {
9429
- "path": "format",
9430
- "valueType": "enum",
9431
- "enumValues": [
9432
- "date",
9433
- "time",
9434
- "datetime"
9435
- ]
9436
9508
  }
9437
9509
  ],
9438
9510
  "cover": [
@@ -9673,13 +9745,8 @@ Object.freeze({
9673
9745
  "path": "value",
9674
9746
  "valueType": "string"
9675
9747
  }, {
9676
- "path": "format",
9677
- "valueType": "enum",
9678
- "enumValues": [
9679
- "date",
9680
- "time",
9681
- "datetime"
9682
- ]
9748
+ "path": "timestamp",
9749
+ "valueType": "boolean"
9683
9750
  }],
9684
9751
  "event-emitter": [
9685
9752
  {
@@ -9752,6 +9819,20 @@ Object.freeze({
9752
9819
  "path": "detected",
9753
9820
  "valueType": "boolean"
9754
9821
  }],
9822
+ "group-status": [
9823
+ {
9824
+ "path": "included",
9825
+ "valueType": "number"
9826
+ },
9827
+ {
9828
+ "path": "excluded",
9829
+ "valueType": "number"
9830
+ },
9831
+ {
9832
+ "path": "total",
9833
+ "valueType": "number"
9834
+ }
9835
+ ],
9755
9836
  "heating-valve": [{
9756
9837
  "path": "position",
9757
9838
  "valueType": "number"
@@ -9786,20 +9867,10 @@ Object.freeze({
9786
9867
  "valueType": "number"
9787
9868
  }
9788
9869
  ],
9789
- "humidity-sensor": [
9790
- {
9791
- "path": "percent",
9792
- "valueType": "number"
9793
- },
9794
- {
9795
- "path": "unit",
9796
- "valueType": "string"
9797
- },
9798
- {
9799
- "path": "precision",
9800
- "valueType": "number"
9801
- }
9802
- ],
9870
+ "humidity-sensor": [{
9871
+ "path": "percent",
9872
+ "valueType": "number"
9873
+ }],
9803
9874
  "image": [{
9804
9875
  "path": "url",
9805
9876
  "valueType": "string"
@@ -10138,20 +10209,10 @@ Object.freeze({
10138
10209
  "valueType": "boolean"
10139
10210
  }
10140
10211
  ],
10141
- "numeric-sensor": [
10142
- {
10143
- "path": "value",
10144
- "valueType": "number"
10145
- },
10146
- {
10147
- "path": "unit",
10148
- "valueType": "string"
10149
- },
10150
- {
10151
- "path": "precision",
10152
- "valueType": "number"
10153
- }
10154
- ],
10212
+ "numeric-sensor": [{
10213
+ "path": "value",
10214
+ "valueType": "number"
10215
+ }],
10155
10216
  "pet-feeder": [
10156
10217
  {
10157
10218
  "path": "foodLevel",
@@ -10243,14 +10304,6 @@ Object.freeze({
10243
10304
  {
10244
10305
  "path": "powerFactor",
10245
10306
  "valueType": "number"
10246
- },
10247
- {
10248
- "path": "unit",
10249
- "valueType": "string"
10250
- },
10251
- {
10252
- "path": "precision",
10253
- "valueType": "number"
10254
10307
  }
10255
10308
  ],
10256
10309
  "presence": [
@@ -10279,20 +10332,10 @@ Object.freeze({
10279
10332
  "valueType": "number"
10280
10333
  }
10281
10334
  ],
10282
- "pressure-sensor": [
10283
- {
10284
- "path": "hpa",
10285
- "valueType": "number"
10286
- },
10287
- {
10288
- "path": "unit",
10289
- "valueType": "string"
10290
- },
10291
- {
10292
- "path": "precision",
10293
- "valueType": "number"
10294
- }
10295
- ],
10335
+ "pressure-sensor": [{
10336
+ "path": "hpa",
10337
+ "valueType": "number"
10338
+ }],
10296
10339
  "privacy-mask": [{
10297
10340
  "path": "enabled",
10298
10341
  "valueType": "boolean"
@@ -10603,20 +10646,10 @@ Object.freeze({
10603
10646
  "path": "tampered",
10604
10647
  "valueType": "boolean"
10605
10648
  }],
10606
- "temperature-sensor": [
10607
- {
10608
- "path": "celsius",
10609
- "valueType": "number"
10610
- },
10611
- {
10612
- "path": "unit",
10613
- "valueType": "string"
10614
- },
10615
- {
10616
- "path": "precision",
10617
- "valueType": "number"
10618
- }
10619
- ],
10649
+ "temperature-sensor": [{
10650
+ "path": "celsius",
10651
+ "valueType": "number"
10652
+ }],
10620
10653
  "update": [
10621
10654
  {
10622
10655
  "path": "currentVersion",
@@ -10727,10 +10760,6 @@ Object.freeze({
10727
10760
  "path": "temperature",
10728
10761
  "valueType": "number"
10729
10762
  },
10730
- {
10731
- "path": "temperatureUnit",
10732
- "valueType": "string"
10733
- },
10734
10763
  {
10735
10764
  "path": "humidity",
10736
10765
  "valueType": "number"
@@ -10739,18 +10768,10 @@ Object.freeze({
10739
10768
  "path": "pressure",
10740
10769
  "valueType": "number"
10741
10770
  },
10742
- {
10743
- "path": "pressureUnit",
10744
- "valueType": "string"
10745
- },
10746
10771
  {
10747
10772
  "path": "windSpeed",
10748
10773
  "valueType": "number"
10749
10774
  },
10750
- {
10751
- "path": "windSpeedUnit",
10752
- "valueType": "string"
10753
- },
10754
10775
  {
10755
10776
  "path": "windBearing",
10756
10777
  "valueType": "number"
@@ -11143,7 +11164,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11143
11164
  return DeviceRole;
11144
11165
  }({});
11145
11166
  //#endregion
11146
- //#region ../types/dist/composition-kEl9mevu.mjs
11167
+ //#region ../types/dist/composition-OhpuespA.mjs
11147
11168
  /**
11148
11169
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11149
11170
  *
@@ -11898,15 +11919,74 @@ function validateExpressionSource(src) {
11898
11919
  }
11899
11920
  return null;
11900
11921
  }
11901
- /** A composed device's stableId is this prefix plus the block id. */
11902
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11903
- /** Longest snippet a `code` source may carry (slice 4). */
11904
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11922
+ /**
11923
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11924
+ * id. Split out so helper schemas can name it without a cycle through
11925
+ * `composition.ts` (D659).
11926
+ */
11905
11927
  var CompositionSourceRefSchema = object({
11906
11928
  /** The owning addon; `stableId` is unique only within it. */
11907
11929
  addonId: string().min(1),
11908
11930
  stableId: string().min(1)
11909
11931
  });
11932
+ var GROUP_TEMPLATE_IDS = [
11933
+ "temperature",
11934
+ "humidity",
11935
+ "pressure",
11936
+ "illuminance",
11937
+ "power",
11938
+ "energy",
11939
+ "battery",
11940
+ "numeric",
11941
+ "motion",
11942
+ "contact",
11943
+ "leak",
11944
+ "smoke",
11945
+ "occupancy",
11946
+ "binary",
11947
+ "switch",
11948
+ "light"
11949
+ ];
11950
+ var NUMERIC_AGGREGATES = [
11951
+ "mean",
11952
+ "median",
11953
+ "min",
11954
+ "max",
11955
+ "sum"
11956
+ ];
11957
+ var BINARY_AGGREGATES = ["any", "all"];
11958
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11959
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11960
+ var AGGREGATE_REDUCERS = [
11961
+ "mean",
11962
+ "median",
11963
+ "min",
11964
+ "max",
11965
+ "sum",
11966
+ "any",
11967
+ "all"
11968
+ ];
11969
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11970
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11971
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11972
+ /** One member. The field is stored so occupancy can name motion or binary. */
11973
+ var HelperMemberSchema = object({
11974
+ source: CompositionSourceRefSchema,
11975
+ cap: string().min(1),
11976
+ fieldPath: string().min(1)
11977
+ });
11978
+ var HelperSpecSchema = discriminatedUnion("kind", [object({
11979
+ kind: literal("group"),
11980
+ templateId: GroupTemplateIdSchema,
11981
+ aggregate: AggregateReducerSchema,
11982
+ members: array(HelperMemberSchema).min(1).max(64),
11983
+ /** Actuator groups only. Readings leave this unset. */
11984
+ active: ActiveCriterionSchema.optional()
11985
+ })]);
11986
+ /** A composed device's stableId is this prefix plus the block id. */
11987
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11988
+ /** Longest snippet a `code` source may carry (slice 4). */
11989
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11910
11990
  /** One field of one capability of one source device. */
11911
11991
  var CompositionFieldReadSchema = object({
11912
11992
  source: CompositionSourceRefSchema,
@@ -11950,7 +12030,33 @@ var CompositionCodeSourceSchema = object({
11950
12030
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11951
12031
  CompositionFromSourceSchema,
11952
12032
  CompositionExpressionSourceSchema,
11953
- CompositionCodeSourceSchema
12033
+ CompositionCodeSourceSchema,
12034
+ object({
12035
+ kind: literal("aggregate"),
12036
+ reducer: _enum([
12037
+ "mean",
12038
+ "median",
12039
+ "min",
12040
+ "max",
12041
+ "sum",
12042
+ "any",
12043
+ "all"
12044
+ ]),
12045
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
12046
+ /** `value` is the reading; the others are the member counts. */
12047
+ publish: _enum([
12048
+ "value",
12049
+ "included",
12050
+ "excluded",
12051
+ "total"
12052
+ ]),
12053
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
12054
+ empty: _enum([
12055
+ "null",
12056
+ "omit",
12057
+ "offline"
12058
+ ])
12059
+ })
11954
12060
  ]);
11955
12061
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11956
12062
  kind: literal("forward"),
@@ -11996,7 +12102,13 @@ var CompositionSchema = object({
11996
12102
  kind: literal("existing"),
11997
12103
  device: CompositionSourceRefSchema
11998
12104
  })]),
11999
- features: array(CompositionFeatureSchema).min(1).max(16)
12105
+ features: array(CompositionFeatureSchema).min(1).max(16),
12106
+ /**
12107
+ * Present when the block is a helper template. Absent on a free
12108
+ * composition (the room example, device 4603). The editor reads this,
12109
+ * never the compiled features.
12110
+ */
12111
+ helper: HelperSpecSchema.optional()
12000
12112
  }).superRefine((composition, ctx) => {
12001
12113
  const seen = /* @__PURE__ */ new Set();
12002
12114
  composition.features.forEach((feature, index) => {
@@ -18147,7 +18259,11 @@ var CompositionProblemSchema = object({
18147
18259
  "item-field-reserved",
18148
18260
  "target-missing",
18149
18261
  "target-unreadable",
18150
- "not-replaceable"
18262
+ "not-replaceable",
18263
+ "native-capability-replace",
18264
+ "mixed-quantity",
18265
+ "helper-mismatch",
18266
+ "member-constraint"
18151
18267
  ]),
18152
18268
  path: string(),
18153
18269
  message: string()
@@ -18168,7 +18284,8 @@ var CompositionUnavailableModeSchema = _enum([
18168
18284
  "null",
18169
18285
  "offline",
18170
18286
  "unknown",
18171
- "release"
18287
+ "release",
18288
+ "omit"
18172
18289
  ]);
18173
18290
  /**
18174
18291
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -18205,9 +18322,11 @@ var CompositionFieldPlanSchema = object({
18205
18322
  reads: array(CompositionFieldReadSchema)
18206
18323
  });
18207
18324
  /**
18208
- * What a feature does to its target (D663): `new` builds a capability on a new
18209
- * device; on an existing device it `add`s a capability the device lacks, or
18210
- * `replace`s named fields of one it has natively.
18325
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
18326
+ * new device; on an existing device it `add`s a capability the device lacks.
18327
+ * Replacing a capability the device already serves is refused
18328
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
18329
+ * wire for a claim the hub already holds.
18211
18330
  */
18212
18331
  var CompositionFeatureModeSchema = _enum([
18213
18332
  "new",
@@ -18380,6 +18499,275 @@ Object.freeze({
18380
18499
  result: ["number", "null"]
18381
18500
  }
18382
18501
  });
18502
+ /**
18503
+ * One catalog row per base reading a group helper can build (D944 §3).
18504
+ * Data, not a form default written into the block.
18505
+ */
18506
+ var NOW = {
18507
+ fieldPath: "lastFetchedAt",
18508
+ expr: "now"
18509
+ };
18510
+ var CHANGED = {
18511
+ fieldPath: "lastChangedAt",
18512
+ expr: "now"
18513
+ };
18514
+ function numeric(input) {
18515
+ return {
18516
+ ...input,
18517
+ family: "numeric",
18518
+ deviceType: DeviceType.Sensor,
18519
+ memberDeviceType: DeviceType.Sensor,
18520
+ commandCaps: [],
18521
+ baseUnit: baseUnitOf(input.quantity),
18522
+ aggregates: NUMERIC_AGGREGATES,
18523
+ accept: [{
18524
+ cap: input.outputCap,
18525
+ fieldPath: input.outputField
18526
+ }]
18527
+ };
18528
+ }
18529
+ function binary(input) {
18530
+ return {
18531
+ ...input,
18532
+ family: "binary",
18533
+ deviceType: DeviceType.Sensor,
18534
+ memberDeviceType: DeviceType.Sensor,
18535
+ commandCaps: [],
18536
+ quantity: "none",
18537
+ baseUnit: void 0,
18538
+ aggregates: BINARY_AGGREGATES,
18539
+ empty: "offline"
18540
+ };
18541
+ }
18542
+ var GROUP_TEMPLATES = [
18543
+ numeric({
18544
+ id: "temperature",
18545
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18546
+ role: DeviceRole.TemperatureSensor,
18547
+ outputCap: "temperature-sensor",
18548
+ outputField: "celsius",
18549
+ quantity: "temperature",
18550
+ defaultAggregate: "mean",
18551
+ empty: "null",
18552
+ companions: [NOW]
18553
+ }),
18554
+ numeric({
18555
+ id: "humidity",
18556
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18557
+ role: DeviceRole.HumiditySensor,
18558
+ outputCap: "humidity-sensor",
18559
+ outputField: "percent",
18560
+ quantity: "humidity",
18561
+ defaultAggregate: "mean",
18562
+ empty: "null",
18563
+ companions: [NOW]
18564
+ }),
18565
+ numeric({
18566
+ id: "pressure",
18567
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18568
+ role: DeviceRole.PressureSensor,
18569
+ outputCap: "pressure-sensor",
18570
+ outputField: "hpa",
18571
+ quantity: "pressure",
18572
+ defaultAggregate: "mean",
18573
+ empty: "null",
18574
+ companions: [NOW]
18575
+ }),
18576
+ numeric({
18577
+ id: "illuminance",
18578
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18579
+ role: DeviceRole.AmbientLightSensor,
18580
+ outputCap: "ambient-light-sensor",
18581
+ outputField: "lux",
18582
+ quantity: "illuminance",
18583
+ defaultAggregate: "mean",
18584
+ empty: "null",
18585
+ companions: [NOW]
18586
+ }),
18587
+ numeric({
18588
+ id: "power",
18589
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18590
+ role: DeviceRole.PowerSensor,
18591
+ outputCap: "power-meter",
18592
+ outputField: "watts",
18593
+ quantity: "power",
18594
+ defaultAggregate: "sum",
18595
+ empty: "omit",
18596
+ companions: [NOW]
18597
+ }),
18598
+ numeric({
18599
+ id: "energy",
18600
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18601
+ role: DeviceRole.EnergySensor,
18602
+ outputCap: "power-meter",
18603
+ outputField: "kwhTotal",
18604
+ quantity: "energy",
18605
+ defaultAggregate: "sum",
18606
+ empty: "omit",
18607
+ companions: [NOW]
18608
+ }),
18609
+ numeric({
18610
+ id: "battery",
18611
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18612
+ role: DeviceRole.BatterySensor,
18613
+ outputCap: "battery",
18614
+ outputField: "percentage",
18615
+ quantity: "percentage",
18616
+ defaultAggregate: "mean",
18617
+ empty: "null",
18618
+ companions: [
18619
+ {
18620
+ fieldPath: "charging",
18621
+ expr: "\"unknown\""
18622
+ },
18623
+ {
18624
+ fieldPath: "sleeping",
18625
+ expr: "false"
18626
+ },
18627
+ {
18628
+ fieldPath: "lastUpdated",
18629
+ expr: "now"
18630
+ }
18631
+ ]
18632
+ }),
18633
+ numeric({
18634
+ id: "numeric",
18635
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18636
+ role: DeviceRole.NumericSensor,
18637
+ outputCap: "numeric-sensor",
18638
+ outputField: "value",
18639
+ quantity: "device",
18640
+ defaultAggregate: "mean",
18641
+ empty: "null",
18642
+ companions: [NOW]
18643
+ }),
18644
+ binary({
18645
+ id: "motion",
18646
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18647
+ role: DeviceRole.MotionSensor,
18648
+ outputCap: "motion",
18649
+ outputField: "detected",
18650
+ defaultAggregate: "any",
18651
+ accept: [{
18652
+ cap: "motion",
18653
+ fieldPath: "detected"
18654
+ }],
18655
+ companions: [{
18656
+ fieldPath: "lastDetectedAt",
18657
+ expr: "null"
18658
+ }, {
18659
+ fieldPath: "autoClearAfterMs",
18660
+ expr: "null"
18661
+ }]
18662
+ }),
18663
+ binary({
18664
+ id: "contact",
18665
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18666
+ role: DeviceRole.ContactSensor,
18667
+ outputCap: "contact",
18668
+ outputField: "entryOpen",
18669
+ defaultAggregate: "any",
18670
+ accept: [{
18671
+ cap: "contact",
18672
+ fieldPath: "entryOpen"
18673
+ }],
18674
+ companions: [CHANGED]
18675
+ }),
18676
+ binary({
18677
+ id: "leak",
18678
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18679
+ role: DeviceRole.LeakSensor,
18680
+ outputCap: "flood",
18681
+ outputField: "flooded",
18682
+ defaultAggregate: "any",
18683
+ accept: [{
18684
+ cap: "flood",
18685
+ fieldPath: "flooded"
18686
+ }],
18687
+ companions: [CHANGED]
18688
+ }),
18689
+ binary({
18690
+ id: "smoke",
18691
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18692
+ role: DeviceRole.SmokeSensor,
18693
+ outputCap: "smoke",
18694
+ outputField: "detected",
18695
+ defaultAggregate: "any",
18696
+ accept: [{
18697
+ cap: "smoke",
18698
+ fieldPath: "detected"
18699
+ }],
18700
+ companions: [CHANGED]
18701
+ }),
18702
+ binary({
18703
+ id: "occupancy",
18704
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18705
+ role: DeviceRole.BinarySensor,
18706
+ outputCap: "binary",
18707
+ outputField: "on",
18708
+ defaultAggregate: "any",
18709
+ accept: [{
18710
+ cap: "motion",
18711
+ fieldPath: "detected"
18712
+ }, {
18713
+ cap: "binary",
18714
+ fieldPath: "on"
18715
+ }],
18716
+ companions: [CHANGED]
18717
+ }),
18718
+ binary({
18719
+ id: "binary",
18720
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18721
+ role: DeviceRole.BinarySensor,
18722
+ outputCap: "binary",
18723
+ outputField: "on",
18724
+ defaultAggregate: "any",
18725
+ accept: [{
18726
+ cap: "binary",
18727
+ fieldPath: "on"
18728
+ }],
18729
+ companions: [CHANGED]
18730
+ }),
18731
+ actuator({
18732
+ id: "switch",
18733
+ 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.",
18734
+ deviceType: DeviceType.Switch,
18735
+ outputCap: "switch",
18736
+ commandCaps: ["switch"]
18737
+ }),
18738
+ actuator({
18739
+ id: "light",
18740
+ 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.",
18741
+ deviceType: DeviceType.Light,
18742
+ outputCap: "switch",
18743
+ commandCaps: [
18744
+ "switch",
18745
+ "brightness",
18746
+ "color"
18747
+ ]
18748
+ })
18749
+ ];
18750
+ function actuator(input) {
18751
+ return {
18752
+ ...input,
18753
+ family: "actuator",
18754
+ role: void 0,
18755
+ memberDeviceType: input.deviceType,
18756
+ outputField: "on",
18757
+ quantity: "none",
18758
+ baseUnit: void 0,
18759
+ aggregates: BINARY_AGGREGATES,
18760
+ defaultAggregate: "any",
18761
+ empty: "offline",
18762
+ accept: [{
18763
+ cap: "switch",
18764
+ fieldPath: "on"
18765
+ }],
18766
+ companions: [CHANGED]
18767
+ };
18768
+ }
18769
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18770
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18383
18771
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18384
18772
  /**
18385
18773
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -20093,12 +20481,12 @@ method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema
20093
20481
  * providers detect renames by matching `uniqueId` and patch `id`
20094
20482
  * in-place so dispatch keeps working.
20095
20483
  *
20096
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
20097
- * `precision`) live in the cap STATUS SLICE so they flow live with every
20098
- * state push and are the single source of truth. The `device_class` tag is
20099
- * an HA-specific input that is consumed at the discovery boundary
20100
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
20101
- * stored after that point.
20484
+ * **Display unit and precision are NOT stored here.** They are a read-time
20485
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
20486
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
20487
+ * neither is repeated on each sample. The `device_class` tag is an
20488
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
20489
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
20102
20490
  *
20103
20491
  * The `raw` escape hatch carries provider-specific attributes that don't
20104
20492
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -20112,6 +20500,14 @@ var SourceInfoSchema = object({
20112
20500
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
20113
20501
  * Used to detect rename when `id` changes. */
20114
20502
  uniqueId: string().optional(),
20503
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
20504
+ quantity: ReadingQuantitySchema.optional(),
20505
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
20506
+ dateTimeShape: _enum([
20507
+ "date",
20508
+ "time",
20509
+ "datetime"
20510
+ ]).optional(),
20115
20511
  /** Provider-specific extras that don't fit the structured slots.
20116
20512
  * Whitelist what goes here per provider — do NOT dump entire HA
20117
20513
  * attribute blobs (would explode the meta JSON column). */
@@ -41024,7 +41420,8 @@ method(object({
41024
41420
  "enum"
41025
41421
  ]),
41026
41422
  enumValues: array(string()).optional(),
41027
- item: boolean().optional()
41423
+ item: boolean().optional(),
41424
+ quantity: ReadingQuantitySchema.optional()
41028
41425
  })).readonly(),
41029
41426
  itemArray: object({
41030
41427
  path: string(),
@@ -44843,8 +45240,8 @@ var MaskPolygonShapeSchema = object({
44843
45240
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44844
45241
  var MaskGridShapeSchema = object({
44845
45242
  kind: literal("grid"),
44846
- gridWidth: number(),
44847
- gridHeight: number(),
45243
+ gridWidth: number().meta(readingMeta("none")),
45244
+ gridHeight: number().meta(readingMeta("none")),
44848
45245
  cells: array(boolean())
44849
45246
  });
44850
45247
  discriminatedUnion("kind", [
@@ -44956,6 +45353,8 @@ var NcSystemEventKindSchema = _enum([
44956
45353
  "detection-blind",
44957
45354
  "motion-noisy",
44958
45355
  "motion-calm",
45356
+ "package-llm-silent",
45357
+ "package-llm-answered",
44959
45358
  "recording-stalled",
44960
45359
  "recording-healthy",
44961
45360
  "recording-degraded",
@@ -55970,6 +56369,28 @@ var PackageVersionInfoSchema = object({
55970
56369
  deprecated: string().optional(),
55971
56370
  distTags: array(string()).readonly()
55972
56371
  });
56372
+ /**
56373
+ * One published document. `ok` is markdown from the registry. `missing` means
56374
+ * the registry answered and the file was not there. `error` is a registry or
56375
+ * tarball failure — never an empty string standing in for either.
56376
+ */
56377
+ var PackageDocBodySchema = discriminatedUnion("status", [
56378
+ object({
56379
+ status: literal("ok"),
56380
+ markdown: string()
56381
+ }),
56382
+ object({ status: literal("missing") }),
56383
+ object({
56384
+ status: literal("error"),
56385
+ message: string()
56386
+ })
56387
+ ]);
56388
+ var PackageDocsSchema = object({
56389
+ name: string(),
56390
+ version: string(),
56391
+ readme: PackageDocBodySchema,
56392
+ changelog: PackageDocBodySchema
56393
+ });
55973
56394
  var SearchResultSchema = object({
55974
56395
  name: string(),
55975
56396
  version: string(),
@@ -56188,7 +56609,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56188
56609
  }), object({ success: literal(true) }), {
56189
56610
  kind: "mutation",
56190
56611
  auth: "admin"
56191
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
56612
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
56613
+ name: string().min(1),
56614
+ version: string().min(1).optional()
56615
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
56192
56616
  kind: "mutation",
56193
56617
  auth: "admin"
56194
56618
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -56238,46 +56662,27 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56238
56662
  }), LogStreamEntrySchema, { kind: "subscription" });
56239
56663
  object({
56240
56664
  /** Carbon dioxide concentration in ppm. */
56241
- co2Ppm: number().min(0).optional(),
56665
+ co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
56242
56666
  /** Total volatile organic compounds in ppb. */
56243
- vocPpb: number().min(0).optional(),
56667
+ vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56244
56668
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56245
- pm25: number().min(0).optional(),
56669
+ pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56246
56670
  /** Particulate matter ≤ 10 μm in µg/m³. */
56247
- pm10: number().min(0).optional(),
56671
+ pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
56248
56672
  /** Composite AQI value (typically 0..500). */
56249
- aqi: number().optional(),
56673
+ aqi: number().optional().meta(readingMeta("index")),
56250
56674
  /** Ms epoch when the slice was last updated. */
56251
- lastFetchedAt: number(),
56252
- /** Live display unit of the single metric this slice carries (e.g. HA
56253
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
56254
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
56255
- * per slice is unambiguous. */
56256
- unit: string().optional(),
56257
- /** Suggested decimal places for numeric display.
56258
- * Populated live from the upstream source when provided (e.g. HA
56259
- * `attributes.suggested_display_precision`). Falls back to
56260
- * auto-formatting when absent. */
56261
- precision: number().int().min(0).max(10).optional()
56675
+ lastFetchedAt: number()
56262
56676
  });
56263
56677
  DeviceType.Sensor;
56264
56678
  object({
56265
- /** Current illuminance in lux (lx). */
56266
- lux: number().min(0).meta({
56267
- unit: "lx",
56679
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56680
+ lux: number().min(0).nullable().meta(readingMeta("illuminance", {
56681
+ precision: 0,
56268
56682
  label: "Illuminance"
56269
- }),
56683
+ })),
56270
56684
  /** Ms epoch when the slice was last updated. */
56271
- lastFetchedAt: number(),
56272
- /** Live display unit from the upstream source (e.g. HA
56273
- * `attributes.unit_of_measurement`). The UI prefers this over the
56274
- * role's canonical unit. Absent → fall back to the canonical unit. */
56275
- unit: string().optional(),
56276
- /** Suggested decimal places for numeric display.
56277
- * Populated live from the upstream source when provided (e.g. HA
56278
- * `attributes.suggested_display_precision`). Falls back to
56279
- * auto-formatting when absent. */
56280
- precision: number().int().min(0).max(10).optional()
56685
+ lastFetchedAt: number()
56281
56686
  });
56282
56687
  DeviceType.Sensor;
56283
56688
  /**
@@ -56422,10 +56827,7 @@ var BatteryStatusSchema = object({
56422
56827
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
56423
56828
  * declines to band a non-finite reading, which is the correct shape.
56424
56829
  */
56425
- percentage: number().min(0).max(100).nullable().meta({
56426
- unit: "%",
56427
- label: "Charge"
56428
- }),
56830
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
56429
56831
  /**
56430
56832
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
56431
56833
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -56452,7 +56854,14 @@ var BatteryStatusSchema = object({
56452
56854
  */
56453
56855
  sleeping: boolean().meta({ label: "Sleeping" }),
56454
56856
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
56455
- lastUpdated: number(),
56857
+ lastUpdated: number().meta(readingMeta("none")),
56858
+ /**
56859
+ * Battery voltage in volts, for a cell the firmware reports as voltage
56860
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
56861
+ * that only report charge. A second reading on this cap, not a second
56862
+ * `numeric-sensor` on the same device.
56863
+ */
56864
+ volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
56456
56865
  /**
56457
56866
  * Ms epoch of the last time the device PROVED it was reachable — a
56458
56867
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -56512,7 +56921,7 @@ object({
56512
56921
  DeviceType.Sensor;
56513
56922
  object({
56514
56923
  /** Current level as 0..100 inclusive. Firmware-reported. */
56515
- percentage: number().min(0).max(100),
56924
+ percentage: number().min(0).max(100).meta(readingMeta("percentage")),
56516
56925
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
56517
56926
  lastChangedAt: number()
56518
56927
  });
@@ -56897,21 +57306,21 @@ object({
56897
57306
  /** Single setpoint in Celsius. Used by single-target modes
56898
57307
  * (heat / cool / dry). Null when the device is in a range mode
56899
57308
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
56900
- target: number().nullable(),
57309
+ target: number().nullable().meta(readingMeta("temperature")),
56901
57310
  /** Upper bound of the dual setpoint in Celsius. Populated only
56902
57311
  * in `heat_cool` mode. */
56903
- targetHigh: number().nullable(),
57312
+ targetHigh: number().nullable().meta(readingMeta("temperature")),
56904
57313
  /** Lower bound of the dual setpoint in Celsius. Populated only
56905
57314
  * in `heat_cool` mode. */
56906
- targetLow: number().nullable(),
57315
+ targetLow: number().nullable().meta(readingMeta("temperature")),
56907
57316
  /** Target relative humidity (0..100). Null when the device has no
56908
57317
  * humidity control. */
56909
- targetHumidity: number().min(0).max(100).nullable(),
57318
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56910
57319
  /** Current measured temperature in Celsius. Read-only — pushed by
56911
57320
  * the device's internal sensor. */
56912
- currentTemp: number().nullable(),
57321
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
56913
57322
  /** Current measured relative humidity (0..100). Read-only. */
56914
- currentHumidity: number().min(0).max(100).nullable(),
57323
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
56915
57324
  /** Vertical louver swing on/off. Null when the device has no vertical
56916
57325
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
56917
57326
  swingVertical: boolean().nullable(),
@@ -56919,11 +57328,11 @@ object({
56919
57328
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
56920
57329
  swingHorizontal: boolean().nullable(),
56921
57330
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
56922
- minTemp: number().nullable().optional(),
57331
+ minTemp: number().nullable().optional().meta(readingMeta("temperature")),
56923
57332
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
56924
- maxTemp: number().nullable().optional(),
57333
+ maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
56925
57334
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
56926
- tempStep: number().positive().nullable().optional(),
57335
+ tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
56927
57336
  /** Ms epoch when the slice was last updated (push or command). */
56928
57337
  lastFetchedAt: number()
56929
57338
  });
@@ -57002,17 +57411,17 @@ DeviceType.Thermostat, DeviceType.Climate, method(object({
57002
57411
  * modes to expose in the picker without a slice fetch.
57003
57412
  */
57004
57413
  var RgbTripletSchema = object({
57005
- r: number().int().min(0).max(255),
57006
- g: number().int().min(0).max(255),
57007
- b: number().int().min(0).max(255)
57414
+ r: number().int().min(0).max(255).meta(readingMeta("none")),
57415
+ g: number().int().min(0).max(255).meta(readingMeta("none")),
57416
+ b: number().int().min(0).max(255).meta(readingMeta("none"))
57008
57417
  });
57009
57418
  var HsvTripletSchema = object({
57010
57419
  /** Hue in degrees, 0..360. */
57011
- h: number().min(0).max(360),
57420
+ h: number().min(0).max(360).meta(readingMeta("none")),
57012
57421
  /** Saturation as 0..100 inclusive. */
57013
- s: number().min(0).max(100),
57422
+ s: number().min(0).max(100).meta(readingMeta("none")),
57014
57423
  /** Value/brightness as 0..100 inclusive. */
57015
- v: number().min(0).max(100)
57424
+ v: number().min(0).max(100).meta(readingMeta("none"))
57016
57425
  });
57017
57426
  var ColorInputSchema = discriminatedUnion("mode", [
57018
57427
  object({
@@ -57041,7 +57450,7 @@ object({
57041
57450
  /** Populated when `mode === 'hsv'`. */
57042
57451
  hsv: HsvTripletSchema.optional(),
57043
57452
  /** Populated when `mode === 'mired'`. */
57044
- mireds: number().int().optional(),
57453
+ mireds: number().int().optional().meta(readingMeta("colour-temperature")),
57045
57454
  /** Ms epoch of the last operator-driven change. */
57046
57455
  lastChangedAt: number()
57047
57456
  });
@@ -57165,18 +57574,12 @@ _enum([
57165
57574
  "empty",
57166
57575
  "full"
57167
57576
  ]);
57168
- /** Units a remaining-life counter is reported in. Closed: the panel renders
57169
- * each. */
57170
- var ConsumableRemainingUnitSchema = _enum([
57171
- "days",
57172
- "hours",
57173
- "cycles"
57174
- ]);
57175
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
57176
- * days). `value` null = unknown (D393). */
57577
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
57578
+ var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
57579
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
57177
57580
  var ConsumableRemainingSchema = object({
57178
- value: number().min(0).nullable(),
57179
- unit: ConsumableRemainingUnitSchema
57581
+ value: number().min(0).nullable().meta(readingMeta("device")),
57582
+ kind: ConsumableRemainingKindSchema
57180
57583
  });
57181
57584
  var ConsumablesStatusSchema = object({
57182
57585
  items: array(object({
@@ -57185,7 +57588,7 @@ var ConsumablesStatusSchema = object({
57185
57588
  /** Display name. */
57186
57589
  label: string().min(1),
57187
57590
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
57188
- level: number().min(0).max(100).nullable(),
57591
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57189
57592
  /** The provider's discrete verdict when known (binary mode). */
57190
57593
  status: ConsumableStatusSchema.nullable(),
57191
57594
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -57202,7 +57605,7 @@ var ConsumablesStatusSchema = object({
57202
57605
  })),
57203
57606
  lastChangedAt: number()
57204
57607
  });
57205
- Object.values(DeviceType), [...ConsumableRemainingUnitSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57608
+ Object.values(DeviceType), [...ConsumableRemainingKindSchema.options], [...ConsumableStatusSchema.options], [...ConsumableKindSchema.options], [...ConsumableFormSchema.options];
57206
57609
  Object.values(DeviceType), method(object({
57207
57610
  deviceId: number().int().nonnegative(),
57208
57611
  key: string().min(1)
@@ -57227,39 +57630,17 @@ object({
57227
57630
  /** Current value. Numeric kind → number; select/text/datetime → string.
57228
57631
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
57229
57632
  * or time-only — HA accepts all three forms). */
57230
- value: union([number(), string()]),
57633
+ value: union([number().meta(readingMeta("device")), string()]),
57231
57634
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
57232
57635
  options: array(string()),
57233
57636
  /** Ms epoch when the slice was last updated. */
57234
57637
  lastChangedAt: number(),
57235
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
57236
- * Populated live from HA `attributes.unit_of_measurement` on each push.
57237
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57238
- unit: string().optional(),
57239
- /** Minimum allowed value. Populated live from HA `attributes.min`.
57240
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57241
- min: number().optional(),
57242
- /** Maximum allowed value. Populated live from HA `attributes.max`.
57243
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57244
- max: number().optional(),
57245
- /** Step increment. Populated live from HA `attributes.step`.
57246
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
57247
- step: number().optional(),
57248
- /** Suggested decimal places for numeric display. Populated live from HA
57249
- * `attributes.suggested_display_precision`, or derived from `step` when the
57250
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
57251
- * other kinds. Falls back to auto-formatting when absent. */
57252
- precision: number().int().min(0).max(10).optional(),
57253
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
57254
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
57255
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
57256
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
57257
- * `has_date`/`has_time` attributes on each push. */
57258
- format: _enum([
57259
- "date",
57260
- "time",
57261
- "datetime"
57262
- ]).optional()
57638
+ /** Minimum allowed value, in the device quantity's base unit. */
57639
+ min: number().optional().meta(readingMeta("device")),
57640
+ /** Maximum allowed value, in the device quantity's base unit. */
57641
+ max: number().optional().meta(readingMeta("device")),
57642
+ /** Step increment, in the device quantity's base unit. */
57643
+ step: number().optional().meta(readingMeta("device"))
57263
57644
  });
57264
57645
  var ControlSetValueInputSchema = discriminatedUnion("kind", [
57265
57646
  object({
@@ -57297,9 +57678,9 @@ object({
57297
57678
  ]),
57298
57679
  /** 0 = fully closed, 100 = fully open. Null when the device has
57299
57680
  * no intermediate position surface. */
57300
- position: number().min(0).max(100).nullable(),
57681
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57301
57682
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
57302
- tiltPosition: number().min(0).max(100).nullable(),
57683
+ tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57303
57684
  /** Ms epoch when the slice was last updated. */
57304
57685
  lastChangedAt: number()
57305
57686
  });
@@ -57357,9 +57738,9 @@ var NormalizedRangeSchema$1 = object({
57357
57738
  object({
57358
57739
  mode: DayNightModeSchema,
57359
57740
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
57360
- sensitivity: number().optional(),
57741
+ sensitivity: number().optional().meta(readingMeta("none")),
57361
57742
  /** Delay before the IR-cut filter flips, in seconds. */
57362
- switchDelaySec: number().optional(),
57743
+ switchDelaySec: number().optional().meta(readingMeta("duration")),
57363
57744
  lastFetchedAt: number()
57364
57745
  });
57365
57746
  /**
@@ -57514,11 +57895,11 @@ object({
57514
57895
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
57515
57896
  availablePrograms: array(string()),
57516
57897
  /** Seconds remaining on the active cycle, or null when not running / not known. */
57517
- remainingSeconds: number().int().nonnegative().nullable(),
57898
+ remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57518
57899
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
57519
- delayedStartSeconds: number().int().nonnegative().nullable(),
57900
+ delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
57520
57901
  /** Cycle completion 0..100, or null when not applicable. */
57521
- progressPercent: number().min(0).max(100).nullable(),
57902
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
57522
57903
  /** True when the door is reported open; null when the door state is not known. */
57523
57904
  doorOpen: boolean().nullable(),
57524
57905
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -57542,7 +57923,7 @@ object({
57542
57923
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
57543
57924
  machineCareDue: boolean().nullable(),
57544
57925
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
57545
- machineCareRemainingRuns: number().int().nonnegative().nullable(),
57926
+ machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
57546
57927
  /** Option states: null when the appliance does not expose the option or has not said. */
57547
57928
  halfLoad: boolean().nullable(),
57548
57929
  hygienePlus: boolean().nullable(),
@@ -57557,7 +57938,7 @@ object({
57557
57938
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
57558
57939
  activeEvents: array(string()),
57559
57940
  /** Numeric error code when the appliance reports one; null otherwise. */
57560
- errorCode: number().nullable(),
57941
+ errorCode: number().nullable().meta(readingMeta("none")),
57561
57942
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
57562
57943
  errorLabel: string().nullable(),
57563
57944
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -57703,24 +58084,14 @@ object({
57703
58084
  /** Ms epoch of the last press. null = never observed since this provider started. */
57704
58085
  lastPressedAt: number().nullable(),
57705
58086
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
57706
- pressCountSinceStart: number()
58087
+ pressCountSinceStart: number().meta(readingMeta("count"))
57707
58088
  });
57708
58089
  object({
57709
58090
  deviceId: number(),
57710
58091
  timestamp: number()
57711
58092
  });
57712
58093
  DeviceType.Button, DeviceType.Camera;
57713
- /**
57714
- * Enum-state sensor — a string value picked from a finite option set.
57715
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
57716
- * states, weather conditions, contact-source channels, mode strings).
57717
- * The option set is stable across observations; it is persisted in the
57718
- * device config blob (via the control slice at adoption) and is NOT
57719
- * stored in `sourceInfo`.
57720
- */
57721
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
57722
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
57723
- var EnumSensorDateTimeFormatSchema = _enum([
58094
+ _enum([
57724
58095
  "date",
57725
58096
  "time",
57726
58097
  "datetime"
@@ -57728,12 +58099,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
57728
58099
  object({
57729
58100
  value: string(),
57730
58101
  /**
57731
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
57732
- * as a locale date/time/datetime; absent for plain enum sensors. HA
57733
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
57734
- * a time-only sensor → `'time'`.
58102
+ * Set when `value` is a timestamp rather than an enum state, so history
58103
+ * does not learn ISO strings as states. The display shape (date / time /
58104
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
57735
58105
  */
57736
- format: EnumSensorDateTimeFormatSchema.optional(),
58106
+ timestamp: boolean().optional(),
57737
58107
  /** Ms epoch when the slice was last updated. */
57738
58108
  lastFetchedAt: number()
57739
58109
  });
@@ -57752,13 +58122,13 @@ var EventFireSchema = object({
57752
58122
  deviceId: number(),
57753
58123
  eventType: string(),
57754
58124
  data: record(string(), unknown()).nullable(),
57755
- timestamp: number(),
57756
- seq: number()
58125
+ timestamp: number().meta(readingMeta("none")),
58126
+ seq: number().meta(readingMeta("count"))
57757
58127
  });
57758
58128
  object({
57759
58129
  eventTypes: array(string()),
57760
58130
  lastEvent: EventFireSchema.nullable(),
57761
- eventCountSinceStart: number()
58131
+ eventCountSinceStart: number().meta(readingMeta("count"))
57762
58132
  });
57763
58133
  DeviceType.EventEmitter;
57764
58134
  var IdentitySchema = object({
@@ -58147,10 +58517,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
58147
58517
  object({
58148
58518
  /** Active speed as 0..100 inclusive. Null when the device has no
58149
58519
  * speed surface (single-speed fan). */
58150
- percentage: number().min(0).max(100).nullable(),
58520
+ percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58151
58521
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
58152
58522
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
58153
- percentageStep: number().positive().optional(),
58523
+ percentageStep: number().positive().optional().meta(readingMeta("percentage")),
58154
58524
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
58155
58525
  * — empty string when no preset is active or supported. */
58156
58526
  preset: string(),
@@ -58232,7 +58602,7 @@ var FeatureProbeStatusSchema = object({
58232
58602
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
58233
58603
  model: string().nullable(),
58234
58604
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
58235
- channelCount: number().nullable(),
58605
+ channelCount: number().nullable().meta(readingMeta("count")),
58236
58606
  /**
58237
58607
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
58238
58608
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -58264,9 +58634,39 @@ object({
58264
58634
  lastChangedAt: number()
58265
58635
  });
58266
58636
  DeviceType.Sensor;
58637
+ object({
58638
+ /** One row per member that has been commanded. A later command replaces that member's row. */
58639
+ results: array(object({
58640
+ deviceId: number().int().nonnegative(),
58641
+ cap: string().min(1),
58642
+ method: string().min(1),
58643
+ ok: boolean(),
58644
+ error: string().optional(),
58645
+ /** Ms epoch of the attempt. */
58646
+ at: number()
58647
+ })) });
58648
+ DeviceType.Switch, DeviceType.Light;
58649
+ object({
58650
+ /** Members whose current value entered the aggregate. */
58651
+ included: number().int().nonnegative().meta(readingMeta("count", {
58652
+ precision: 0,
58653
+ label: "Reporting"
58654
+ })),
58655
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
58656
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
58657
+ precision: 0,
58658
+ label: "Excluded"
58659
+ })),
58660
+ /** `included` + `excluded`. */
58661
+ total: number().int().nonnegative().meta(readingMeta("count", {
58662
+ precision: 0,
58663
+ label: "Members"
58664
+ }))
58665
+ });
58666
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
58267
58667
  object({
58268
58668
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
58269
- position: number().min(0).max(100).nullable(),
58669
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58270
58670
  /** Ms epoch when the slice was last updated (push or poll). */
58271
58671
  lastFetchedAt: number()
58272
58672
  });
@@ -58275,9 +58675,9 @@ object({
58275
58675
  /** Whether the humidifier is currently on. */
58276
58676
  on: boolean(),
58277
58677
  /** Current measured relative humidity (0..100). Null when not reported. */
58278
- currentHumidity: number().min(0).max(100).nullable(),
58678
+ currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58279
58679
  /** Target relative humidity (0..100). Null when no setpoint surface. */
58280
- targetHumidity: number().min(0).max(100).nullable(),
58680
+ targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
58281
58681
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
58282
58682
  * device has no mode surface. */
58283
58683
  mode: string().nullable(),
@@ -58287,9 +58687,9 @@ object({
58287
58687
  * `idle` / `off`). Null when the device doesn't report it. */
58288
58688
  action: string().nullable(),
58289
58689
  /** HA `min_humidity` attribute. Null → UI uses 0. */
58290
- minHumidity: number().nullable(),
58690
+ minHumidity: number().nullable().meta(readingMeta("humidity")),
58291
58691
  /** HA `max_humidity` attribute. Null → UI uses 100. */
58292
- maxHumidity: number().nullable(),
58692
+ maxHumidity: number().nullable().meta(readingMeta("humidity")),
58293
58693
  /** Ms epoch when the slice was last updated. */
58294
58694
  lastChangedAt: number()
58295
58695
  });
@@ -58313,22 +58713,13 @@ DeviceType.Humidifier, method(object({
58313
58713
  auth: "protected"
58314
58714
  });
58315
58715
  object({
58316
- /** Current relative humidity, 0..100. */
58317
- percent: number().min(0).max(100).meta({
58318
- unit: "%",
58716
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
58717
+ percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
58718
+ precision: 0,
58319
58719
  label: "Humidity"
58320
- }),
58720
+ })),
58321
58721
  /** Ms epoch when the slice was last updated. */
58322
- lastFetchedAt: number(),
58323
- /** Live display unit from the upstream source (e.g. HA
58324
- * `attributes.unit_of_measurement`). The UI prefers this over the
58325
- * role's canonical unit. Absent → fall back to the canonical unit. */
58326
- unit: string().optional(),
58327
- /** Suggested decimal places for numeric display.
58328
- * Populated live from the upstream source when provided (e.g. HA
58329
- * `attributes.suggested_display_precision`). Falls back to
58330
- * auto-formatting when absent. */
58331
- precision: number().int().min(0).max(10).optional()
58722
+ lastFetchedAt: number()
58332
58723
  });
58333
58724
  DeviceType.Sensor, DeviceType.Thermostat;
58334
58725
  object({
@@ -58336,7 +58727,7 @@ object({
58336
58727
  * entity exposes no `entity_picture` (yet). */
58337
58728
  url: string().nullable(),
58338
58729
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
58339
- lastUpdated: number().nullable()
58730
+ lastUpdated: number().nullable().meta(readingMeta("none"))
58340
58731
  });
58341
58732
  DeviceType.Image;
58342
58733
  /**
@@ -58392,19 +58783,19 @@ var NormalizedRangeSchema = object({
58392
58783
  });
58393
58784
  object({
58394
58785
  /** Normalized 0–100. */
58395
- brightness: number().optional(),
58786
+ brightness: number().optional().meta(readingMeta("none")),
58396
58787
  /** Normalized 0–100. */
58397
- contrast: number().optional(),
58788
+ contrast: number().optional().meta(readingMeta("none")),
58398
58789
  /** Normalized 0–100. */
58399
- saturation: number().optional(),
58790
+ saturation: number().optional().meta(readingMeta("none")),
58400
58791
  /** Normalized 0–100. */
58401
- sharpness: number().optional(),
58792
+ sharpness: number().optional().meta(readingMeta("none")),
58402
58793
  mirror: boolean().optional(),
58403
58794
  flip: boolean().optional(),
58404
58795
  rotate: ImageRotateSchema.optional(),
58405
58796
  whiteBalance: WhiteBalanceModeSchema.optional(),
58406
58797
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
58407
- warmth: number().optional(),
58798
+ warmth: number().optional().meta(readingMeta("none")),
58408
58799
  exposureMode: ExposureModeSchema.optional(),
58409
58800
  backlightMode: BacklightModeSchema.optional(),
58410
58801
  lastFetchedAt: number()
@@ -58649,10 +59040,10 @@ var IntercomAbilitySchema = object({
58649
59040
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
58650
59041
  codecs: array(string()),
58651
59042
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
58652
- sampleRate: number(),
59043
+ sampleRate: number().meta(readingMeta("none")),
58653
59044
  duplex: _enum(["half", "full"]),
58654
59045
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
58655
- maxBacklogMs: number()
59046
+ maxBacklogMs: number().meta(readingMeta("duration-ms"))
58656
59047
  });
58657
59048
  var IntercomStatusSchema = object({
58658
59049
  /** True while a talk session is open. */
@@ -58763,13 +59154,13 @@ object({
58763
59154
  activity: LawnMowerActivitySchema,
58764
59155
  /** 0..100 battery percentage. Null when the device has no battery
58765
59156
  * reading. */
58766
- batteryLevel: number().min(0).max(100).nullable(),
59157
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58767
59158
  /** 0..100 mowing-completion percentage of the current task, or null when no
58768
59159
  * task is active / progress is unavailable. */
58769
- progressPercent: number().min(0).max(100).nullable(),
59160
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
58770
59161
  /** Current device/event code (dynamic — mostly status, sometimes an error),
58771
59162
  * or null when unknown. */
58772
- currentCode: number().nullable(),
59163
+ currentCode: number().nullable().meta(readingMeta("none")),
58773
59164
  /** Human label for {@link currentCode}, or null when undecodable. */
58774
59165
  currentCodeLabel: string().nullable(),
58775
59166
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -59231,7 +59622,7 @@ object({
59231
59622
  state: MediaPlayerStateSchema,
59232
59623
  /** Volume as 0..100 inclusive. Null when the device has no volume
59233
59624
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
59234
- volumeLevel: number().min(0).max(100).nullable(),
59625
+ volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59235
59626
  /** Mute toggle distinct from volume=0. Null when no mute surface.
59236
59627
  * Pair with `DeviceFeature.MediaPlayerMute`. */
59237
59628
  isMuted: boolean().nullable(),
@@ -59244,10 +59635,10 @@ object({
59244
59635
  currentMedia: MediaInfoSchema.nullable(),
59245
59636
  /** Current playback position in ms. Null when not seekable or
59246
59637
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
59247
- positionMs: number().int().nonnegative().nullable(),
59638
+ positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59248
59639
  /** Total duration of the current media in ms. Null when unknown
59249
59640
  * (live stream) or nothing is playing. */
59250
- durationMs: number().int().nonnegative().nullable(),
59641
+ durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
59251
59642
  /** Shuffle toggle. Null when no shuffle surface.
59252
59643
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
59253
59644
  shuffle: boolean().nullable(),
@@ -59547,7 +59938,7 @@ object({
59547
59938
  * it, every provider seeded it at boot, and every session left it
59548
59939
  * `detected: true` after its detach.
59549
59940
  */
59550
- autoClearAfterMs: number().nullable()
59941
+ autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
59551
59942
  });
59552
59943
  /**
59553
59944
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -59597,7 +59988,7 @@ var MotionZoneRegionSchema = object({
59597
59988
  });
59598
59989
  object({
59599
59990
  enabled: boolean(),
59600
- sensitivity: number(),
59991
+ sensitivity: number().meta(readingMeta("none")),
59601
59992
  /** Grid region(s). Today exactly one `grid` shape. */
59602
59993
  regions: array(MotionZoneRegionSchema),
59603
59994
  lastFetchedAt: number()
@@ -59901,7 +60292,7 @@ var NavigationStatusSchema = object({
59901
60292
  following: boolean(),
59902
60293
  flash: boolean(),
59903
60294
  lightMode: NavigationLightModeSchema,
59904
- lightLevel: number().min(40).max(100),
60295
+ lightLevel: number().min(40).max(100).meta(readingMeta("none")),
59905
60296
  /** Ms epoch when the slice was last updated. */
59906
60297
  lastChangedAt: number()
59907
60298
  });
@@ -59947,13 +60338,13 @@ var NetworkLinkStatusSchema = object({
59947
60338
  * one whose reading has not landed must not be drawn at 0 %. Consumers
59948
60339
  * SKIP a null rather than coerce it.
59949
60340
  */
59950
- signalPercent: number().min(0).max(100).nullable(),
60341
+ signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59951
60342
  /** Raw received signal strength in dBm, when the firmware reports one. */
59952
- rssiDbm: number().optional(),
60343
+ rssiDbm: number().optional().meta(readingMeta("signal-level")),
59953
60344
  /** Network name of a wireless link, when the firmware reports it. */
59954
60345
  ssid: string().optional(),
59955
60346
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
59956
- lastUpdated: number()
60347
+ lastUpdated: number().meta(readingMeta("none"))
59957
60348
  });
59958
60349
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
59959
60350
  deviceId: number(),
@@ -60272,7 +60663,7 @@ object({
60272
60663
  lastError: string().nullable(),
60273
60664
  /** Number of deliveries currently buffered server-side (rate-limited
60274
60665
  * notifiers may queue). 0 when the provider sends synchronously. */
60275
- queueDepth: number().int().nonnegative(),
60666
+ queueDepth: number().int().nonnegative().meta(readingMeta("count")),
60276
60667
  /** Per-feature capability matrix — authoritative source for the
60277
60668
  * compose-form field gating. */
60278
60669
  supports: NotifierSupportsSchema
@@ -60322,15 +60713,8 @@ DeviceType.Notifier, method(NotifierSendInputSchema, NotifierSendResultSchema, {
60322
60713
  acceptedAt: number()
60323
60714
  });
60324
60715
  object({
60325
- value: number(),
60326
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
60327
- * Populated live from the upstream source on each state push.
60328
- * Absent for unitless sensors. */
60329
- unit: string().optional(),
60330
- /** Suggested decimal places for numeric display.
60331
- * Populated live from the upstream source when provided.
60332
- * Falls back to auto-formatting when absent. */
60333
- precision: number().int().min(0).max(10).optional(),
60716
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
60717
+ value: number().nullable().meta(readingMeta("device")),
60334
60718
  /** Ms epoch when the slice was last updated. */
60335
60719
  lastFetchedAt: number()
60336
60720
  });
@@ -60620,23 +61004,26 @@ object({
60620
61004
  /** Food currently in the bowl (grams). Null when the device has not
60621
61005
  * reported a reading yet. On dual-hopper models this is the combined
60622
61006
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
60623
- foodLevel: number().nullable(),
61007
+ foodLevel: number().nullable().meta(readingMeta("none")),
60624
61008
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
60625
61009
  * single-hopper models. */
60626
- food1: number().nullable(),
61010
+ food1: number().nullable().meta(readingMeta("none")),
60627
61011
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
60628
61012
  * single-hopper models. */
60629
- food2: number().nullable(),
61013
+ food2: number().nullable().meta(readingMeta("none")),
60630
61014
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
60631
61015
  * (`device_class: problem`, on = low). True when the bowl is empty /
60632
61016
  * below the feeder's low threshold. */
60633
61017
  lowFood: boolean(),
60634
61018
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
60635
61019
  * device has no battery reading. */
60636
- batteryPower: number().min(0).max(100).nullable(),
60637
- /** Days of desiccant life remaining. Null when the model has no
60638
- * desiccant sensor. */
60639
- desiccantLeftDays: number().nullable(),
61020
+ batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61021
+ /**
61022
+ * Desiccant life remaining, stored in seconds (duration base, D944).
61023
+ * Providers convert from the days the feeder reports. Null when the
61024
+ * model has no desiccant sensor.
61025
+ */
61026
+ desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
60640
61027
  /** True while a feed is in progress. */
60641
61028
  feeding: boolean(),
60642
61029
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -60647,7 +61034,7 @@ object({
60647
61034
  * with `errorCode` for consumers that want the raw integer. */
60648
61035
  error: string().nullable(),
60649
61036
  /** Raw device error code (0 / null = no error). */
60650
- errorCode: number().nullable(),
61037
+ errorCode: number().nullable().meta(readingMeta("none")),
60651
61038
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
60652
61039
  isDualHopper: boolean(),
60653
61040
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -60657,7 +61044,7 @@ object({
60657
61044
  /** Play a chime when dispensing. */
60658
61045
  feedSound: boolean(),
60659
61046
  /** Speaker / prompt volume level (device-scaled integer). */
60660
- volume: number(),
61047
+ volume: number().meta(readingMeta("none")),
60661
61048
  /** Ms epoch when the slice was last refreshed from the cloud. */
60662
61049
  lastFetchedAt: number()
60663
61050
  });
@@ -61094,36 +61481,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
61094
61481
  });
61095
61482
  object({
61096
61483
  /** Instantaneous power draw in watts. */
61097
- watts: number().optional().meta({
61098
- unit: "W",
61099
- label: "Power"
61100
- }),
61484
+ watts: number().optional().meta(readingMeta("power", { label: "Power" })),
61101
61485
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61102
- kwhTotal: number().optional().meta({
61103
- unit: "kWh",
61104
- label: "Energy"
61105
- }),
61486
+ kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61106
61487
  /** Voltage in volts. */
61107
- volts: number().optional(),
61488
+ volts: number().optional().meta(readingMeta("voltage")),
61108
61489
  /** Current in amperes. */
61109
- amps: number().optional(),
61110
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
61111
- va: number().optional(),
61490
+ amps: number().optional().meta(readingMeta("current")),
61491
+ /** Apparent power in volt-amperes. */
61492
+ va: number().optional().meta(readingMeta("apparent-power")),
61112
61493
  /** Power factor (0..1) if reported. */
61113
- powerFactor: number().min(0).max(1).optional(),
61494
+ powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
61114
61495
  /** Ms epoch when the slice was last updated. */
61115
- lastFetchedAt: number(),
61116
- /** Live display unit of the single metric this slice carries (e.g. HA
61117
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
61118
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
61119
- * unit per slice is unambiguous. The UI prefers this over the role's
61120
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
61121
- unit: string().optional(),
61122
- /** Suggested decimal places for numeric display.
61123
- * Populated live from the upstream source when provided (e.g. HA
61124
- * `attributes.suggested_display_precision`). Falls back to
61125
- * auto-formatting when absent. */
61126
- precision: number().int().min(0).max(10).optional()
61496
+ lastFetchedAt: number()
61127
61497
  });
61128
61498
  DeviceType.Sensor;
61129
61499
  /**
@@ -61144,11 +61514,11 @@ DeviceType.Sensor;
61144
61514
  */
61145
61515
  var GpsLocationSchema = object({
61146
61516
  /** Latitude in decimal degrees, -90..90. */
61147
- latitude: number().min(-90).max(90),
61517
+ latitude: number().min(-90).max(90).meta(readingMeta("angle")),
61148
61518
  /** Longitude in decimal degrees, -180..180. */
61149
- longitude: number().min(-180).max(180),
61519
+ longitude: number().min(-180).max(180).meta(readingMeta("angle")),
61150
61520
  /** Reported accuracy in meters (lower = better). */
61151
- accuracyMeters: number().nonnegative()
61521
+ accuracyMeters: number().nonnegative().meta(readingMeta("length"))
61152
61522
  });
61153
61523
  object({
61154
61524
  /** `home` / `not_home` / any user-defined zone name. */
@@ -61160,25 +61530,16 @@ object({
61160
61530
  gps: GpsLocationSchema.nullable(),
61161
61531
  /** Optional battery level of the tracking device (0..100). Null
61162
61532
  * when not reported. */
61163
- batteryPercent: number().min(0).max(100).nullable(),
61533
+ batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61164
61534
  /** Ms epoch when the slice was last updated. */
61165
61535
  lastChangedAt: number()
61166
61536
  });
61167
61537
  DeviceType.Presence;
61168
61538
  object({
61169
- /** Current pressure in hPa. */
61170
- hpa: number(),
61539
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
61540
+ hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
61171
61541
  /** Ms epoch when the slice was last updated. */
61172
- lastFetchedAt: number(),
61173
- /** Live display unit from the upstream source (e.g. HA
61174
- * `attributes.unit_of_measurement`). The UI prefers this over the
61175
- * role's canonical unit. Absent → fall back to the canonical unit. */
61176
- unit: string().optional(),
61177
- /** Suggested decimal places for numeric display.
61178
- * Populated live from the upstream source when provided (e.g. HA
61179
- * `attributes.suggested_display_precision`). Falls back to
61180
- * auto-formatting when absent. */
61181
- precision: number().int().min(0).max(10).optional()
61542
+ lastFetchedAt: number()
61182
61543
  });
61183
61544
  DeviceType.Sensor;
61184
61545
  /**
@@ -61511,8 +61872,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
61511
61872
  }), _void(), { kind: "mutation" });
61512
61873
  var PtzAutotrackSettingsSchema = object({
61513
61874
  targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
61514
- stopDelaySeconds: number().int().min(0).max(300),
61515
- disappearDelaySeconds: number().int().min(0).max(300)
61875
+ stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
61876
+ disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
61516
61877
  });
61517
61878
  /**
61518
61879
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -61569,7 +61930,7 @@ object({
61569
61930
  /** null = unknown — never coerced to false (D393). */
61570
61931
  running: boolean().nullable(),
61571
61932
  /** Epoch ms the current boolean began; null while `running` is null. */
61572
- since: number().nullable(),
61933
+ since: number().nullable().meta(readingMeta("none")),
61573
61934
  source: RunningStateSourceSchema.meta({ label: "Source" }),
61574
61935
  phase: string().nullable(),
61575
61936
  /** null until a reading has been published (D393). */
@@ -62767,9 +63128,9 @@ var RecordTriggerSchema = _enum([
62767
63128
  */
62768
63129
  var RecordWindowSchema = object({
62769
63130
  trigger: RecordTriggerSchema,
62770
- day: number().int().min(0).max(6),
62771
- startMinute: number().int().min(0).max(1439),
62772
- endMinute: number().int().min(1).max(1440)
63131
+ day: number().int().min(0).max(6).meta(readingMeta("none")),
63132
+ startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
63133
+ endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
62773
63134
  });
62774
63135
  /** Status of one physical volume, as the camera itself describes it. */
62775
63136
  var OnboardStorageVolumeSchema = object({
@@ -62838,10 +63199,10 @@ object({
62838
63199
  /** Master "record to the card at all" switch. */
62839
63200
  enabled: boolean().nullable(),
62840
63201
  overwriteWhenFull: boolean().nullable(),
62841
- preRecordSec: number().nullable(),
62842
- postRecordSec: number().nullable(),
63202
+ preRecordSec: number().nullable().meta(readingMeta("duration")),
63203
+ postRecordSec: number().nullable().meta(readingMeta("duration")),
62843
63204
  /** Length of one recorded file, in minutes. */
62844
- segmentMinutes: number().nullable(),
63205
+ segmentMinutes: number().nullable().meta(readingMeta("none")),
62845
63206
  /** The primary track's weekly windows, flattened. */
62846
63207
  windows: array(RecordWindowSchema),
62847
63208
  /**
@@ -62857,7 +63218,7 @@ object({
62857
63218
  * provider reporting a non-zero count also reports the schedule as not
62858
63219
  * writable.
62859
63220
  */
62860
- unreadableWindows: number(),
63221
+ unreadableWindows: number().meta(readingMeta("count")),
62861
63222
  /**
62862
63223
  * The camera is scheduled to record and has NO usable storage.
62863
63224
  *
@@ -63494,7 +63855,7 @@ var CamStreamDescriptorSchema = object({
63494
63855
  url: string().optional(),
63495
63856
  codec: string().optional(),
63496
63857
  resolution: CamStreamResolutionSchema.optional(),
63497
- fps: number().positive().optional(),
63858
+ fps: number().positive().optional().meta(readingMeta("rate")),
63498
63859
  label: string().optional(),
63499
63860
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
63500
63861
  deviceFeatures: array(string()).optional(),
@@ -63524,18 +63885,18 @@ var StreamProfileSchema = _enum([
63524
63885
  ]);
63525
63886
  /** The current encoding config of one stream profile. */
63526
63887
  var StreamProfileConfigSchema = object({
63527
- width: number(),
63528
- height: number(),
63888
+ width: number().meta(readingMeta("none")),
63889
+ height: number().meta(readingMeta("none")),
63529
63890
  codec: _enum(["h264", "h265"]),
63530
- framerate: number(),
63531
- bitrate: number(),
63891
+ framerate: number().meta(readingMeta("rate")),
63892
+ bitrate: number().meta(readingMeta("bitrate")),
63532
63893
  bitrateMode: _enum(["vbr", "cbr"]).optional(),
63533
63894
  encoderProfile: _enum([
63534
63895
  "high",
63535
63896
  "main",
63536
63897
  "baseline"
63537
63898
  ]).optional(),
63538
- gop: number().optional(),
63899
+ gop: number().optional().meta(readingMeta("none")),
63539
63900
  /**
63540
63901
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
63541
63902
  *
@@ -64178,23 +64539,13 @@ object({
64178
64539
  });
64179
64540
  DeviceType.Sensor;
64180
64541
  object({
64181
- /** Current temperature in Celsius. */
64182
- celsius: number().meta({
64183
- unit: "°C",
64542
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
64543
+ celsius: number().nullable().meta(readingMeta("temperature", {
64544
+ precision: 1,
64184
64545
  label: "Temperature"
64185
- }),
64546
+ })),
64186
64547
  /** Ms epoch when the slice was last updated (push or poll). */
64187
- lastFetchedAt: number(),
64188
- /** Live display unit from the upstream source (e.g. HA
64189
- * `attributes.unit_of_measurement`). The UI prefers this over the
64190
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
64191
- * Absent → the UI falls back to the role's canonical unit. */
64192
- unit: string().optional(),
64193
- /** Suggested decimal places for numeric display.
64194
- * Populated live from the upstream source when provided (e.g. HA
64195
- * `attributes.suggested_display_precision`). Falls back to
64196
- * auto-formatting when absent. */
64197
- precision: number().int().min(0).max(10).optional()
64548
+ lastFetchedAt: number()
64198
64549
  });
64199
64550
  DeviceType.Sensor, DeviceType.Thermostat;
64200
64551
  /**
@@ -64446,16 +64797,16 @@ object({
64446
64797
  ]),
64447
64798
  /** 0..100 battery percentage. Null when the device has no battery
64448
64799
  * reading. */
64449
- batteryLevel: number().min(0).max(100).nullable(),
64800
+ batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64450
64801
  /** Current fan-speed token (provider-verbatim). Null when unknown or
64451
64802
  * the vacuum has no speed control. */
64452
64803
  fanSpeed: string().nullable(),
64453
64804
  /** Speed tokens the hardware accepts — drives the UI selector. */
64454
64805
  availableFanSpeeds: array(string()),
64455
64806
  /** 0..100 cleaning-completion percentage of the current task, or null. */
64456
- progressPercent: number().min(0).max(100).nullable(),
64807
+ progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64457
64808
  /** Current error code (0 / null = no error). */
64458
- errorCode: number().nullable(),
64809
+ errorCode: number().nullable().meta(readingMeta("none")),
64459
64810
  /** Human label for {@link errorCode}, or null when none / undecodable. */
64460
64811
  errorLabel: string().nullable(),
64461
64812
  /** Ms epoch when the slice was last updated. */
@@ -64494,7 +64845,7 @@ object({
64494
64845
  ]),
64495
64846
  /** 0 = fully closed, 100 = fully open. Null when the device has no
64496
64847
  * intermediate position surface. */
64497
- position: number().min(0).max(100).nullable(),
64848
+ position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64498
64849
  /** Ms epoch when the slice was last updated. */
64499
64850
  lastChangedAt: number()
64500
64851
  });
@@ -64522,9 +64873,9 @@ object({
64522
64873
  DeviceType.Sensor;
64523
64874
  object({
64524
64875
  /** Current measured temperature. Null when not reported. */
64525
- currentTemp: number().nullable(),
64876
+ currentTemp: number().nullable().meta(readingMeta("temperature")),
64526
64877
  /** Target temperature setpoint. Null when no setpoint surface. */
64527
- targetTemp: number().nullable(),
64878
+ targetTemp: number().nullable().meta(readingMeta("temperature")),
64528
64879
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
64529
64880
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
64530
64881
  * device reports an unknown state. */
@@ -64535,9 +64886,9 @@ object({
64535
64886
  * has no away surface. */
64536
64887
  away: boolean().nullable(),
64537
64888
  /** HA `min_temp` attribute. Null when not reported. */
64538
- minTemp: number().nullable(),
64889
+ minTemp: number().nullable().meta(readingMeta("temperature")),
64539
64890
  /** HA `max_temp` attribute. Null when not reported. */
64540
- maxTemp: number().nullable(),
64891
+ maxTemp: number().nullable().meta(readingMeta("temperature")),
64541
64892
  /** Ms epoch when the slice was last updated. */
64542
64893
  lastChangedAt: number()
64543
64894
  });
@@ -64561,25 +64912,18 @@ DeviceType.WaterHeater, method(object({
64561
64912
  auth: "protected"
64562
64913
  });
64563
64914
  object({
64564
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
64565
- * when no condition has been reported yet. */
64915
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
64566
64916
  condition: string().nullable(),
64567
- /** Current temperature in the reported unit. Null when not provided. */
64568
- temperature: number().nullable(),
64569
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
64570
- temperatureUnit: string().nullable(),
64917
+ /** Current temperature in Celsius. Null when not provided or refused. */
64918
+ temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
64571
64919
  /** Relative humidity (0..100). Null when not provided. */
64572
- humidity: number().min(0).max(100).nullable(),
64573
- /** Barometric pressure in the reported unit. Null when not provided. */
64574
- pressure: number().nullable(),
64575
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
64576
- pressureUnit: string().nullable(),
64577
- /** Wind speed in the reported unit. Null when not provided. */
64578
- windSpeed: number().nullable(),
64579
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
64580
- windSpeedUnit: string().nullable(),
64920
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
64921
+ /** Barometric pressure in hPa. Null when not provided or refused. */
64922
+ pressure: number().nullable().meta(readingMeta("pressure")),
64923
+ /** Wind speed in metres per second. Null when not provided or refused. */
64924
+ windSpeed: number().nullable().meta(readingMeta("speed")),
64581
64925
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
64582
- windBearing: number().nullable(),
64926
+ windBearing: number().nullable().meta(readingMeta("angle")),
64583
64927
  /** Ms epoch when the slice was last updated. */
64584
64928
  lastFetchedAt: number()
64585
64929
  });
@@ -65491,6 +65835,13 @@ Object.freeze({
65491
65835
  access: "view",
65492
65836
  role: "admin"
65493
65837
  },
65838
+ "addons.getPackageDocs": {
65839
+ capName: "addons",
65840
+ capScope: "system",
65841
+ addonId: null,
65842
+ access: "view",
65843
+ role: "admin"
65844
+ },
65494
65845
  "addons.getVersions": {
65495
65846
  capName: "addons",
65496
65847
  capScope: "system",
@@ -68599,6 +68950,20 @@ Object.freeze({
68599
68950
  access: "view",
68600
68951
  role: "viewer"
68601
68952
  },
68953
+ "groupCommands.getStatus": {
68954
+ capName: "group-commands",
68955
+ capScope: "device",
68956
+ addonId: null,
68957
+ access: "view",
68958
+ role: "viewer"
68959
+ },
68960
+ "groupStatus.getStatus": {
68961
+ capName: "group-status",
68962
+ capScope: "device",
68963
+ addonId: null,
68964
+ access: "view",
68965
+ role: "viewer"
68966
+ },
68602
68967
  "heatingValve.getStatus": {
68603
68968
  capName: "heating-valve",
68604
68969
  capScope: "device",
@@ -78421,6 +78786,37 @@ var SENSOR_CAP_VOCABULARY = {
78421
78786
  icon: "flame"
78422
78787
  }]
78423
78788
  },
78789
+ "group-commands": {
78790
+ scope: "device",
78791
+ noun: t("Group command", "Comando di gruppo")
78792
+ },
78793
+ "group-status": {
78794
+ scope: "device",
78795
+ noun: t("Group", "Gruppo"),
78796
+ state: [
78797
+ {
78798
+ kind: "numeric",
78799
+ field: "included",
78800
+ quantity: t("the members reporting", "i membri che rispondono"),
78801
+ label: t("Reporting", "Rispondono"),
78802
+ icon: "users"
78803
+ },
78804
+ {
78805
+ kind: "numeric",
78806
+ field: "excluded",
78807
+ quantity: t("the members left out", "i membri esclusi"),
78808
+ label: t("Excluded", "Esclusi"),
78809
+ icon: "users"
78810
+ },
78811
+ {
78812
+ kind: "numeric",
78813
+ field: "total",
78814
+ quantity: t("the member count", "il numero di membri"),
78815
+ label: t("Members", "Membri"),
78816
+ icon: "sigma"
78817
+ }
78818
+ ]
78819
+ },
78424
78820
  "humidity-sensor": {
78425
78821
  scope: "device",
78426
78822
  noun: t("Humidity", "Umidità"),
@@ -78454,7 +78850,6 @@ var SENSOR_CAP_VOCABULARY = {
78454
78850
  field: "value",
78455
78851
  quantity: t("the reading", "il valore"),
78456
78852
  label: t("Reading", "Valore"),
78457
- unitField: "unit",
78458
78853
  icon: "gauge"
78459
78854
  }]
78460
78855
  },
@@ -78590,7 +78985,7 @@ var SENSOR_CAP_VOCABULARY = {
78590
78985
  field: "temperature",
78591
78986
  quantity: t("the outdoor temperature", "la temperatura esterna"),
78592
78987
  label: t("Temperature", "Temperatura"),
78593
- unitField: "temperatureUnit",
78988
+ unit: "°C",
78594
78989
  icon: "thermometer"
78595
78990
  },
78596
78991
  {
@@ -78606,7 +79001,7 @@ var SENSOR_CAP_VOCABULARY = {
78606
79001
  field: "windSpeed",
78607
79002
  quantity: t("the wind speed", "la velocità del vento"),
78608
79003
  label: t("Wind", "Vento"),
78609
- unitField: "windSpeedUnit",
79004
+ unit: "m/s",
78610
79005
  icon: "wind"
78611
79006
  },
78612
79007
  {
@@ -78614,7 +79009,7 @@ var SENSOR_CAP_VOCABULARY = {
78614
79009
  field: "pressure",
78615
79010
  quantity: t("the air pressure", "la pressione atmosferica"),
78616
79011
  label: t("Pressure", "Pressione"),
78617
- unitField: "pressureUnit",
79012
+ unit: "hPa",
78618
79013
  icon: "gauge"
78619
79014
  }
78620
79015
  ]
@@ -79887,6 +80282,16 @@ Object.freeze({
79887
80282
  form: "single",
79888
80283
  optional: false
79889
80284
  }],
80285
+ "groupCommands.getStatus": [{
80286
+ name: "deviceId",
80287
+ form: "single",
80288
+ optional: false
80289
+ }],
80290
+ "groupStatus.getStatus": [{
80291
+ name: "deviceId",
80292
+ form: "single",
80293
+ optional: false
80294
+ }],
79890
80295
  "heatingValve.getStatus": [{
79891
80296
  name: "deviceId",
79892
80297
  form: "single",
@@ -81871,6 +82276,8 @@ new Map(Object.entries({
81871
82276
  "detection-blind": "device-events",
81872
82277
  "motion-noisy": "device-events",
81873
82278
  "motion-calm": "device-events",
82279
+ "package-llm-silent": "device-events",
82280
+ "package-llm-answered": "device-events",
81874
82281
  "recording-stalled": "device-events",
81875
82282
  "recording-healthy": "device-events",
81876
82283
  "recording-degraded": "device-events",
@@ -81954,6 +82361,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
81954
82361
  label: "Motion without a subject",
81955
82362
  kinds: ["motion-noisy", "motion-calm"]
81956
82363
  },
82364
+ {
82365
+ id: "package-llm",
82366
+ family: "device-events",
82367
+ label: "Package confirm",
82368
+ kinds: ["package-llm-silent", "package-llm-answered"]
82369
+ },
81957
82370
  {
81958
82371
  id: "recording",
81959
82372
  family: "device-events",
@@ -82135,6 +82548,8 @@ var DEVICE_EVENTS = [
82135
82548
  "detection-blind",
82136
82549
  "motion-noisy",
82137
82550
  "motion-calm",
82551
+ "package-llm-silent",
82552
+ "package-llm-answered",
82138
82553
  "recording-stalled",
82139
82554
  "recording-healthy",
82140
82555
  "recording-degraded",
@@ -82403,6 +82818,15 @@ new Map([
82403
82818
  snapshotLabel: "Cameras with constant motion now",
82404
82819
  snapshotSection: null
82405
82820
  },
82821
+ {
82822
+ id: "package-llm-confirm",
82823
+ family: "device-events",
82824
+ label: "Package confirm (not answering / answering again)",
82825
+ bad: ["package-llm-silent"],
82826
+ recovery: ["package-llm-answered"],
82827
+ snapshotLabel: "Cameras whose package confirm is not answering",
82828
+ snapshotSection: null
82829
+ },
82406
82830
  {
82407
82831
  id: "recording-health",
82408
82832
  family: "device-events",