@camstack/addon-provider-ecowitt 0.2.148 → 0.2.150

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.
package/dist/addon.js CHANGED
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let http = require("http");
3
3
  let events = require("events");
4
4
  let dgram = require("dgram");
5
- //#region ../types/dist/event-category-CT-hz5Rb.mjs
5
+ //#region ../types/dist/event-category-DmZDPDf_.mjs
6
6
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
7
7
  EventCategory["SystemBoot"] = "system.boot";
8
8
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -629,7 +629,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
629
629
  * (a track was promoted to a stationary-registry entry) or departs (the
630
630
  * object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
631
631
  * the dedicated "Stationary" section — never the live event feed. Payload:
632
- * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
632
+ * `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp, observedAt, zones }`.
633
633
  */
634
634
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
635
635
  /**
@@ -767,6 +767,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
767
767
  */
768
768
  EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
769
769
  /**
770
+ * An action run started or ended. Emitted by the `actions` surface.
771
+ * Payload: `ActionRunChanged` (`{ actionId, runId, isRunning, outcome?, lastRunAt, lastError }`).
772
+ */
773
+ EventCategory["ActionsRunChanged"] = "actions.onRunChanged";
774
+ /**
770
775
  * Progress update from a running model conversion job.
771
776
  * Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
772
777
  * Emitted by `addon-model-studio` on the converting node.
@@ -5445,7 +5450,7 @@ var ZodIssueCode$1 = {
5445
5450
  var ZodFirstPartyTypeKind$1;
5446
5451
  ZodFirstPartyTypeKind$1 || (ZodFirstPartyTypeKind$1 = {});
5447
5452
  //#endregion
5448
- //#region ../types/dist/sleep-bykKrTgu.mjs
5453
+ //#region ../types/dist/sleep-Crlmu_tO.mjs
5449
5454
  /**
5450
5455
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5451
5456
  * window to float samples (D455).
@@ -8565,6 +8570,8 @@ Object.freeze({ "doorbell": "lastPressedAt" });
8565
8570
  /** Every capability name, so addon code can check a name without the schema graph. */
8566
8571
  var CAP_NAMES = Object.freeze([
8567
8572
  "accessories",
8573
+ "action-source",
8574
+ "actions",
8568
8575
  "addon-pages",
8569
8576
  "addon-pages-source",
8570
8577
  "addon-routes",
@@ -8638,6 +8645,7 @@ var CAP_NAMES = Object.freeze([
8638
8645
  "filesystem-browse",
8639
8646
  "flood",
8640
8647
  "gas",
8648
+ "group-active",
8641
8649
  "group-commands",
8642
8650
  "group-status",
8643
8651
  "heating-valve",
@@ -8660,6 +8668,7 @@ var CAP_NAMES = Object.freeze([
8660
8668
  "login-method",
8661
8669
  "media-player",
8662
8670
  "mesh-network",
8671
+ "meter",
8663
8672
  "metrics-provider",
8664
8673
  "model-convert",
8665
8674
  "model-distributor",
@@ -8751,11 +8760,11 @@ var CAP_NAMES = Object.freeze([
8751
8760
  Object.freeze({
8752
8761
  "air-quality-sensor": [
8753
8762
  {
8754
- "path": "co2Ppm",
8763
+ "path": "co2",
8755
8764
  "valueType": "number"
8756
8765
  },
8757
8766
  {
8758
- "path": "vocPpb",
8767
+ "path": "voc",
8759
8768
  "valueType": "number"
8760
8769
  },
8761
8770
  {
@@ -8805,7 +8814,7 @@ Object.freeze({
8805
8814
  }
8806
8815
  ],
8807
8816
  "ambient-light-sensor": [{
8808
- "path": "lux",
8817
+ "path": "illuminance",
8809
8818
  "valueType": "number"
8810
8819
  }],
8811
8820
  "audio-metrics": [
@@ -8866,7 +8875,7 @@ Object.freeze({
8866
8875
  ],
8867
8876
  "battery": [
8868
8877
  {
8869
- "path": "percentage",
8878
+ "path": "level",
8870
8879
  "valueType": "number"
8871
8880
  },
8872
8881
  {
@@ -8888,7 +8897,7 @@ Object.freeze({
8888
8897
  "valueType": "number"
8889
8898
  },
8890
8899
  {
8891
- "path": "volts",
8900
+ "path": "voltage",
8892
8901
  "valueType": "number"
8893
8902
  },
8894
8903
  {
@@ -8909,7 +8918,7 @@ Object.freeze({
8909
8918
  "valueType": "boolean"
8910
8919
  }],
8911
8920
  "brightness": [{
8912
- "path": "percentage",
8921
+ "path": "level",
8913
8922
  "valueType": "number"
8914
8923
  }],
8915
8924
  "broker": [{
@@ -9110,7 +9119,7 @@ Object.freeze({
9110
9119
  "valueType": "number"
9111
9120
  },
9112
9121
  {
9113
- "path": "mireds",
9122
+ "path": "colorTemperature",
9114
9123
  "valueType": "number"
9115
9124
  }
9116
9125
  ],
@@ -9289,15 +9298,15 @@ Object.freeze({
9289
9298
  "valueType": "string"
9290
9299
  },
9291
9300
  {
9292
- "path": "remainingSeconds",
9301
+ "path": "remaining",
9293
9302
  "valueType": "number"
9294
9303
  },
9295
9304
  {
9296
- "path": "delayedStartSeconds",
9305
+ "path": "delayedStart",
9297
9306
  "valueType": "number"
9298
9307
  },
9299
9308
  {
9300
- "path": "progressPercent",
9309
+ "path": "progress",
9301
9310
  "valueType": "number"
9302
9311
  },
9303
9312
  {
@@ -9411,7 +9420,7 @@ Object.freeze({
9411
9420
  ],
9412
9421
  "fan-control": [
9413
9422
  {
9414
- "path": "percentage",
9423
+ "path": "speed",
9415
9424
  "valueType": "number"
9416
9425
  },
9417
9426
  {
@@ -9458,6 +9467,10 @@ Object.freeze({
9458
9467
  "path": "detected",
9459
9468
  "valueType": "boolean"
9460
9469
  }],
9470
+ "group-active": [{
9471
+ "path": "active",
9472
+ "valueType": "boolean"
9473
+ }],
9461
9474
  "group-status": [
9462
9475
  {
9463
9476
  "path": "included",
@@ -9507,7 +9520,7 @@ Object.freeze({
9507
9520
  }
9508
9521
  ],
9509
9522
  "humidity-sensor": [{
9510
- "path": "percent",
9523
+ "path": "humidity",
9511
9524
  "valueType": "number"
9512
9525
  }],
9513
9526
  "image": [{
@@ -9617,7 +9630,7 @@ Object.freeze({
9617
9630
  "valueType": "number"
9618
9631
  },
9619
9632
  {
9620
- "path": "progressPercent",
9633
+ "path": "progress",
9621
9634
  "valueType": "number"
9622
9635
  },
9623
9636
  {
@@ -9696,11 +9709,11 @@ Object.freeze({
9696
9709
  "valueType": "string"
9697
9710
  },
9698
9711
  {
9699
- "path": "positionMs",
9712
+ "path": "position",
9700
9713
  "valueType": "number"
9701
9714
  },
9702
9715
  {
9703
- "path": "durationMs",
9716
+ "path": "duration",
9704
9717
  "valueType": "number"
9705
9718
  },
9706
9719
  {
@@ -9717,6 +9730,10 @@ Object.freeze({
9717
9730
  ]
9718
9731
  }
9719
9732
  ],
9733
+ "meter": [{
9734
+ "path": "value",
9735
+ "valueType": "number"
9736
+ }],
9720
9737
  "motion": [
9721
9738
  {
9722
9739
  "path": "detected",
@@ -9798,11 +9815,11 @@ Object.freeze({
9798
9815
  ]
9799
9816
  },
9800
9817
  {
9801
- "path": "signalPercent",
9818
+ "path": "signal",
9802
9819
  "valueType": "number"
9803
9820
  },
9804
9821
  {
9805
- "path": "rssiDbm",
9822
+ "path": "rssi",
9806
9823
  "valueType": "number"
9807
9824
  },
9808
9825
  {
@@ -9857,6 +9874,18 @@ Object.freeze({
9857
9874
  "path": "foodLevel",
9858
9875
  "valueType": "number"
9859
9876
  },
9877
+ {
9878
+ "path": "bowlFood",
9879
+ "valueType": "number"
9880
+ },
9881
+ {
9882
+ "path": "eating",
9883
+ "valueType": "boolean"
9884
+ },
9885
+ {
9886
+ "path": "supportsMarkRefilled",
9887
+ "valueType": "boolean"
9888
+ },
9860
9889
  {
9861
9890
  "path": "food1",
9862
9891
  "valueType": "number"
@@ -9921,23 +9950,23 @@ Object.freeze({
9921
9950
  ],
9922
9951
  "power-meter": [
9923
9952
  {
9924
- "path": "watts",
9953
+ "path": "power",
9925
9954
  "valueType": "number"
9926
9955
  },
9927
9956
  {
9928
- "path": "kwhTotal",
9957
+ "path": "energy",
9929
9958
  "valueType": "number"
9930
9959
  },
9931
9960
  {
9932
- "path": "volts",
9961
+ "path": "voltage",
9933
9962
  "valueType": "number"
9934
9963
  },
9935
9964
  {
9936
- "path": "amps",
9965
+ "path": "current",
9937
9966
  "valueType": "number"
9938
9967
  },
9939
9968
  {
9940
- "path": "va",
9969
+ "path": "apparentPower",
9941
9970
  "valueType": "number"
9942
9971
  },
9943
9972
  {
@@ -9965,14 +9994,10 @@ Object.freeze({
9965
9994
  {
9966
9995
  "path": "gps.accuracyMeters",
9967
9996
  "valueType": "number"
9968
- },
9969
- {
9970
- "path": "batteryPercent",
9971
- "valueType": "number"
9972
9997
  }
9973
9998
  ],
9974
9999
  "pressure-sensor": [{
9975
- "path": "hpa",
10000
+ "path": "pressure",
9976
10001
  "valueType": "number"
9977
10002
  }],
9978
10003
  "privacy-mask": [{
@@ -10286,7 +10311,7 @@ Object.freeze({
10286
10311
  "valueType": "boolean"
10287
10312
  }],
10288
10313
  "temperature-sensor": [{
10289
- "path": "celsius",
10314
+ "path": "temperature",
10290
10315
  "valueType": "number"
10291
10316
  }],
10292
10317
  "update": [
@@ -10334,7 +10359,7 @@ Object.freeze({
10334
10359
  "valueType": "string"
10335
10360
  },
10336
10361
  {
10337
- "path": "progressPercent",
10362
+ "path": "progress",
10338
10363
  "valueType": "number"
10339
10364
  },
10340
10365
  {
@@ -10474,7 +10499,7 @@ function userError(code, params, englishFallback) {
10474
10499
  return new CamstackError(code, params, englishFallback);
10475
10500
  }
10476
10501
  //#endregion
10477
- //#region ../types/dist/err-msg-BXKzRBwg.mjs
10502
+ //#region ../types/dist/err-msg-DIorGYeC.mjs
10478
10503
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10479
10504
  DeviceType["Camera"] = "camera";
10480
10505
  DeviceType["Hub"] = "hub";
@@ -10568,8 +10593,14 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
10568
10593
  DeviceType["PetFeeder"] = "pet-feeder";
10569
10594
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10570
10595
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10571
- * Sources: native Bosch Home Connect, reusable by similar integrations. */
10596
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
10597
+ * A plug-built dishwasher carries running-state and power instead. */
10572
10598
  DeviceType["Dishwasher"] = "dishwasher";
10599
+ /** Washing machine. A plug-built one is running while its power stays
10600
+ * above a threshold. No program cap — that surface is the dishwasher's. */
10601
+ DeviceType["Washer"] = "washer";
10602
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
10603
+ DeviceType["Dryer"] = "dryer";
10573
10604
  return DeviceType;
10574
10605
  }({});
10575
10606
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -10827,7 +10858,7 @@ function errMsg(err) {
10827
10858
  return String(err);
10828
10859
  }
10829
10860
  //#endregion
10830
- //#region ../types/dist/composition-OhpuespA.mjs
10861
+ //#region ../types/dist/composition-CZImLpOI.mjs
10831
10862
  /**
10832
10863
  * The ffmpeg build every CamStack node downloads, per platform and arch.
10833
10864
  *
@@ -11142,6 +11173,13 @@ var table = {
11142
11173
  apply: () => {
11143
11174
  throw new ExpressionEvalError("latchedAt: stateful evaluation context is required");
11144
11175
  }
11176
+ },
11177
+ schmitt: {
11178
+ minArgs: 3,
11179
+ maxArgs: 5,
11180
+ apply: () => {
11181
+ throw new ExpressionEvalError("schmitt: stateful evaluation context is required");
11182
+ }
11145
11183
  }
11146
11184
  };
11147
11185
  Object.freeze(Object.assign(Object.create(null), table));
@@ -11583,6 +11621,99 @@ function validateExpressionSource(src) {
11583
11621
  return null;
11584
11622
  }
11585
11623
  /**
11624
+ * One power reading becomes a washer, dryer or dishwasher (D944 helper).
11625
+ *
11626
+ * Standby of the control board is under 5 W; a motor or a heater is far
11627
+ * above that, so 5 W is the line. A door lock or a short pump spike is not
11628
+ * a cycle, so running starts only after 30 s above the line.
11629
+ *
11630
+ * A wash, dry or dishwasher pause (soak, heat-wait, heater off between
11631
+ * bursts) lasts several minutes. Ending the cycle on the first dip would
11632
+ * report "finished" in the middle of the program, so the stop dwell is
11633
+ * 10 minutes on every type. The number is stored per type so one machine
11634
+ * can move later without moving the others.
11635
+ */
11636
+ var APPLIANCE_TEMPLATE_ID = "appliance";
11637
+ var APPLIANCE_KINDS = [
11638
+ "washer",
11639
+ "dryer",
11640
+ "dishwasher"
11641
+ ];
11642
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
11643
+ /**
11644
+ * Statistics and integrals over a history read (D944).
11645
+ *
11646
+ * Pure: the same points always give the same answer, so a restart recomputes
11647
+ * rather than trusting a buffer. A gauge mean is time-weighted (value × ms),
11648
+ * never the mean of the samples. A hole with no held value is not integrated
11649
+ * as zero. A truncated read is not published.
11650
+ */
11651
+ var STATISTIC_STATS = [
11652
+ "min",
11653
+ "max",
11654
+ "mean",
11655
+ "sum",
11656
+ "count",
11657
+ "change"
11658
+ ];
11659
+ /**
11660
+ * The range a statistics or integral helper asks history for (D944).
11661
+ *
11662
+ * Calendar edges are the hub's timezone, never UTC midnight: a day is 23, 24
11663
+ * or 25 hours across a DST change. A tier is eligible only when its retention
11664
+ * is strictly longer than the longest that range can be. Forever qualifies.
11665
+ * Equal to a tier's retention is a refusal, not a short answer.
11666
+ */
11667
+ var MINUTE_MS = 6e4;
11668
+ var HOUR_MS = 60 * MINUTE_MS;
11669
+ var DAY_MS = 24 * HOUR_MS;
11670
+ var ROLLING_SPANS = [
11671
+ "1h",
11672
+ "24h",
11673
+ "2d",
11674
+ "7d",
11675
+ "30d",
11676
+ "custom"
11677
+ ];
11678
+ var CALENDAR_PERIODS = [
11679
+ "today",
11680
+ "yesterday",
11681
+ "this-week",
11682
+ "this-month"
11683
+ ];
11684
+ var RESET_PERIODS = [
11685
+ "never",
11686
+ "daily",
11687
+ "weekly",
11688
+ "monthly"
11689
+ ];
11690
+ 5 * MINUTE_MS;
11691
+ 24 * HOUR_MS, 2 * DAY_MS, 7 * DAY_MS, 30 * DAY_MS;
11692
+ 7 * DAY_MS, 30 * DAY_MS, 365 * DAY_MS;
11693
+ var rollingWindowObject = object({
11694
+ kind: literal("rolling"),
11695
+ span: _enum(ROLLING_SPANS),
11696
+ customMinutes: number().int().min(5).optional()
11697
+ }).check((payload) => {
11698
+ if (payload.value.span === "custom" && payload.value.customMinutes === void 0) payload.issues.push({
11699
+ code: "custom",
11700
+ message: "a custom range needs a duration in minutes",
11701
+ path: ["customMinutes"],
11702
+ input: payload.value
11703
+ });
11704
+ if (payload.value.span !== "custom" && payload.value.customMinutes !== void 0) payload.issues.push({
11705
+ code: "custom",
11706
+ message: "only a custom range carries a duration",
11707
+ path: ["customMinutes"],
11708
+ input: payload.value
11709
+ });
11710
+ });
11711
+ var RollingWindowSchema = rollingWindowObject;
11712
+ var StatisticWindowSchema = discriminatedUnion("kind", [rollingWindowObject, object({
11713
+ kind: literal("calendar"),
11714
+ period: _enum(CALENDAR_PERIODS)
11715
+ })]);
11716
+ /**
11586
11717
  * A composition source identity: `(addonId, stableId)`, never a numeric device
11587
11718
  * id. Split out so helper schemas can name it without a cycle through
11588
11719
  * `composition.ts` (D659).
@@ -11608,7 +11739,13 @@ var GROUP_TEMPLATE_IDS = [
11608
11739
  "occupancy",
11609
11740
  "binary",
11610
11741
  "switch",
11611
- "light"
11742
+ "light",
11743
+ "fan",
11744
+ "cover",
11745
+ "valve",
11746
+ "lock",
11747
+ "media-player",
11748
+ "climate"
11612
11749
  ];
11613
11750
  var NUMERIC_AGGREGATES = [
11614
11751
  "mean",
@@ -11638,14 +11775,73 @@ var HelperMemberSchema = object({
11638
11775
  cap: string().min(1),
11639
11776
  fieldPath: string().min(1)
11640
11777
  });
11641
- var HelperSpecSchema = discriminatedUnion("kind", [object({
11642
- kind: literal("group"),
11643
- templateId: GroupTemplateIdSchema,
11644
- aggregate: AggregateReducerSchema,
11645
- members: array(HelperMemberSchema).min(1).max(64),
11646
- /** Actuator groups only. Readings leave this unset. */
11647
- active: ActiveCriterionSchema.optional()
11648
- })]);
11778
+ var HelperSpecSchema = discriminatedUnion("kind", [
11779
+ object({
11780
+ kind: literal("group"),
11781
+ templateId: GroupTemplateIdSchema,
11782
+ aggregate: AggregateReducerSchema,
11783
+ members: array(HelperMemberSchema).min(1).max(64),
11784
+ /** Actuator groups only. Readings leave this unset. */
11785
+ active: ActiveCriterionSchema.optional()
11786
+ }),
11787
+ object({
11788
+ kind: literal("appliance"),
11789
+ templateId: literal(APPLIANCE_TEMPLATE_ID),
11790
+ appliance: _enum(APPLIANCE_KINDS),
11791
+ thresholdWatts: number().positive(),
11792
+ startDwellMs: number().int().nonnegative(),
11793
+ stopDwellMs: number().int().nonnegative(),
11794
+ member: HelperMemberSchema
11795
+ }),
11796
+ object({
11797
+ kind: literal("statistic"),
11798
+ stat: _enum(STATISTIC_STATS),
11799
+ window: StatisticWindowSchema,
11800
+ source: HelperMemberSchema
11801
+ }),
11802
+ object({
11803
+ kind: literal("integral"),
11804
+ reset: _enum(RESET_PERIODS),
11805
+ output: _enum(["energy", "meter"]),
11806
+ source: HelperMemberSchema
11807
+ }),
11808
+ object({
11809
+ kind: literal("threshold"),
11810
+ source: HelperMemberSchema,
11811
+ high: number(),
11812
+ low: number(),
11813
+ onForMs: number().int().nonnegative(),
11814
+ offForMs: number().int().nonnegative()
11815
+ }),
11816
+ object({
11817
+ kind: literal("arithmetic"),
11818
+ op: _enum([
11819
+ "sum",
11820
+ "difference",
11821
+ "ratio"
11822
+ ]),
11823
+ inputs: array(HelperMemberSchema).min(2).max(64)
11824
+ }),
11825
+ object({
11826
+ kind: literal("expression"),
11827
+ expr: string().min(1),
11828
+ inputs: array(object({
11829
+ name: string().min(1),
11830
+ member: HelperMemberSchema
11831
+ })).min(1).max(64),
11832
+ output: _enum(["number", "boolean"])
11833
+ }),
11834
+ object({
11835
+ kind: literal("derivative"),
11836
+ window: RollingWindowSchema,
11837
+ per: _enum([
11838
+ "second",
11839
+ "minute",
11840
+ "hour"
11841
+ ]),
11842
+ source: HelperMemberSchema
11843
+ })
11844
+ ]);
11649
11845
  /** A composed device's stableId is this prefix plus the block id. */
11650
11846
  var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11651
11847
  /** Longest snippet a `code` source may carry (slice 4). */
@@ -11706,6 +11902,12 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11706
11902
  "all"
11707
11903
  ]),
11708
11904
  reads: array(CompositionFieldReadSchema).min(1).max(64),
11905
+ /**
11906
+ * Set when the reads are an enum state, not a yes/no: the listed states
11907
+ * count as active (a cover is `open`). A state outside the list is inactive;
11908
+ * a read that is not a state at all is left out. Only `any` / `all` use it.
11909
+ */
11910
+ activeWhen: array(string().min(1)).min(1).optional(),
11709
11911
  /** `value` is the reading; the others are the member counts. */
11710
11912
  publish: _enum([
11711
11913
  "value",
@@ -11719,6 +11921,26 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11719
11921
  "omit",
11720
11922
  "offline"
11721
11923
  ])
11924
+ }),
11925
+ object({
11926
+ kind: literal("series"),
11927
+ read: CompositionFieldReadSchema,
11928
+ /** `value` is the statistic or the integral. The others are the gap counts. */
11929
+ publish: _enum([
11930
+ "value",
11931
+ "included",
11932
+ "excluded",
11933
+ "total"
11934
+ ]),
11935
+ /**
11936
+ * What an unanswered `value` does. Counts ignore this. `offline` leaves the
11937
+ * device unseeded until history answers, so a restart cannot publish 0.
11938
+ */
11939
+ empty: _enum([
11940
+ "null",
11941
+ "omit",
11942
+ "offline"
11943
+ ])
11722
11944
  })
11723
11945
  ]);
11724
11946
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
@@ -15156,6 +15378,14 @@ var AutomationConditionLeafSchema = object({
15156
15378
  array(union([string(), number()]))
15157
15379
  ])
15158
15380
  });
15381
+ var AutomationConditionStationaryPresentSchema = object({
15382
+ kind: literal("stationary-present"),
15383
+ deviceId: number().int().nonnegative(),
15384
+ /** Class names; a macro class matches every raw class that maps to it. */
15385
+ classes: array(string().min(1)).min(1).optional(),
15386
+ /** The parked object must be in at least one of these zones. */
15387
+ zones: array(string().min(1)).min(1).optional()
15388
+ });
15159
15389
  /**
15160
15390
  * The expression leaf, declared as a plain object rather than an intersection
15161
15391
  * with {@link ExpressionSourceSchema}: a discriminated union has to be able to
@@ -15189,7 +15419,8 @@ var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
15189
15419
  child: AutomationConditionSchema
15190
15420
  }),
15191
15421
  AutomationConditionLeafSchema,
15192
- AutomationConditionExpressionSchema
15422
+ AutomationConditionExpressionSchema,
15423
+ AutomationConditionStationaryPresentSchema
15193
15424
  ]));
15194
15425
  /**
15195
15426
  * What starts a run.
@@ -15230,7 +15461,34 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
15230
15461
  kind: literal("schedule"),
15231
15462
  cron: string().min(1).max(120)
15232
15463
  }),
15233
- object({ kind: literal("manual") })
15464
+ object({ kind: literal("manual") }),
15465
+ object({
15466
+ kind: literal("detection"),
15467
+ /** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
15468
+ * matches every raw class that maps to it; any other name matches
15469
+ * exactly. */
15470
+ classes: array(string().min(1)).min(1),
15471
+ deviceIds: array(number().int().nonnegative()).min(1),
15472
+ /** The track must have visited at least one of these zones. */
15473
+ zones: array(string().min(1)).min(1).optional(),
15474
+ /** Best-so-far confidence the track must reach. */
15475
+ minScore: number().min(0).max(1).optional()
15476
+ }),
15477
+ object({
15478
+ kind: literal("stationary"),
15479
+ deviceIds: array(number().int().nonnegative()).min(1),
15480
+ /** Class names; a macro class matches like the detection trigger. */
15481
+ classes: array(string().min(1)).min(1).optional(),
15482
+ /** The parked object must be in at least one of these zones. */
15483
+ zones: array(string().min(1)).min(1).optional(),
15484
+ /** `appeared`: it became parked. `departed`: the registry retired it.
15485
+ * The vocabulary of `PipelineAnalyticsStationaryChanged`. */
15486
+ phase: _enum(["appeared", "departed"])
15487
+ }),
15488
+ object({
15489
+ kind: literal("interval"),
15490
+ minutes: number().int().min(1).max(1440)
15491
+ })
15234
15492
  ]);
15235
15493
  /**
15236
15494
  * One action step.
@@ -15250,28 +15508,47 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
15250
15508
  * is called. The runner still receives a number and is untouched; the
15251
15509
  * resolution is the recipe's job, not the runner's.
15252
15510
  */
15511
+ var AutomationWaitStepSchema = object({
15512
+ kind: literal("wait"),
15513
+ seconds: number().min(0).max(300)
15514
+ });
15515
+ var AutomationCapStepSchema = object({
15516
+ kind: literal("cap"),
15517
+ /**
15518
+ * One device, several same-family devices (locks / climate family / …),
15519
+ * or `{ $var }` for the device that triggered the run. An array is
15520
+ * fan-out at codegen — each id gets the same cap.method call.
15521
+ */
15522
+ deviceId: union([
15523
+ number().int(),
15524
+ array(number().int()).min(1),
15525
+ object({ $var: string().min(1) })
15526
+ ]),
15527
+ cap: string().min(1),
15528
+ method: string().min(1),
15529
+ /** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
15530
+ * SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
15531
+ * the expression leaf. */
15532
+ args: record(string(), unknown()).optional()
15533
+ });
15534
+ /**
15535
+ * The steps an internal Action may contain: cap and wait, and nothing that can
15536
+ * call back into an Action — so an Action can never reach itself (no cycles).
15537
+ */
15538
+ var ActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCapStepSchema]);
15253
15539
  var AutomationActionSchema = discriminatedUnion("kind", [
15540
+ AutomationWaitStepSchema,
15541
+ AutomationCapStepSchema,
15254
15542
  object({
15255
- kind: literal("wait"),
15256
- seconds: number().min(0).max(300)
15257
- }),
15258
- object({
15259
- kind: literal("cap"),
15260
- /**
15261
- * One device, several same-family devices (locks / climate family / …),
15262
- * or `{ $var }` for the device that triggered the run. An array is
15263
- * fan-out at codegen — each id gets the same cap.method call.
15264
- */
15265
- deviceId: union([
15266
- number().int(),
15267
- array(number().int()).min(1),
15268
- object({ $var: string().min(1) })
15269
- ]),
15270
- cap: string().min(1),
15271
- method: string().min(1),
15272
- /** Values may carry `{{vars.x}}` slots, which SUBSTITUTE and do not
15273
- * evaluate (§3.2.3). Anything beyond substitution is the expression leaf. */
15274
- args: record(string(), unknown()).optional()
15543
+ kind: literal("action"),
15544
+ /** An entry of the `actions` surface (`internal:<uuid>` or `<addonId>:<localId>`). */
15545
+ actionId: string().min(1),
15546
+ /** Values for the action's declared variables; the others take their default. */
15547
+ variables: record(string(), union([
15548
+ string(),
15549
+ number(),
15550
+ boolean()
15551
+ ])).optional()
15275
15552
  }),
15276
15553
  object({
15277
15554
  kind: literal("code"),
@@ -15280,10 +15557,357 @@ var AutomationActionSchema = discriminatedUnion("kind", [
15280
15557
  code: string().min(1).max(2e4)
15281
15558
  })
15282
15559
  ]);
15560
+ /**
15561
+ * How a trigger that arrives while the automation is busy (or cooling down) is
15562
+ * handled. One of the automation's execution modes, never a property of its
15563
+ * actions. Absent means `single`.
15564
+ *
15565
+ * - `single` — drop the trigger while a run is in progress (traced + logged).
15566
+ * - `restart` — abort the run in progress (at its next step boundary), start fresh.
15567
+ * - `queued` — queue up to `max` triggers, drop the rest (traced).
15568
+ * - `parallel` — up to `max` runs at once, drop the rest (traced).
15569
+ * - `cooldown` — `single`, and no new run until `minutes` after the last run
15570
+ * STARTED; persisted across a restart. A manual trigger bypasses only this
15571
+ * wait, never the single/queued limits.
15572
+ */
15573
+ var AutomationRunModeSchema = discriminatedUnion("mode", [
15574
+ object({ mode: literal("single") }),
15575
+ object({ mode: literal("restart") }),
15576
+ object({
15577
+ mode: literal("queued"),
15578
+ max: number().int().min(1).max(10)
15579
+ }),
15580
+ object({
15581
+ mode: literal("parallel"),
15582
+ max: number().int().min(1).max(10)
15583
+ }),
15584
+ object({
15585
+ mode: literal("cooldown"),
15586
+ minutes: number().int().min(1).max(1440)
15587
+ })
15588
+ ]);
15283
15589
  var AutomationRecipeSchema = object({
15284
15590
  triggers: array(AutomationTriggerSchema),
15285
15591
  conditions: AutomationConditionSchema.optional(),
15286
- actions: array(AutomationActionSchema)
15592
+ actions: array(AutomationActionSchema),
15593
+ runMode: AutomationRunModeSchema.optional(),
15594
+ /** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
15595
+ keepTraces: number().int().min(1).max(100).optional()
15596
+ });
15597
+ new Set([
15598
+ {
15599
+ id: "person",
15600
+ name: "Person"
15601
+ },
15602
+ {
15603
+ id: "vehicle",
15604
+ name: "Vehicle"
15605
+ },
15606
+ {
15607
+ id: "animal",
15608
+ name: "Animal"
15609
+ },
15610
+ {
15611
+ id: "package",
15612
+ name: "Package"
15613
+ }
15614
+ ].map((l) => l.id));
15615
+ var COCO_TO_MACRO = {
15616
+ mapping: {
15617
+ person: "person",
15618
+ bicycle: "vehicle",
15619
+ car: "vehicle",
15620
+ motorcycle: "vehicle",
15621
+ airplane: "vehicle",
15622
+ bus: "vehicle",
15623
+ train: "vehicle",
15624
+ truck: "vehicle",
15625
+ boat: "vehicle",
15626
+ bird: "animal",
15627
+ cat: "animal",
15628
+ dog: "animal",
15629
+ horse: "animal",
15630
+ sheep: "animal",
15631
+ cow: "animal",
15632
+ elephant: "animal",
15633
+ bear: "animal",
15634
+ zebra: "animal",
15635
+ giraffe: "animal",
15636
+ suitcase: "package",
15637
+ backpack: "package",
15638
+ handbag: "package"
15639
+ },
15640
+ preserveOriginal: false
15641
+ };
15642
+ new Set(Object.values(COCO_TO_MACRO.mapping));
15643
+ /**
15644
+ * Run traces — what an automation (or, later, an Action run) DID, node by node,
15645
+ * so the editor can paint every block green or red with its reason.
15646
+ *
15647
+ * A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
15648
+ * every condition node (containers and leaves, including the ones a
15649
+ * short-circuit never reached), the run-mode verdict and every action step —
15650
+ * each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
15651
+ * for an Action run). Skipped
15652
+ * evaluations are traced too, and identical consecutive skips collapse into a
15653
+ * repeat count so a trigger that fails every 5 minutes cannot flush real runs.
15654
+ *
15655
+ * {@link AutomationStepTraceSchema} is deliberately free of automation
15656
+ * vocabulary so an Action run records the same step shape.
15657
+ */
15658
+ /** A value a trace may show: JSON primitives and short lists, nothing deeper. */
15659
+ var TraceValueSchema = union([
15660
+ string(),
15661
+ number(),
15662
+ boolean(),
15663
+ _null(),
15664
+ array(union([string(), number()]))
15665
+ ]);
15666
+ var AutomationRunOutcomeSchema = _enum([
15667
+ "ran",
15668
+ "skipped-trigger",
15669
+ "skipped-conditions",
15670
+ "skipped-cooldown",
15671
+ "skipped-overlap",
15672
+ "failed",
15673
+ "aborted"
15674
+ ]);
15675
+ /** `not-evaluated` = a short-circuit never reached the node. */
15676
+ var TraceNodeOutcomeSchema = _enum([
15677
+ "pass",
15678
+ "fail",
15679
+ "not-evaluated"
15680
+ ]);
15681
+ var AutomationTriggerTraceSchema = object({
15682
+ kind: string().min(1),
15683
+ outcome: _enum(["pass", "fail"]),
15684
+ /** Why an event was rejected ("class person not in [dog]") or what fired. */
15685
+ reason: string(),
15686
+ /** deviceId, className, trackId, score … — only what the trigger exposes. */
15687
+ vars: record(string(), TraceValueSchema)
15688
+ });
15689
+ var AutomationConditionTraceNodeSchema = object({
15690
+ /** `conditions` for the root, `conditions.<i>` for its i-th child, … */
15691
+ path: string().min(1),
15692
+ kind: _enum([
15693
+ "all",
15694
+ "any",
15695
+ "not",
15696
+ "condition",
15697
+ "expression",
15698
+ "stationary-present"
15699
+ ]),
15700
+ outcome: TraceNodeOutcomeSchema,
15701
+ reason: string(),
15702
+ deviceId: number().int().optional(),
15703
+ cap: string().optional(),
15704
+ fieldPath: string().optional(),
15705
+ operator: string().optional(),
15706
+ expected: TraceValueSchema.optional(),
15707
+ /** What the mirror held; `null` when it held nothing (unknown). */
15708
+ observed: TraceValueSchema.optional()
15709
+ });
15710
+ var AutomationRunVerdictSchema = object({
15711
+ mode: AutomationRunModeSchema,
15712
+ decision: _enum([
15713
+ "run",
15714
+ "queued",
15715
+ "restart",
15716
+ "drop",
15717
+ "cooldown",
15718
+ "bypass"
15719
+ ]),
15720
+ reason: string()
15721
+ });
15722
+ /** One executed (or not-run) step — shared by automations and Action runs. */
15723
+ var AutomationStepTraceSchema = object({
15724
+ /** `actions.<i>` */
15725
+ path: string().min(1),
15726
+ kind: string().min(1),
15727
+ cap: string().optional(),
15728
+ method: string().optional(),
15729
+ deviceId: number().int().optional(),
15730
+ startedAt: number().optional(),
15731
+ endedAt: number().optional(),
15732
+ outcome: _enum([
15733
+ "ok",
15734
+ "error",
15735
+ "not-run"
15736
+ ]),
15737
+ message: string().optional()
15738
+ });
15739
+ var AutomationRunTraceSchema = object({
15740
+ runId: string().min(1),
15741
+ startedAt: number(),
15742
+ endedAt: number(),
15743
+ trigger: AutomationTriggerTraceSchema,
15744
+ conditions: array(AutomationConditionTraceNodeSchema),
15745
+ verdict: AutomationRunVerdictSchema.optional(),
15746
+ steps: array(AutomationStepTraceSchema),
15747
+ outcome: AutomationRunOutcomeSchema,
15748
+ /** How many identical consecutive skips this trace stands for (>= 1). */
15749
+ repeat: number().int().min(1),
15750
+ /** When the latest of those repeats happened. */
15751
+ lastAt: number()
15752
+ });
15753
+ /**
15754
+ * What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
15755
+ * `action:<actionId>`): the cooldown stamp and the trace ring.
15756
+ */
15757
+ var AutomationStateDocSchema = object({
15758
+ version: literal(1),
15759
+ lastRunStartedAt: number().nullable(),
15760
+ traces: array(AutomationRunTraceSchema)
15761
+ });
15762
+ /**
15763
+ * Actions — named, runnable things an operator (or an automation) can start,
15764
+ * declared as LIST ENTRIES, never as devices.
15765
+ *
15766
+ * An entry is either **internal** (a short list of cap/wait steps kept in the
15767
+ * hub's `actions` record kind) or declared by an addon through the
15768
+ * `action-source` collection (a Home Assistant script, a scene …). The one
15769
+ * surface the UI and automations talk to is the `actions` cap; it keeps no
15770
+ * catalog of its own (D3) — every `list` asks the providers live.
15771
+ *
15772
+ * This file is the data half: the entry schema, the variables an entry may
15773
+ * declare, and the pure resolution of the values a run was given. It holds no
15774
+ * runtime.
15775
+ */
15776
+ /** What a variable's value is. `select` takes one of `options`. */
15777
+ var ActionVariableTypeSchema = _enum([
15778
+ "string",
15779
+ "number",
15780
+ "boolean",
15781
+ "select"
15782
+ ]);
15783
+ var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
15784
+ var ActionVariableValueSchema = union([
15785
+ string(),
15786
+ number(),
15787
+ boolean()
15788
+ ]);
15789
+ /**
15790
+ * One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
15791
+ * value) or `{{vars.name}}` (inside a string) in their args.
15792
+ */
15793
+ var ActionVariableSchema = object({
15794
+ name: string().regex(ACTION_VARIABLE_NAME_RE),
15795
+ label: string().min(1).max(120).optional(),
15796
+ type: ActionVariableTypeSchema,
15797
+ required: boolean().optional(),
15798
+ default: ActionVariableValueSchema.optional(),
15799
+ /** The allowed values of a `select`. */
15800
+ options: array(string().min(1)).min(1).optional()
15801
+ }).superRefine((v, ctx) => {
15802
+ if (v.type === "select" && v.options === void 0) ctx.addIssue({
15803
+ code: "custom",
15804
+ message: "a select variable needs options",
15805
+ path: ["options"]
15806
+ });
15807
+ if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
15808
+ code: "custom",
15809
+ message: variableValueError(v, v.default) ?? "invalid default",
15810
+ path: ["default"]
15811
+ });
15812
+ });
15813
+ /** Why `value` is not a legal value for the variable, or null when it is. */
15814
+ function variableValueError(variable, value) {
15815
+ switch (variable.type) {
15816
+ case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
15817
+ case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
15818
+ case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
15819
+ case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
15820
+ }
15821
+ }
15822
+ var ActionSourceKindSchema = _enum(["internal", "addon"]);
15823
+ function isInternalActionId(actionId) {
15824
+ return actionId.startsWith("internal:") && actionId.length > 9;
15825
+ }
15826
+ /** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
15827
+ function addonIdOfActionId(actionId) {
15828
+ if (isInternalActionId(actionId)) return null;
15829
+ const at = actionId.indexOf(":");
15830
+ return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
15831
+ }
15832
+ var ActionStatusSchema = object({
15833
+ isRunning: boolean(),
15834
+ /** Ms epoch the latest run STARTED; null when it never ran. */
15835
+ lastRunAt: number().nullable(),
15836
+ /** The latest run's failure; null on success or when it never ran. */
15837
+ lastError: string().nullable()
15838
+ });
15839
+ var ActionEntrySchema = object({
15840
+ /** `internal:<uuid>` or `<addonId>:<localId>`. */
15841
+ id: string().min(3),
15842
+ name: string().min(1).max(120),
15843
+ /** The addon that declared it; `core-blocks` for an internal one. */
15844
+ addonId: string().min(1),
15845
+ source: ActionSourceKindSchema,
15846
+ /** An addon-declared entry is edited where it was declared. */
15847
+ readOnly: boolean(),
15848
+ /** Internal only: cap and wait steps. No action-in-action, so no cycles. */
15849
+ steps: array(ActionStepSchema).min(1).max(50).optional(),
15850
+ variables: array(ActionVariableSchema).max(20).optional(),
15851
+ status: ActionStatusSchema
15852
+ }).superRefine((entry, ctx) => {
15853
+ if (entry.source === "internal") {
15854
+ if (!isInternalActionId(entry.id)) ctx.addIssue({
15855
+ code: "custom",
15856
+ message: "an internal action id is internal:<uuid>",
15857
+ path: ["id"]
15858
+ });
15859
+ return;
15860
+ }
15861
+ if (entry.steps !== void 0) ctx.addIssue({
15862
+ code: "custom",
15863
+ message: "only an internal action carries steps",
15864
+ path: ["steps"]
15865
+ });
15866
+ if (!entry.readOnly) ctx.addIssue({
15867
+ code: "custom",
15868
+ message: "an addon action is read-only here",
15869
+ path: ["readOnly"]
15870
+ });
15871
+ if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
15872
+ code: "custom",
15873
+ message: "an addon action id is <addonId>:<localId>",
15874
+ path: ["id"]
15875
+ });
15876
+ });
15877
+ /** What an operator writes for an internal action. */
15878
+ var ActionInputSchema = object({
15879
+ name: string().min(1).max(120),
15880
+ steps: array(ActionStepSchema).min(1).max(50),
15881
+ variables: array(ActionVariableSchema).max(20).optional()
15882
+ });
15883
+ /** Who started a run. Absent = the operator pressed it. */
15884
+ var ActionRunOriginSchema = object({
15885
+ /** The automation block whose step started it. */
15886
+ blockId: string().min(1).optional() });
15887
+ /** `started` = the caller did not ask to wait. */
15888
+ var ActionRunOutcomeSchema = union([AutomationRunOutcomeSchema, literal("started")]);
15889
+ var ActionRunResultSchema = object({
15890
+ runId: string().min(1),
15891
+ outcome: ActionRunOutcomeSchema,
15892
+ /** Why it did not run, or what failed. */
15893
+ message: string().optional()
15894
+ });
15895
+ _enum([
15896
+ "not-found",
15897
+ "read-only",
15898
+ "invalid-variables",
15899
+ "invalid-steps",
15900
+ "not-linked",
15901
+ "busy"
15902
+ ]);
15903
+ object({
15904
+ actionId: string(),
15905
+ runId: string(),
15906
+ isRunning: boolean(),
15907
+ /** Set when the run ended. */
15908
+ outcome: AutomationRunOutcomeSchema.optional(),
15909
+ lastRunAt: number().nullable(),
15910
+ lastError: string().nullable()
15287
15911
  });
15288
15912
  /**
15289
15913
  * `addon-pages` — system-scoped singleton aggregator cap. Public-facing
@@ -16878,7 +17502,22 @@ var BrokerProviderInfoSchema = object({
16878
17502
  */
16879
17503
  interactiveConnectPath: string().min(1).optional(),
16880
17504
  /** Relative hub path to disconnect the linked account (same popup pattern). */
16881
- interactiveDisconnectPath: string().min(1).optional()
17505
+ interactiveDisconnectPath: string().min(1).optional(),
17506
+ /**
17507
+ * The kind signs in through a server-held interactive session instead of
17508
+ * (or before) typed credentials. `basePath` is a hub-relative route set
17509
+ * served by the owning addon (`/addon/<id>/...`) speaking the sign-in
17510
+ * contract: `POST {basePath}/start` (body: the form's settings) answers
17511
+ * `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
17512
+ * `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
17513
+ * 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
17514
+ * (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
17515
+ * existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
17516
+ * (`{ sessionId }`) drops the session. The dialog that edits a broker
17517
+ * offers it as the default path; the fields the session fills are
17518
+ * marked `filledBySignIn` in the kind's settings schema.
17519
+ */
17520
+ signIn: object({ basePath: string().min(1) }).optional()
16882
17521
  }))
16883
17522
  });
16884
17523
  var ListInputSchema = object({
@@ -17955,7 +18594,7 @@ var CompositionEventPlanSchema = object({
17955
18594
  stateField: string(),
17956
18595
  fieldKey: string()
17957
18596
  });
17958
- /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
18597
+ /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.temperature`. */
17959
18598
  var CompositionProblemSchema = object({
17960
18599
  code: _enum([
17961
18600
  "reserved-variant",
@@ -17983,7 +18622,8 @@ var CompositionProblemSchema = object({
17983
18622
  "native-capability-replace",
17984
18623
  "mixed-quantity",
17985
18624
  "helper-mismatch",
17986
- "member-constraint"
18625
+ "member-constraint",
18626
+ "retention"
17987
18627
  ]),
17988
18628
  path: string(),
17989
18629
  message: string()
@@ -18217,13 +18857,17 @@ Object.freeze({
18217
18857
  latchedAt: {
18218
18858
  kind: "fixed",
18219
18859
  result: ["number", "null"]
18860
+ },
18861
+ schmitt: {
18862
+ kind: "fixed",
18863
+ result: ["boolean", "null"]
18220
18864
  }
18221
18865
  });
18222
18866
  /**
18223
18867
  * One catalog row per base reading a group helper can build (D944 §3).
18224
18868
  * Data, not a form default written into the block.
18225
18869
  */
18226
- var NOW = {
18870
+ var NOW$2 = {
18227
18871
  fieldPath: "lastFetchedAt",
18228
18872
  expr: "now"
18229
18873
  };
@@ -18237,6 +18881,8 @@ function numeric(input) {
18237
18881
  family: "numeric",
18238
18882
  deviceType: DeviceType.Sensor,
18239
18883
  memberDeviceType: DeviceType.Sensor,
18884
+ memberDeviceTypes: [DeviceType.Sensor],
18885
+ activeWhen: void 0,
18240
18886
  commandCaps: [],
18241
18887
  baseUnit: baseUnitOf(input.quantity),
18242
18888
  aggregates: NUMERIC_AGGREGATES,
@@ -18252,6 +18898,8 @@ function binary(input) {
18252
18898
  family: "binary",
18253
18899
  deviceType: DeviceType.Sensor,
18254
18900
  memberDeviceType: DeviceType.Sensor,
18901
+ memberDeviceTypes: [DeviceType.Sensor],
18902
+ activeWhen: void 0,
18255
18903
  commandCaps: [],
18256
18904
  quantity: "none",
18257
18905
  baseUnit: void 0,
@@ -18265,73 +18913,73 @@ var GROUP_TEMPLATES = [
18265
18913
  summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18266
18914
  role: DeviceRole.TemperatureSensor,
18267
18915
  outputCap: "temperature-sensor",
18268
- outputField: "celsius",
18916
+ outputField: "temperature",
18269
18917
  quantity: "temperature",
18270
18918
  defaultAggregate: "mean",
18271
18919
  empty: "null",
18272
- companions: [NOW]
18920
+ companions: [NOW$2]
18273
18921
  }),
18274
18922
  numeric({
18275
18923
  id: "humidity",
18276
18924
  summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18277
18925
  role: DeviceRole.HumiditySensor,
18278
18926
  outputCap: "humidity-sensor",
18279
- outputField: "percent",
18927
+ outputField: "humidity",
18280
18928
  quantity: "humidity",
18281
18929
  defaultAggregate: "mean",
18282
18930
  empty: "null",
18283
- companions: [NOW]
18931
+ companions: [NOW$2]
18284
18932
  }),
18285
18933
  numeric({
18286
18934
  id: "pressure",
18287
18935
  summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18288
18936
  role: DeviceRole.PressureSensor,
18289
18937
  outputCap: "pressure-sensor",
18290
- outputField: "hpa",
18938
+ outputField: "pressure",
18291
18939
  quantity: "pressure",
18292
18940
  defaultAggregate: "mean",
18293
18941
  empty: "null",
18294
- companions: [NOW]
18942
+ companions: [NOW$2]
18295
18943
  }),
18296
18944
  numeric({
18297
18945
  id: "illuminance",
18298
18946
  summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18299
18947
  role: DeviceRole.AmbientLightSensor,
18300
18948
  outputCap: "ambient-light-sensor",
18301
- outputField: "lux",
18949
+ outputField: "illuminance",
18302
18950
  quantity: "illuminance",
18303
18951
  defaultAggregate: "mean",
18304
18952
  empty: "null",
18305
- companions: [NOW]
18953
+ companions: [NOW$2]
18306
18954
  }),
18307
18955
  numeric({
18308
18956
  id: "power",
18309
18957
  summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18310
18958
  role: DeviceRole.PowerSensor,
18311
18959
  outputCap: "power-meter",
18312
- outputField: "watts",
18960
+ outputField: "power",
18313
18961
  quantity: "power",
18314
18962
  defaultAggregate: "sum",
18315
18963
  empty: "omit",
18316
- companions: [NOW]
18964
+ companions: [NOW$2]
18317
18965
  }),
18318
18966
  numeric({
18319
18967
  id: "energy",
18320
18968
  summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18321
18969
  role: DeviceRole.EnergySensor,
18322
18970
  outputCap: "power-meter",
18323
- outputField: "kwhTotal",
18971
+ outputField: "energy",
18324
18972
  quantity: "energy",
18325
18973
  defaultAggregate: "sum",
18326
18974
  empty: "omit",
18327
- companions: [NOW]
18975
+ companions: [NOW$2]
18328
18976
  }),
18329
18977
  numeric({
18330
18978
  id: "battery",
18331
18979
  summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18332
18980
  role: DeviceRole.BatterySensor,
18333
18981
  outputCap: "battery",
18334
- outputField: "percentage",
18982
+ outputField: "level",
18335
18983
  quantity: "percentage",
18336
18984
  defaultAggregate: "mean",
18337
18985
  empty: "null",
@@ -18359,7 +19007,7 @@ var GROUP_TEMPLATES = [
18359
19007
  quantity: "device",
18360
19008
  defaultAggregate: "mean",
18361
19009
  empty: "null",
18362
- companions: [NOW]
19010
+ companions: [NOW$2]
18363
19011
  }),
18364
19012
  binary({
18365
19013
  id: "motion",
@@ -18453,6 +19101,11 @@ var GROUP_TEMPLATES = [
18453
19101
  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.",
18454
19102
  deviceType: DeviceType.Switch,
18455
19103
  outputCap: "switch",
19104
+ outputField: "on",
19105
+ member: {
19106
+ cap: "switch",
19107
+ fieldPath: "on"
19108
+ },
18456
19109
  commandCaps: ["switch"]
18457
19110
  }),
18458
19111
  actuator({
@@ -18460,34 +19113,136 @@ var GROUP_TEMPLATES = [
18460
19113
  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.",
18461
19114
  deviceType: DeviceType.Light,
18462
19115
  outputCap: "switch",
19116
+ outputField: "on",
19117
+ member: {
19118
+ cap: "switch",
19119
+ fieldPath: "on"
19120
+ },
18463
19121
  commandCaps: [
18464
19122
  "switch",
18465
19123
  "brightness",
18466
19124
  "color"
18467
19125
  ]
19126
+ }),
19127
+ actuator({
19128
+ id: "fan",
19129
+ summary: "On when at least one fan is on, or when all of them are. Speed, preset, direction and oscillation are offered only when every fan has them. A command is not sent to a fan that lacks it.",
19130
+ deviceType: DeviceType.Fan,
19131
+ outputCap: "switch",
19132
+ outputField: "on",
19133
+ member: {
19134
+ cap: "switch",
19135
+ fieldPath: "on"
19136
+ },
19137
+ commandCaps: ["switch", "fan-control"]
19138
+ }),
19139
+ actuator({
19140
+ id: "cover",
19141
+ summary: "Open when at least one cover is open or part-open, or only when all of them are. Position and tilt are offered only when every cover has them. A cover that reports nothing is left out, never read as closed.",
19142
+ deviceType: DeviceType.Cover,
19143
+ outputCap: "group-active",
19144
+ outputField: "active",
19145
+ member: {
19146
+ cap: "cover",
19147
+ fieldPath: "state"
19148
+ },
19149
+ activeWhen: [
19150
+ "open",
19151
+ "opening",
19152
+ "stopped"
19153
+ ],
19154
+ commandCaps: ["cover"]
19155
+ }),
19156
+ actuator({
19157
+ id: "valve",
19158
+ summary: "Open when at least one valve is open or part-open, or only when all of them are. Position is offered only when every valve has it. A valve that reports nothing is left out, never read as closed.",
19159
+ deviceType: DeviceType.Valve,
19160
+ outputCap: "group-active",
19161
+ outputField: "active",
19162
+ member: {
19163
+ cap: "valve",
19164
+ fieldPath: "state"
19165
+ },
19166
+ activeWhen: [
19167
+ "open",
19168
+ "opening",
19169
+ "stopped"
19170
+ ],
19171
+ commandCaps: ["valve"]
19172
+ }),
19173
+ actuator({
19174
+ id: "lock",
19175
+ summary: "Locked when at least one lock is locked, or only when all of them are. A jammed or moving lock is not locked. Locking is one tap; unlocking the group needs a confirmation every time, and nothing is sent without it.",
19176
+ deviceType: DeviceType.Lock,
19177
+ outputCap: "group-active",
19178
+ outputField: "active",
19179
+ member: {
19180
+ cap: "lock-control",
19181
+ fieldPath: "state"
19182
+ },
19183
+ activeWhen: ["locked"],
19184
+ commandCaps: ["lock-control"]
19185
+ }),
19186
+ actuator({
19187
+ id: "media-player",
19188
+ summary: "Playing when at least one player is playing, or only when all of them are. Volume is offered only when every player has it. A player that reports nothing is left out, never read as stopped.",
19189
+ deviceType: DeviceType.MediaPlayer,
19190
+ outputCap: "group-active",
19191
+ outputField: "active",
19192
+ member: {
19193
+ cap: "media-player",
19194
+ fieldPath: "state"
19195
+ },
19196
+ activeWhen: ["playing", "buffering"],
19197
+ commandCaps: ["media-player"]
19198
+ }),
19199
+ actuator({
19200
+ id: "climate",
19201
+ summary: "Running when at least one thermostat or air conditioner is not off, or only when all of them are. Target temperature and fan are offered only when every device has them. A device that reports nothing is left out, never read as off.",
19202
+ deviceType: DeviceType.Thermostat,
19203
+ memberDeviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
19204
+ outputCap: "group-active",
19205
+ outputField: "active",
19206
+ member: {
19207
+ cap: "climate-control",
19208
+ fieldPath: "mode"
19209
+ },
19210
+ activeWhen: [
19211
+ "heat",
19212
+ "cool",
19213
+ "auto",
19214
+ "heat_cool",
19215
+ "fan_only",
19216
+ "dry"
19217
+ ],
19218
+ commandCaps: ["climate-control"]
18468
19219
  })
18469
19220
  ];
18470
19221
  function actuator(input) {
18471
19222
  return {
18472
- ...input,
19223
+ id: input.id,
19224
+ summary: input.summary,
19225
+ deviceType: input.deviceType,
19226
+ outputCap: input.outputCap,
19227
+ outputField: input.outputField,
19228
+ commandCaps: input.commandCaps,
18473
19229
  family: "actuator",
18474
19230
  role: void 0,
18475
19231
  memberDeviceType: input.deviceType,
18476
- outputField: "on",
19232
+ memberDeviceTypes: input.memberDeviceTypes ?? [input.deviceType],
19233
+ activeWhen: input.activeWhen,
18477
19234
  quantity: "none",
18478
19235
  baseUnit: void 0,
18479
19236
  aggregates: BINARY_AGGREGATES,
18480
19237
  defaultAggregate: "any",
18481
19238
  empty: "offline",
18482
- accept: [{
18483
- cap: "switch",
18484
- fieldPath: "on"
18485
- }],
19239
+ accept: [input.member],
18486
19240
  companions: [CHANGED]
18487
19241
  };
18488
19242
  }
18489
19243
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18490
19244
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
19245
+ DeviceType.Camera;
18491
19246
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18492
19247
  /**
18493
19248
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -18802,7 +19557,18 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
18802
19557
  kind: "mutation",
18803
19558
  auth: "admin",
18804
19559
  access: "view"
18805
- });
19560
+ }), method(object({
19561
+ blockId: string().min(1),
19562
+ trace: AutomationRunTraceSchema.optional(),
19563
+ runStartedAt: number().optional(),
19564
+ keepTraces: number().int().min(1).max(100).optional()
19565
+ }), _void(), {
19566
+ kind: "mutation",
19567
+ auth: "admin"
19568
+ }), method(object({ blockId: string().min(1) }), AutomationStateDocSchema, { auth: "admin" }), method(object({ blockId: string().min(1) }), object({
19569
+ traces: array(AutomationRunTraceSchema),
19570
+ lastError: string().nullable()
19571
+ }), { auth: "admin" });
18806
19572
  /**
18807
19573
  * `custom-model-registry` — collection cap exposing operator-registered
18808
19574
  * custom detection models. Each provider (today: `addon-model-studio`)
@@ -43881,7 +44647,7 @@ var NetworkAccessStatusSchema = object({
43881
44647
  });
43882
44648
  /**
43883
44649
  * Optional, richer endpoint shape returned by providers that expose
43884
- * MORE than one ingress concurrently (Tailscale Ingress with mixed
44650
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
43885
44651
  * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
43886
44652
  * the originating provider config (mode + sourcePort) so the
43887
44653
  * orchestrator UI can label rows distinctly. Providers that expose only
@@ -44274,51 +45040,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
44274
45040
  kind: "mutation",
44275
45041
  auth: "admin"
44276
45042
  });
44277
- new Set([
44278
- {
44279
- id: "person",
44280
- name: "Person"
44281
- },
44282
- {
44283
- id: "vehicle",
44284
- name: "Vehicle"
44285
- },
44286
- {
44287
- id: "animal",
44288
- name: "Animal"
44289
- },
44290
- {
44291
- id: "package",
44292
- name: "Package"
44293
- }
44294
- ].map((l) => l.id));
44295
- var COCO_TO_MACRO = {
44296
- mapping: {
44297
- person: "person",
44298
- bicycle: "vehicle",
44299
- car: "vehicle",
44300
- motorcycle: "vehicle",
44301
- airplane: "vehicle",
44302
- bus: "vehicle",
44303
- train: "vehicle",
44304
- truck: "vehicle",
44305
- boat: "vehicle",
44306
- bird: "animal",
44307
- cat: "animal",
44308
- dog: "animal",
44309
- horse: "animal",
44310
- sheep: "animal",
44311
- cow: "animal",
44312
- elephant: "animal",
44313
- bear: "animal",
44314
- zebra: "animal",
44315
- giraffe: "animal",
44316
- suitcase: "package",
44317
- backpack: "package",
44318
- handbag: "package"
44319
- },
44320
- preserveOriginal: false
44321
- };
44322
45043
  var AUDIO_MACRO_LABELS = [
44323
45044
  {
44324
45045
  id: "speech",
@@ -46568,7 +47289,20 @@ var NcRuleInputSchema = object({
46568
47289
  * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
46569
47290
  * path does not run Zod defaults.
46570
47291
  */
46571
- groupingSec: number().int().min(5).max(60).optional()
47292
+ groupingSec: number().int().min(5).max(60).optional(),
47293
+ /**
47294
+ * ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
47295
+ * carries "Correct" / "Wrong" buttons (and, where the channel has room, a
47296
+ * "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
47297
+ * same mark the Library writes, and records whose notification it was and
47298
+ * when. A channel with no buttons gets the same two choices as links.
47299
+ *
47300
+ * `.optional()`, never `.default()` (a Zod default does not run on the addon
47301
+ * cap path): absent and `false` both mean off, which is what every rule
47302
+ * authored before this field meant. An editor turns it off by writing
47303
+ * `false`, so it needs no `clear` key.
47304
+ */
47305
+ askVerdict: boolean().optional()
46572
47306
  });
46573
47307
  /**
46574
47308
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -56872,9 +57606,9 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56872
57606
  */
56873
57607
  var AirQualitySensorStatusSchema = object({
56874
57608
  /** Carbon dioxide concentration in ppm. */
56875
- co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
57609
+ co2: number().min(0).optional().meta(readingMeta("concentration-volume")),
56876
57610
  /** Total volatile organic compounds in ppb. */
56877
- vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
57611
+ voc: number().min(0).optional().meta(readingMeta("concentration-ppb")),
56878
57612
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
56879
57613
  pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
56880
57614
  /** Particulate matter ≤ 10 μm in µg/m³. */
@@ -56907,12 +57641,12 @@ var airQualitySensorCapability = {
56907
57641
  * History (D850): recorded series.
56908
57642
  */
56909
57643
  history: { fields: {
56910
- co2Ppm: {
57644
+ co2: {
56911
57645
  kind: "gauge",
56912
57646
  unit: "ppm",
56913
57647
  deadband: 5
56914
57648
  },
56915
- vocPpb: {
57649
+ voc: {
56916
57650
  kind: "gauge",
56917
57651
  unit: "ppb",
56918
57652
  deadband: 5
@@ -56943,7 +57677,7 @@ var airQualitySensorCapability = {
56943
57677
  */
56944
57678
  var AmbientLightSensorStatusSchema = object({
56945
57679
  /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
56946
- lux: number().min(0).nullable().meta(readingMeta("illuminance", {
57680
+ illuminance: number().min(0).nullable().meta(readingMeta("illuminance", {
56947
57681
  precision: 0,
56948
57682
  label: "Illuminance"
56949
57683
  })),
@@ -56972,7 +57706,7 @@ var ambientLightSensorCapability = {
56972
57706
  /**
56973
57707
  * History (D850): recorded series. Wide range, so a RELATIVE deadband (10 %).
56974
57708
  */
56975
- history: { fields: { lux: {
57709
+ history: { fields: { illuminance: {
56976
57710
  kind: "gauge",
56977
57711
  unit: "lx",
56978
57712
  deadbandPct: 10,
@@ -57111,6 +57845,84 @@ var audioMetricsCapability = {
57111
57845
  */
57112
57846
  history: "none"
57113
57847
  };
57848
+ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
57849
+ actionId: string().min(1),
57850
+ variables: record(string(), ActionVariableValueSchema).optional()
57851
+ }), _void(), {
57852
+ kind: "mutation",
57853
+ auth: "admin"
57854
+ }), method(object({ actionId: string().min(1) }), _void(), {
57855
+ kind: "mutation",
57856
+ auth: "admin"
57857
+ });
57858
+ /**
57859
+ * actions — the ONE surface for runnable actions, for the UI and for
57860
+ * automations. Provided by the core-blocks builtin on the hub.
57861
+ *
57862
+ * An action is a LIST ENTRY, not a device (D883): either internal (cap and wait
57863
+ * steps, kept in the hub's `actions` record kind) or declared by an addon over
57864
+ * `action-source`. `list` asks every `action-source` provider LIVE and overlays
57865
+ * the run status from the hub's `runs` ledger; there is no catalog here to
57866
+ * drift (D3).
57867
+ *
57868
+ * **Permissions follow the camera, as linked devices do.** An action is linked
57869
+ * to cameras by the operator (`setLinks`, D356: nothing links itself). A
57870
+ * non-admin may `get` / `run` an action only through a camera they may see that
57871
+ * links it: they name the camera (`deviceId`, a top-level field so the scope
57872
+ * gate sees it) and the provider refuses an action that camera does not link.
57873
+ * An admin, and an in-process caller (an automation block), may run any action.
57874
+ * Every refusal is typed (`[not-linked] …`) and logged.
57875
+ *
57876
+ * A second `run` of an action that is already running is REFUSED — traced and
57877
+ * logged — never queued.
57878
+ */
57879
+ var ActionIdInput = object({ actionId: string().min(1) });
57880
+ var ActionLinksSchema = object({
57881
+ deviceId: number().int().nonnegative(),
57882
+ actionIds: array(string().min(1)).max(100)
57883
+ });
57884
+ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
57885
+ actionId: string().min(1),
57886
+ deviceId: number().int().nonnegative().optional()
57887
+ }), object({ action: ActionEntrySchema.nullable() }), {
57888
+ role: "viewer",
57889
+ auth: "protected",
57890
+ kind: "query",
57891
+ caller: "required"
57892
+ }), method(object({
57893
+ actionId: string().min(1),
57894
+ variables: record(string(), ActionVariableValueSchema).optional(),
57895
+ wait: boolean().optional(),
57896
+ origin: ActionRunOriginSchema.optional(),
57897
+ deviceId: number().int().nonnegative().optional()
57898
+ }), ActionRunResultSchema, {
57899
+ role: "viewer",
57900
+ auth: "protected",
57901
+ kind: "mutation",
57902
+ caller: "required"
57903
+ }), method(ActionIdInput, _void(), {
57904
+ kind: "mutation",
57905
+ auth: "admin"
57906
+ }), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({ action: ActionEntrySchema }), {
57907
+ kind: "mutation",
57908
+ auth: "admin"
57909
+ }), method(object({
57910
+ actionId: string().min(1),
57911
+ action: ActionInputSchema.partial()
57912
+ }), object({ action: ActionEntrySchema }), {
57913
+ kind: "mutation",
57914
+ auth: "admin"
57915
+ }), method(ActionIdInput, _void(), {
57916
+ kind: "mutation",
57917
+ auth: "admin"
57918
+ }), method(object({ deviceId: number().int().nonnegative() }), array(ActionEntrySchema), {
57919
+ role: "viewer",
57920
+ auth: "protected",
57921
+ kind: "query"
57922
+ }), method(object({ deviceId: number().int().nonnegative() }), ActionLinksSchema, { auth: "admin" }), method(ActionLinksSchema, ActionLinksSchema, {
57923
+ kind: "mutation",
57924
+ auth: "admin"
57925
+ });
57114
57926
  /**
57115
57927
  * Automation-control cap. Models HA `automation.*` entities on
57116
57928
  * `DeviceType.Automation`. An automation is a trigger+condition+
@@ -57207,7 +58019,7 @@ var BatteryStatusSchema = object({
57207
58019
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
57208
58020
  * declines to band a non-finite reading, which is the correct shape.
57209
58021
  */
57210
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
58022
+ level: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
57211
58023
  /**
57212
58024
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
57213
58025
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -57217,7 +58029,7 @@ var BatteryStatusSchema = object({
57217
58029
  * `'unknown'` is the absence of an answer: the provider has not read the
57218
58030
  * power fields yet, or the firmware reported neither. Before it existed,
57219
58031
  * two unreadable fields produced `'none'`, and a camera nobody had reached
57220
- * was drawn "on battery alone" — the same D315 defect `percentage` closed
58032
+ * was drawn "on battery alone" — the same D315 defect `level` closed
57221
58033
  * by going nullable. Consumers SKIP it (no charger state published, no
57222
58034
  * glyph, no HomeKit ChargingState) rather than coerce it either way.
57223
58035
  */
@@ -57241,7 +58053,7 @@ var BatteryStatusSchema = object({
57241
58053
  * that only report charge. A second reading on this cap, not a second
57242
58054
  * `numeric-sensor` on the same device.
57243
58055
  */
57244
- volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
58056
+ voltage: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
57245
58057
  /**
57246
58058
  * Ms epoch of the last time the device PROVED it was reachable — a
57247
58059
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -57275,7 +58087,7 @@ var BatteryStatusSchema = object({
57275
58087
  /**
57276
58088
  * True when the source is a BINARY low-battery indicator (HA
57277
58089
  * `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
57278
- * charge level — `percentage` is then a coarse stand-in (100 = normal,
58090
+ * charge level — `level` is then a coarse stand-in (100 = normal,
57279
58091
  * sub-threshold = low). UI MUST render "Normal"/"Low" instead of a
57280
58092
  * misleading exact percentage. Absent/false → genuine 0–100 % reading.
57281
58093
  */
@@ -57288,21 +58100,22 @@ var BATTERY_DEVICE_TYPES = [
57288
58100
  DeviceType.Switch,
57289
58101
  DeviceType.PetFeeder,
57290
58102
  DeviceType.Container,
57291
- DeviceType.Thermostat
58103
+ DeviceType.Thermostat,
58104
+ DeviceType.EventEmitter
57292
58105
  ];
57293
58106
  /** Generic Customize target for the percentage reading (D936). */
57294
58107
  var batteryCustomize = {
57295
58108
  version: 1,
57296
58109
  targets: [{
57297
- id: "percentage",
58110
+ id: "level",
57298
58111
  cardinality: "scalar",
57299
58112
  vocabulary: {
57300
58113
  cap: "battery",
57301
- stateField: "percentage"
58114
+ stateField: "level"
57302
58115
  },
57303
58116
  deviceTypes: BATTERY_DEVICE_TYPES,
57304
58117
  apply: "add",
57305
- path: "percentage",
58118
+ path: "level",
57306
58119
  sources: [{
57307
58120
  kind: "field",
57308
58121
  valueKinds: ["number", "string"],
@@ -57396,7 +58209,7 @@ onStatusChanged: { data: object({
57396
58209
  schema: BatteryStatusSchema,
57397
58210
  kind: "push",
57398
58211
  empty: {
57399
- percentage: null,
58212
+ level: null,
57400
58213
  charging: "unknown",
57401
58214
  sleeping: false,
57402
58215
  lastUpdated: 0
@@ -57423,11 +58236,11 @@ onStatusChanged: { data: object({
57423
58236
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
57424
58237
  */
57425
58238
  history: { fields: {
57426
- percentage: {
58239
+ level: {
57427
58240
  kind: "gauge",
57428
58241
  unit: "%"
57429
58242
  },
57430
- volts: {
58243
+ voltage: {
57431
58244
  kind: "gauge",
57432
58245
  unit: "V"
57433
58246
  }
@@ -57487,14 +58300,14 @@ var binaryCapability = {
57487
58300
  * controls to this single-method surface.
57488
58301
  *
57489
58302
  * The cap is intentionally minimal: a single `setBrightness({deviceId,
57490
- * percentage})` mutation plus the auto-injected `getStatus`. Drivers
58303
+ * level})` mutation plus the auto-injected `getStatus`. Drivers
57491
58304
  * that expose richer controls (color temperature, scenes, schedules)
57492
58305
  * should surface those via the device's `getSettingsUISchema()`
57493
58306
  * instead of bloating this cap.
57494
58307
  */
57495
58308
  var BrightnessStatusSchema = object({
57496
58309
  /** Current level as 0..100 inclusive. Firmware-reported. */
57497
- percentage: number().min(0).max(100).meta(readingMeta("percentage")),
58310
+ level: number().min(0).max(100).meta(readingMeta("percentage")),
57498
58311
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
57499
58312
  lastChangedAt: number()
57500
58313
  });
@@ -57506,7 +58319,7 @@ var brightnessCapability = {
57506
58319
  deviceTypes: [DeviceType.Light],
57507
58320
  methods: { setBrightness: method(object({
57508
58321
  deviceId: number().int().nonnegative(),
57509
- percentage: number().min(0).max(100)
58322
+ level: number().min(0).max(100)
57510
58323
  }), _void(), {
57511
58324
  kind: "mutation",
57512
58325
  auth: "protected"
@@ -57519,7 +58332,7 @@ var brightnessCapability = {
57519
58332
  */
57520
58333
  onBrightnessChanged: { data: object({
57521
58334
  deviceId: number(),
57522
- percentage: number().min(0).max(100),
58335
+ level: number().min(0).max(100),
57523
58336
  lastChangedAt: number()
57524
58337
  }) } },
57525
58338
  status: {
@@ -57542,7 +58355,7 @@ onBrightnessChanged: { data: object({
57542
58355
  /**
57543
58356
  * History (D850): recorded series. Actuator tier: level keeps every change.
57544
58357
  */
57545
- history: { fields: { percentage: {
58358
+ history: { fields: { level: {
57546
58359
  kind: "gauge",
57547
58360
  unit: "%",
57548
58361
  minIntervalMs: 0
@@ -58139,11 +58952,11 @@ var ColorInputSchema = discriminatedUnion("mode", [
58139
58952
  }),
58140
58953
  object({
58141
58954
  mode: literal("mired"),
58142
- mireds: number().int().min(50).max(1e3)
58955
+ colorTemperature: number().int().min(50).max(1e3)
58143
58956
  })
58144
58957
  ]);
58145
58958
  var ColorStatusSchema = object({
58146
- /** Active color mode — which of `rgb` / `hsv` / `mireds` reflects
58959
+ /** Active color mode — which of `rgb` / `hsv` / `colorTemperature` reflects
58147
58960
  * the bulb's current state. */
58148
58961
  mode: _enum([
58149
58962
  "rgb",
@@ -58155,7 +58968,7 @@ var ColorStatusSchema = object({
58155
58968
  /** Populated when `mode === 'hsv'`. */
58156
58969
  hsv: HsvTripletSchema.optional(),
58157
58970
  /** Populated when `mode === 'mired'`. */
58158
- mireds: number().int().optional().meta(readingMeta("colour-temperature")),
58971
+ colorTemperature: number().int().optional().meta(readingMeta("colour-temperature")),
58159
58972
  /** Ms epoch of the last operator-driven change. */
58160
58973
  lastChangedAt: number()
58161
58974
  });
@@ -58187,7 +59000,7 @@ onColorChanged: { data: object({
58187
59000
  ]),
58188
59001
  rgb: RgbTripletSchema.optional(),
58189
59002
  hsv: HsvTripletSchema.optional(),
58190
- mireds: number().int().optional(),
59003
+ colorTemperature: number().int().optional(),
58191
59004
  lastChangedAt: number()
58192
59005
  }) } },
58193
59006
  status: {
@@ -59064,11 +59877,11 @@ var DishwasherStatusSchema = object({
59064
59877
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
59065
59878
  availablePrograms: array(string()),
59066
59879
  /** Seconds remaining on the active cycle, or null when not running / not known. */
59067
- remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
59880
+ remaining: number().int().nonnegative().nullable().meta(readingMeta("duration")),
59068
59881
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
59069
- delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
59882
+ delayedStart: number().int().nonnegative().nullable().meta(readingMeta("duration")),
59070
59883
  /** Cycle completion 0..100, or null when not applicable. */
59071
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59884
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59072
59885
  /** True when the door is reported open; null when the door state is not known. */
59073
59886
  doorOpen: boolean().nullable(),
59074
59887
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -59287,14 +60100,14 @@ var dishwasherCapability = {
59287
60100
  runtimeState: DishwasherStatusSchema,
59288
60101
  durability: "session",
59289
60102
  history: { fields: {
59290
- progressPercent: {
60103
+ progress: {
59291
60104
  kind: "gauge",
59292
60105
  unit: "%",
59293
60106
  deadband: 2
59294
60107
  },
59295
60108
  state: { kind: "enum" },
59296
60109
  operationState: { kind: "enum" },
59297
- remainingSeconds: {
60110
+ remaining: {
59298
60111
  kind: "gauge",
59299
60112
  unit: "s",
59300
60113
  minIntervalMs: 6e4
@@ -59885,7 +60698,7 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
59885
60698
  var FanControlStatusSchema = object({
59886
60699
  /** Active speed as 0..100 inclusive. Null when the device has no
59887
60700
  * speed surface (single-speed fan). */
59888
- percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
60701
+ speed: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
59889
60702
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
59890
60703
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
59891
60704
  percentageStep: number().positive().optional().meta(readingMeta("percentage")),
@@ -59912,7 +60725,7 @@ var fanControlCapability = {
59912
60725
  methods: {
59913
60726
  setPercentage: method(object({
59914
60727
  deviceId: number().int().nonnegative(),
59915
- percentage: number().min(0).max(100)
60728
+ speed: number().min(0).max(100)
59916
60729
  }), _void(), {
59917
60730
  kind: "mutation",
59918
60731
  auth: "protected"
@@ -59945,7 +60758,7 @@ var fanControlCapability = {
59945
60758
  },
59946
60759
  /**
59947
60760
  * Runtime-state slice — mirrored by the kernel. UI fan speed
59948
- * sliders read `percentage` for live updates.
60761
+ * sliders read `speed` for live updates.
59949
60762
  */
59950
60763
  runtimeState: FanControlStatusSchema,
59951
60764
  /**
@@ -59959,7 +60772,7 @@ var fanControlCapability = {
59959
60772
  * History (D850): recorded series. Actuator tier: speed keeps every change.
59960
60773
  */
59961
60774
  history: { fields: {
59962
- percentage: {
60775
+ speed: {
59963
60776
  kind: "gauge",
59964
60777
  unit: "%",
59965
60778
  minIntervalMs: 0
@@ -60148,7 +60961,17 @@ var groupCommandsCapability = {
60148
60961
  scope: "device",
60149
60962
  deviceNative: true,
60150
60963
  mode: "singleton",
60151
- deviceTypes: [DeviceType.Switch, DeviceType.Light],
60964
+ deviceTypes: [
60965
+ DeviceType.Switch,
60966
+ DeviceType.Light,
60967
+ DeviceType.Fan,
60968
+ DeviceType.Cover,
60969
+ DeviceType.Valve,
60970
+ DeviceType.Lock,
60971
+ DeviceType.MediaPlayer,
60972
+ DeviceType.Thermostat,
60973
+ DeviceType.Climate
60974
+ ],
60152
60975
  methods: {},
60153
60976
  status: {
60154
60977
  schema: GroupCommandsSchema,
@@ -60166,6 +60989,49 @@ var groupCommandsCapability = {
60166
60989
  history: "none"
60167
60990
  };
60168
60991
  /**
60992
+ * Whether a helper actuator group counts as active (D944): at least one
60993
+ * included member is, or every included member is. What "active" means is the
60994
+ * type's: a cover is open, a lock is locked, a media player is playing, a
60995
+ * climate device is not off. A group of lights, switches and fans reports
60996
+ * `switch.on` instead; this cap is for the types whose own state is not a
60997
+ * yes/no. Unknown members are left out (`group-status` counts them) and an
60998
+ * empty included set takes the device offline, never `active: false`.
60999
+ */
61000
+ var GroupActiveSchema = object({
61001
+ active: boolean().meta({ label: "Active" }),
61002
+ /** Ms epoch when the slice was last updated. */
61003
+ lastChangedAt: number()
61004
+ });
61005
+ var groupActiveCapability = {
61006
+ name: "group-active",
61007
+ scope: "device",
61008
+ deviceNative: true,
61009
+ mode: "singleton",
61010
+ deviceTypes: [
61011
+ DeviceType.Cover,
61012
+ DeviceType.Valve,
61013
+ DeviceType.Lock,
61014
+ DeviceType.MediaPlayer,
61015
+ DeviceType.Climate,
61016
+ DeviceType.Thermostat
61017
+ ],
61018
+ methods: {},
61019
+ status: {
61020
+ schema: GroupActiveSchema,
61021
+ kind: "push"
61022
+ },
61023
+ runtimeState: GroupActiveSchema,
61024
+ /**
61025
+ * Runtime-state durability: **session** — recomputed from the members on the
61026
+ * next source event; a restored value would outlive the members it named.
61027
+ *
61028
+ * See `RuntimeStateDurability`. Enforced by
61029
+ * `scripts/check-runtime-state-durability.ts`.
61030
+ */
61031
+ durability: "session",
61032
+ history: "none"
61033
+ };
61034
+ /**
60169
61035
  * How many members of a helper group answered (D944). Counts are integers,
60170
61036
  * not measurements: zero included is a real count, never a stand-in reading.
60171
61037
  * The reading itself stays on the output capability. History is none — the
@@ -60196,7 +61062,14 @@ var groupStatusCapability = {
60196
61062
  deviceTypes: [
60197
61063
  DeviceType.Sensor,
60198
61064
  DeviceType.Switch,
60199
- DeviceType.Light
61065
+ DeviceType.Light,
61066
+ DeviceType.Fan,
61067
+ DeviceType.Cover,
61068
+ DeviceType.Valve,
61069
+ DeviceType.Lock,
61070
+ DeviceType.MediaPlayer,
61071
+ DeviceType.Thermostat,
61072
+ DeviceType.Climate
60200
61073
  ],
60201
61074
  methods: {},
60202
61075
  status: {
@@ -60344,7 +61217,7 @@ var humidifierCapability = {
60344
61217
  */
60345
61218
  var HumiditySensorStatusSchema = object({
60346
61219
  /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
60347
- percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
61220
+ humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
60348
61221
  precision: 0,
60349
61222
  label: "Humidity"
60350
61223
  })),
@@ -60373,7 +61246,7 @@ var humiditySensorCapability = {
60373
61246
  /**
60374
61247
  * History (D850): recorded series.
60375
61248
  */
60376
- history: { fields: { percent: {
61249
+ history: { fields: { humidity: {
60377
61250
  kind: "gauge",
60378
61251
  unit: "%",
60379
61252
  deadband: .5
@@ -61013,7 +61886,7 @@ var LawnMowerControlStatusSchema = object({
61013
61886
  batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61014
61887
  /** 0..100 mowing-completion percentage of the current task, or null when no
61015
61888
  * task is active / progress is unavailable. */
61016
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61889
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
61017
61890
  /** Current device/event code (dynamic — mostly status, sometimes an error),
61018
61891
  * or null when unknown. */
61019
61892
  currentCode: number().nullable().meta(readingMeta("none")),
@@ -61469,7 +62342,14 @@ var lockControlCapability = {
61469
62342
  }),
61470
62343
  unlock: method(object({
61471
62344
  deviceId: number().int().nonnegative(),
61472
- code: string().min(1).optional()
62345
+ code: string().min(1).optional(),
62346
+ /**
62347
+ * Honoured by a helper lock GROUP only (D944): unlocking several doors
62348
+ * with one call is refused unless the caller says it was confirmed. A
62349
+ * single lock ignores it. The refusal lives in the callee, so no
62350
+ * automation or API caller reaches the doors by skipping a dialog.
62351
+ */
62352
+ confirm: boolean().optional()
61473
62353
  }), _void(), {
61474
62354
  kind: "mutation",
61475
62355
  auth: "protected"
@@ -61506,6 +62386,44 @@ var lockControlCapability = {
61506
62386
  volatileStateFields: ["lastChangedAt"]
61507
62387
  };
61508
62388
  /**
62389
+ * A monotonic total between resets (D944). Energy uses `power-meter.energy`
62390
+ * so dashboards keep one series; every other instantaneous reading lands here.
62391
+ * `value` is the time integral in the source quantity's accumulated base
62392
+ * (base-unit seconds, or kWh on the power meter). `null` is a gap, never zero.
62393
+ */
62394
+ var MeterStatusSchema = object({
62395
+ value: number().nullable().meta(readingMeta("device", { label: "Total" })),
62396
+ lastFetchedAt: number()
62397
+ });
62398
+ var meterCapability = {
62399
+ name: "meter",
62400
+ scope: "device",
62401
+ deviceNative: true,
62402
+ mode: "singleton",
62403
+ deviceTypes: [DeviceType.Sensor],
62404
+ methods: {},
62405
+ status: {
62406
+ schema: MeterStatusSchema,
62407
+ kind: "push"
62408
+ },
62409
+ runtimeState: MeterStatusSchema,
62410
+ /**
62411
+ * Runtime-state durability: **restored** — the total is a cache of the
62412
+ * history integral. A restart shows it only until the history pass
62413
+ * recomputes the open period; it is not an in-memory buffer.
62414
+ *
62415
+ * See `RuntimeStateDurability`. Enforced by
62416
+ * `scripts/check-runtime-state-durability.ts`.
62417
+ */
62418
+ durability: "restored",
62419
+ history: { fields: { value: {
62420
+ kind: "counter",
62421
+ deadband: .001,
62422
+ minIntervalMs: 6e4
62423
+ } } },
62424
+ volatileStateFields: ["lastFetchedAt"]
62425
+ };
62426
+ /**
61509
62427
  * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
61510
62428
  * Apple TV, Spotify, Roku, generic OTT receivers, …) on
61511
62429
  * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
@@ -61562,10 +62480,10 @@ var MediaPlayerStatusSchema = object({
61562
62480
  currentMedia: MediaInfoSchema.nullable(),
61563
62481
  /** Current playback position in ms. Null when not seekable or
61564
62482
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
61565
- positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
62483
+ position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
61566
62484
  /** Total duration of the current media in ms. Null when unknown
61567
62485
  * (live stream) or nothing is playing. */
61568
- durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
62486
+ duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
61569
62487
  /** Shuffle toggle. Null when no shuffle surface.
61570
62488
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
61571
62489
  shuffle: boolean().nullable(),
@@ -61604,7 +62522,7 @@ var mediaPlayerCapability = {
61604
62522
  }),
61605
62523
  seek: method(object({
61606
62524
  deviceId: number().int().nonnegative(),
61607
- positionMs: number().int().nonnegative()
62525
+ position: number().int().nonnegative()
61608
62526
  }), _void(), {
61609
62527
  kind: "mutation",
61610
62528
  auth: "protected"
@@ -61676,7 +62594,7 @@ var mediaPlayerCapability = {
61676
62594
  /**
61677
62595
  * History (D850): volume (a level an operator or an automation sets; the
61678
62596
  * default 10 s floor folds a slider drag into its end value) and mute.
61679
- * `positionMs` is not recorded: it moves continuously while playing.
62597
+ * `position` is not recorded: it moves continuously while playing.
61680
62598
  */
61681
62599
  history: { fields: {
61682
62600
  volumeLevel: {
@@ -62638,9 +63556,9 @@ var NetworkLinkStatusSchema = object({
62638
63556
  * one whose reading has not landed must not be drawn at 0 %. Consumers
62639
63557
  * SKIP a null rather than coerce it.
62640
63558
  */
62641
- signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63559
+ signal: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
62642
63560
  /** Raw received signal strength in dBm, when the firmware reports one. */
62643
- rssiDbm: number().optional().meta(readingMeta("signal-level")),
63561
+ rssi: number().optional().meta(readingMeta("signal-level")),
62644
63562
  /** Network name of a wireless link, when the firmware reports it. */
62645
63563
  ssid: string().optional(),
62646
63564
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
@@ -62676,7 +63594,7 @@ onStatusChanged: { data: object({
62676
63594
  kind: "push",
62677
63595
  empty: {
62678
63596
  type: "unknown",
62679
- signalPercent: null,
63597
+ signal: null,
62680
63598
  lastUpdated: 0
62681
63599
  }
62682
63600
  },
@@ -62699,13 +63617,13 @@ onStatusChanged: { data: object({
62699
63617
  * History (D850): recorded series.
62700
63618
  */
62701
63619
  history: { fields: {
62702
- signalPercent: {
63620
+ signal: {
62703
63621
  kind: "gauge",
62704
63622
  unit: "%",
62705
63623
  deadband: 3,
62706
63624
  minIntervalMs: 6e4
62707
63625
  },
62708
- rssiDbm: {
63626
+ rssi: {
62709
63627
  kind: "gauge",
62710
63628
  unit: "dBm",
62711
63629
  deadband: 2,
@@ -63450,10 +64368,20 @@ var PetFeederDeviceStatusSchema = _enum([
63450
64368
  ]);
63451
64369
  var gramsPortion = number().int().min(4).max(200);
63452
64370
  var PetFeederStatusSchema = object({
63453
- /** Food currently in the bowl (grams). Null when the device has not
63454
- * reported a reading yet. On dual-hopper models this is the combined
63455
- * bowl reading; per-hopper levels live in `food1`/`food2`. */
64371
+ /** Food in the TANK (hopper), as the feeder's own sensor reports it
64372
+ * (`state.food`). A coarse level code, not grams — `0` is empty. The
64373
+ * amount in the bowl is `bowlFood`. On dual-hopper models this is the
64374
+ * combined reading; per-hopper levels live in `food1`/`food2`. Null when
64375
+ * the device has not reported yet. */
63456
64376
  foodLevel: number().nullable().meta(readingMeta("none")),
64377
+ /** Food in the BOWL as the feeder reports it (`state.bowl`), in the
64378
+ * feeder's own units. Null when the model reports none. */
64379
+ bowlFood: number().nullable().meta(readingMeta("none")),
64380
+ /** True while the pet is eating (`state.eating`). */
64381
+ eating: boolean(),
64382
+ /** Whether `markFoodReplenished` does anything on this model. When false
64383
+ * the client must not offer it. */
64384
+ supportsMarkRefilled: boolean(),
63457
64385
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
63458
64386
  * single-hopper models. */
63459
64387
  food1: number().nullable().meta(readingMeta("none")),
@@ -63461,8 +64389,8 @@ var PetFeederStatusSchema = object({
63461
64389
  * single-hopper models. */
63462
64390
  food2: number().nullable().meta(readingMeta("none")),
63463
64391
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
63464
- * (`device_class: problem`, on = low). True when the bowl is empty /
63465
- * below the feeder's low threshold. */
64392
+ * (`device_class: problem`, on = low). True when the tank reports empty,
64393
+ * or, on a dual-hopper model, when EITHER hopper is empty. */
63466
64394
  lowFood: boolean(),
63467
64395
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
63468
64396
  * device has no battery reading. */
@@ -63530,7 +64458,9 @@ var petFeederCapability = {
63530
64458
  kind: "mutation",
63531
64459
  auth: "protected"
63532
64460
  }),
63533
- /** Mark a hopper as refilled (D4H/D4S/D4SH). */
64461
+ /** Tell the PetKit cloud a hopper was refilled so it stops its low-food
64462
+ * reminders (D4H/D4S/D4SH only — see `supportsMarkRefilled`). It does not
64463
+ * change any sensor reading. */
63534
64464
  markFoodReplenished: method(object({ deviceId: number().int().nonnegative() }), _void(), {
63535
64465
  kind: "mutation",
63536
64466
  auth: "protected"
@@ -63603,6 +64533,7 @@ var petFeederCapability = {
63603
64533
  */
63604
64534
  history: { fields: {
63605
64535
  foodLevel: { kind: "gauge" },
64536
+ bowlFood: { kind: "gauge" },
63606
64537
  food1: { kind: "gauge" },
63607
64538
  food2: { kind: "gauge" },
63608
64539
  batteryPower: {
@@ -64012,27 +64943,89 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
64012
64943
  * in real-world devices.
64013
64944
  */
64014
64945
  var PowerMeterStatusSchema = object({
64015
- /** Instantaneous power draw in watts. */
64016
- watts: number().optional().meta(readingMeta("power", { label: "Power" })),
64946
+ /** Instantaneous power draw (watts). */
64947
+ power: number().optional().meta(readingMeta("power", { label: "Power" })),
64017
64948
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
64018
- kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
64019
- /** Voltage in volts. */
64020
- volts: number().optional().meta(readingMeta("voltage")),
64949
+ energy: number().optional().meta(readingMeta("energy", { label: "Energy" })),
64950
+ /** Voltage (volts). */
64951
+ voltage: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
64021
64952
  /** Current in amperes. */
64022
- amps: number().optional().meta(readingMeta("current")),
64953
+ current: number().optional().meta(readingMeta("current", { label: "Current" })),
64023
64954
  /** Apparent power in volt-amperes. */
64024
- va: number().optional().meta(readingMeta("apparent-power")),
64955
+ apparentPower: number().optional().meta(readingMeta("apparent-power")),
64025
64956
  /** Power factor (0..1) if reported. */
64026
64957
  powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
64027
64958
  /** Ms epoch when the slice was last updated. */
64028
64959
  lastFetchedAt: number()
64029
64960
  });
64961
+ /**
64962
+ * Loads that draw metered mains power (D951). The reading is a feature of the
64963
+ * device itself — a plug, a lamp, a fan, an air conditioner, a boiler, a
64964
+ * vacuum dock — not a separate sensor beside it. A `Sensor` stays: a clamp
64965
+ * meter or a standalone energy monitor IS the device. Cover, lock, valve,
64966
+ * camera and the like are left out: their draw is not what anyone meters.
64967
+ */
64968
+ var POWER_METER_DEVICE_TYPES = [
64969
+ DeviceType.Sensor,
64970
+ DeviceType.Switch,
64971
+ DeviceType.Light,
64972
+ DeviceType.Fan,
64973
+ DeviceType.Climate,
64974
+ DeviceType.Thermostat,
64975
+ DeviceType.Humidifier,
64976
+ DeviceType.WaterHeater,
64977
+ DeviceType.MediaPlayer,
64978
+ DeviceType.Washer,
64979
+ DeviceType.Dryer,
64980
+ DeviceType.Dishwasher,
64981
+ DeviceType.Vacuum,
64982
+ DeviceType.LawnMower,
64983
+ DeviceType.PetFeeder
64984
+ ];
64985
+ /** Everything but a bare `Sensor`: a sensor that takes its reading from another sensor adds nothing. */
64986
+ var POWER_METER_CUSTOMIZE_DEVICE_TYPES = POWER_METER_DEVICE_TYPES.filter((type) => type !== DeviceType.Sensor);
64987
+ /** One scalar target per reading; each is sourced from a device of its own (D951). */
64988
+ function powerTarget(id, unit) {
64989
+ return {
64990
+ id,
64991
+ cardinality: "scalar",
64992
+ vocabulary: {
64993
+ cap: "power-meter",
64994
+ stateField: id
64995
+ },
64996
+ deviceTypes: POWER_METER_CUSTOMIZE_DEVICE_TYPES,
64997
+ apply: "add",
64998
+ path: id,
64999
+ sources: [{
65000
+ kind: "numeric",
65001
+ requiredUnit: unit,
65002
+ transforms: [{ kind: "identity" }]
65003
+ }],
65004
+ companions: [{
65005
+ kind: "clock",
65006
+ path: "lastFetchedAt",
65007
+ when: "always"
65008
+ }],
65009
+ sourceReport: { status: "assignment-status" },
65010
+ advancedExpression: true
65011
+ };
65012
+ }
65013
+ /** "Power and energy" extension: power, energy, voltage and current from other devices (D951). */
65014
+ var powerMeterCustomize = {
65015
+ version: 1,
65016
+ targets: [
65017
+ powerTarget("power", "W"),
65018
+ powerTarget("energy", "kWh"),
65019
+ powerTarget("voltage", "V"),
65020
+ powerTarget("current", "A")
65021
+ ]
65022
+ };
64030
65023
  var powerMeterCapability = {
64031
65024
  name: "power-meter",
64032
65025
  scope: "device",
64033
65026
  deviceNative: true,
64034
65027
  mode: "singleton",
64035
- deviceTypes: [DeviceType.Sensor],
65028
+ deviceTypes: POWER_METER_DEVICE_TYPES,
64036
65029
  methods: {},
64037
65030
  status: {
64038
65031
  schema: PowerMeterStatusSchema,
@@ -64040,41 +65033,41 @@ var powerMeterCapability = {
64040
65033
  },
64041
65034
  runtimeState: PowerMeterStatusSchema,
64042
65035
  /**
64043
- * Runtime-state durability: **restored** — as `numeric-sensor`; `kwhTotal` is an accumulator whose restored value is the baseline.
65036
+ * Runtime-state durability: **restored** — as `numeric-sensor`; `energy` is an accumulator whose restored value is the baseline.
64044
65037
  *
64045
65038
  * See `RuntimeStateDurability`. Enforced by
64046
65039
  * `scripts/check-runtime-state-durability.ts`.
64047
65040
  */
64048
65041
  durability: "restored",
64049
65042
  /**
64050
- * History (D850): recorded series. Gauges are floored at 30 s; `kwhTotal` is the one monotonic meter (counter).
65043
+ * History (D850): recorded series. Gauges are floored at 30 s; `energy` is the one monotonic meter (counter).
64051
65044
  */
64052
65045
  history: { fields: {
64053
- watts: {
65046
+ power: {
64054
65047
  kind: "gauge",
64055
65048
  unit: "W",
64056
65049
  deadband: 1,
64057
65050
  minIntervalMs: 3e4
64058
65051
  },
64059
- kwhTotal: {
65052
+ energy: {
64060
65053
  kind: "counter",
64061
65054
  unit: "kWh",
64062
65055
  deadband: .01,
64063
65056
  minIntervalMs: 6e4
64064
65057
  },
64065
- volts: {
65058
+ voltage: {
64066
65059
  kind: "gauge",
64067
65060
  unit: "V",
64068
65061
  deadband: 1,
64069
65062
  minIntervalMs: 3e4
64070
65063
  },
64071
- amps: {
65064
+ current: {
64072
65065
  kind: "gauge",
64073
65066
  unit: "A",
64074
65067
  deadband: .05,
64075
65068
  minIntervalMs: 3e4
64076
65069
  },
64077
- va: {
65070
+ apparentPower: {
64078
65071
  kind: "gauge",
64079
65072
  unit: "VA",
64080
65073
  deadband: 1,
@@ -64089,7 +65082,8 @@ var powerMeterCapability = {
64089
65082
  } },
64090
65083
  /** Clock fields: written, but excluded from the compare that decides
64091
65084
  * whether persisting is worth a SQLite commit. */
64092
- volatileStateFields: ["lastFetchedAt"]
65085
+ volatileStateFields: ["lastFetchedAt"],
65086
+ customize: powerMeterCustomize
64093
65087
  };
64094
65088
  /**
64095
65089
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -64123,9 +65117,6 @@ var PresenceStatusSchema = object({
64123
65117
  location: string().nullable(),
64124
65118
  /** GPS coordinates when available. Null otherwise. */
64125
65119
  gps: GpsLocationSchema.nullable(),
64126
- /** Optional battery level of the tracking device (0..100). Null
64127
- * when not reported. */
64128
- batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64129
65120
  /** Ms epoch when the slice was last updated. */
64130
65121
  lastChangedAt: number()
64131
65122
  });
@@ -64154,14 +65145,7 @@ var presenceCapability = {
64154
65145
  * `scripts/check-runtime-state-durability.ts`.
64155
65146
  */
64156
65147
  durability: "restored",
64157
- /**
64158
- * History (D850): recorded series.
64159
- */
64160
- history: { fields: { batteryPercent: {
64161
- kind: "gauge",
64162
- unit: "%",
64163
- deadband: 1
64164
- } } },
65148
+ history: "none",
64165
65149
  /** Clock fields: written, but excluded from the compare that decides
64166
65150
  * whether persisting is worth a SQLite commit. */
64167
65151
  volatileStateFields: ["lastChangedAt"]
@@ -64176,7 +65160,7 @@ var presenceCapability = {
64176
65160
  */
64177
65161
  var PressureSensorStatusSchema = object({
64178
65162
  /** Current pressure in hPa. `null` when the sample was refused (D393). */
64179
- hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
65163
+ pressure: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
64180
65164
  /** Ms epoch when the slice was last updated. */
64181
65165
  lastFetchedAt: number()
64182
65166
  });
@@ -64202,7 +65186,7 @@ var pressureSensorCapability = {
64202
65186
  /**
64203
65187
  * History (D850): recorded series.
64204
65188
  */
64205
- history: { fields: { hpa: {
65189
+ history: { fields: { pressure: {
64206
65190
  kind: "gauge",
64207
65191
  unit: "hPa",
64208
65192
  deadband: .1
@@ -64770,6 +65754,8 @@ var runningCustomize = {
64770
65754
  stateField: "running"
64771
65755
  },
64772
65756
  deviceTypes: [
65757
+ DeviceType.Washer,
65758
+ DeviceType.Dryer,
64773
65759
  DeviceType.Dishwasher,
64774
65760
  DeviceType.Vacuum,
64775
65761
  DeviceType.LawnMower,
@@ -67259,7 +68245,8 @@ var switchCapability = {
67259
68245
  deviceTypes: [
67260
68246
  DeviceType.Switch,
67261
68247
  DeviceType.Siren,
67262
- DeviceType.Light
68248
+ DeviceType.Light,
68249
+ DeviceType.Fan
67263
68250
  ],
67264
68251
  methods: { setState: method(object({
67265
68252
  deviceId: number(),
@@ -67898,7 +68885,7 @@ var tamperCapability = {
67898
68885
  */
67899
68886
  var TemperatureSensorStatusSchema = object({
67900
68887
  /** Current temperature in Celsius. `null` when the sample was refused (D393). */
67901
- celsius: number().nullable().meta(readingMeta("temperature", {
68888
+ temperature: number().nullable().meta(readingMeta("temperature", {
67902
68889
  precision: 1,
67903
68890
  label: "Temperature"
67904
68891
  })),
@@ -67927,7 +68914,7 @@ var temperatureSensorCapability = {
67927
68914
  /**
67928
68915
  * History (D850): recorded series.
67929
68916
  */
67930
- history: { fields: { celsius: {
68917
+ history: { fields: { temperature: {
67931
68918
  kind: "gauge",
67932
68919
  unit: "°C",
67933
68920
  deadband: .1
@@ -68224,7 +69211,7 @@ var VacuumControlStatusSchema = object({
68224
69211
  /** Speed tokens the hardware accepts — drives the UI selector. */
68225
69212
  availableFanSpeeds: array(string()),
68226
69213
  /** 0..100 cleaning-completion percentage of the current task, or null. */
68227
- progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
69214
+ progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
68228
69215
  /** Current error code (0 / null = no error). */
68229
69216
  errorCode: number().nullable().meta(readingMeta("none")),
68230
69217
  /** Human label for {@link errorCode}, or null when none / undecodable. */
@@ -69472,6 +70459,7 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
69472
70459
  kind: "mutation",
69473
70460
  auth: "admin"
69474
70461
  });
70462
+ DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
69475
70463
  /**
69476
70464
  * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
69477
70465
  * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
@@ -69856,7 +70844,7 @@ function tryConvertUnit(value, from, to) {
69856
70844
  return fromCanonical(toCanonical(value, fromSpec), toSpec);
69857
70845
  }
69858
70846
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
69859
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Camera, DeviceType.Camera, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
70847
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
69860
70848
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
69861
70849
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
69862
70850
  new Set(CAP_NAMES);
@@ -70185,6 +71173,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
70185
71173
  featureProbe: featureProbeCapability,
70186
71174
  flood: floodCapability,
70187
71175
  gas: gasCapability,
71176
+ groupActive: groupActiveCapability,
70188
71177
  groupCommands: groupCommandsCapability,
70189
71178
  groupStatus: groupStatusCapability,
70190
71179
  heatingValve: heatingValveCapability,
@@ -70196,6 +71185,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
70196
71185
  lawnMowerControl: lawnMowerControlCapability,
70197
71186
  lockControl: lockControlCapability,
70198
71187
  mediaPlayer: mediaPlayerCapability,
71188
+ meter: meterCapability,
70199
71189
  motion: motionCapability,
70200
71190
  motionTrigger: motionTriggerCapability,
70201
71191
  motionZones: motionZonesCapability,
@@ -70258,7 +71248,7 @@ var BaseDevice = class {
70258
71248
  * `device-local-state.ts`. Drivers access via:
70259
71249
  *
70260
71250
  * `this.state.battery.sleeping = true` // patches the battery slice
70261
- * `const pct = this.state.battery.percentage` // reads the battery slice
71251
+ * `const pct = this.state.battery.level` // reads the battery slice
70262
71252
  * `this.state.deviceStatus.online = true` // mirrors `markOnline(true)`
70263
71253
  *
70264
71254
  * Adding a new cap with `runtimeState:` automatically extends this
@@ -70298,7 +71288,7 @@ var BaseDevice = class {
70298
71288
  * shape; drivers don't redeclare it, they just write through.
70299
71289
  *
70300
71290
  * Read: `this.runtimeState.getCapState('battery')` →
70301
- * `{percentage, charging, sleeping, lastUpdated}` for any
71291
+ * `{level, charging, sleeping, lastUpdated}` for any
70302
71292
  * provider that registers `batteryCapability`.
70303
71293
  * Write: `this.runtimeState.setCapState('battery', { … })`.
70304
71294
  *
@@ -71455,7 +72445,7 @@ var BaseDeviceProvider = class extends BaseAddon {
71455
72445
  return toDeviceSummary(device, this.addonId);
71456
72446
  }
71457
72447
  };
71458
- DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher;
72448
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
71459
72449
  new Set(Object.values(DeviceType));
71460
72450
  Object.freeze({
71461
72451
  "accessories.getStatus": {
@@ -71472,6 +72462,104 @@ Object.freeze({
71472
72462
  access: "create",
71473
72463
  role: "admin"
71474
72464
  },
72465
+ "actions.create": {
72466
+ capName: "actions",
72467
+ capScope: "system",
72468
+ addonId: null,
72469
+ access: "create",
72470
+ role: "admin"
72471
+ },
72472
+ "actions.delete": {
72473
+ capName: "actions",
72474
+ capScope: "system",
72475
+ addonId: null,
72476
+ access: "delete",
72477
+ role: "admin"
72478
+ },
72479
+ "actions.get": {
72480
+ capName: "actions",
72481
+ capScope: "system",
72482
+ addonId: null,
72483
+ access: "view",
72484
+ role: "viewer"
72485
+ },
72486
+ "actions.getLinks": {
72487
+ capName: "actions",
72488
+ capScope: "system",
72489
+ addonId: null,
72490
+ access: "view",
72491
+ role: "admin"
72492
+ },
72493
+ "actions.getTraces": {
72494
+ capName: "actions",
72495
+ capScope: "system",
72496
+ addonId: null,
72497
+ access: "view",
72498
+ role: "admin"
72499
+ },
72500
+ "actions.list": {
72501
+ capName: "actions",
72502
+ capScope: "system",
72503
+ addonId: null,
72504
+ access: "view",
72505
+ role: "admin"
72506
+ },
72507
+ "actions.listForCamera": {
72508
+ capName: "actions",
72509
+ capScope: "system",
72510
+ addonId: null,
72511
+ access: "view",
72512
+ role: "viewer"
72513
+ },
72514
+ "actions.run": {
72515
+ capName: "actions",
72516
+ capScope: "system",
72517
+ addonId: null,
72518
+ access: "create",
72519
+ role: "viewer"
72520
+ },
72521
+ "actions.setLinks": {
72522
+ capName: "actions",
72523
+ capScope: "system",
72524
+ addonId: null,
72525
+ access: "create",
72526
+ role: "admin"
72527
+ },
72528
+ "actions.stop": {
72529
+ capName: "actions",
72530
+ capScope: "system",
72531
+ addonId: null,
72532
+ access: "create",
72533
+ role: "admin"
72534
+ },
72535
+ "actions.update": {
72536
+ capName: "actions",
72537
+ capScope: "system",
72538
+ addonId: null,
72539
+ access: "create",
72540
+ role: "admin"
72541
+ },
72542
+ "actionSource.listActions": {
72543
+ capName: "action-source",
72544
+ capScope: "system",
72545
+ addonId: null,
72546
+ access: "view",
72547
+ role: "admin"
72548
+ },
72549
+ "actionSource.runAction": {
72550
+ capName: "action-source",
72551
+ capScope: "system",
72552
+ addonId: null,
72553
+ access: "create",
72554
+ role: "admin"
72555
+ },
72556
+ "actionSource.stopAction": {
72557
+ capName: "action-source",
72558
+ capScope: "system",
72559
+ addonId: null,
72560
+ access: "create",
72561
+ role: "admin"
72562
+ },
71475
72563
  "addonPages.listPages": {
71476
72564
  capName: "addon-pages",
71477
72565
  capScope: "system",
@@ -72753,6 +73841,13 @@ Object.freeze({
72753
73841
  access: "view",
72754
73842
  role: "admin"
72755
73843
  },
73844
+ "coreBlocks.getAutomationRunState": {
73845
+ capName: "core-blocks",
73846
+ capScope: "system",
73847
+ addonId: null,
73848
+ access: "view",
73849
+ role: "admin"
73850
+ },
72756
73851
  "coreBlocks.getBlockSettings": {
72757
73852
  capName: "core-blocks",
72758
73853
  capScope: "system",
@@ -72774,6 +73869,13 @@ Object.freeze({
72774
73869
  access: "view",
72775
73870
  role: "admin"
72776
73871
  },
73872
+ "coreBlocks.getTraces": {
73873
+ capName: "core-blocks",
73874
+ capScope: "system",
73875
+ addonId: null,
73876
+ access: "view",
73877
+ role: "admin"
73878
+ },
72777
73879
  "coreBlocks.getTypeDefs": {
72778
73880
  capName: "core-blocks",
72779
73881
  capScope: "system",
@@ -72809,6 +73911,13 @@ Object.freeze({
72809
73911
  access: "create",
72810
73912
  role: "admin"
72811
73913
  },
73914
+ "coreBlocks.reportAutomationRun": {
73915
+ capName: "core-blocks",
73916
+ capScope: "system",
73917
+ addonId: null,
73918
+ access: "create",
73919
+ role: "admin"
73920
+ },
72812
73921
  "coreBlocks.restart": {
72813
73922
  capName: "core-blocks",
72814
73923
  capScope: "system",
@@ -74678,6 +75787,13 @@ Object.freeze({
74678
75787
  access: "view",
74679
75788
  role: "viewer"
74680
75789
  },
75790
+ "groupActive.getStatus": {
75791
+ capName: "group-active",
75792
+ capScope: "device",
75793
+ addonId: null,
75794
+ access: "view",
75795
+ role: "viewer"
75796
+ },
74681
75797
  "groupCommands.getStatus": {
74682
75798
  capName: "group-commands",
74683
75799
  capScope: "device",
@@ -75644,6 +76760,13 @@ Object.freeze({
75644
76760
  access: "create",
75645
76761
  role: "admin"
75646
76762
  },
76763
+ "meter.getStatus": {
76764
+ capName: "meter",
76765
+ capScope: "device",
76766
+ addonId: null,
76767
+ access: "view",
76768
+ role: "viewer"
76769
+ },
75647
76770
  "metricsProvider.collectSnapshot": {
75648
76771
  capName: "metrics-provider",
75649
76772
  capScope: "system",
@@ -81368,7 +82491,7 @@ var ACTUATOR_CAP_VOCABULARY = {
81368
82491
  label: t("Full brightness", "Luminosità massima"),
81369
82492
  phrase: t("Set {device} to full brightness", "Porta {device} alla massima luminosità"),
81370
82493
  icon: "sun",
81371
- args: { percentage: 100 }
82494
+ args: { level: 100 }
81372
82495
  },
81373
82496
  {
81374
82497
  id: "brightness.half",
@@ -81376,7 +82499,7 @@ var ACTUATOR_CAP_VOCABULARY = {
81376
82499
  label: t("Half brightness", "Luminosità a metà"),
81377
82500
  phrase: t("Dim {device} to half", "Abbassa {device} a metà luminosità"),
81378
82501
  icon: "sun-dim",
81379
- args: { percentage: 50 }
82502
+ args: { level: 50 }
81380
82503
  },
81381
82504
  {
81382
82505
  id: "brightness.off",
@@ -81384,16 +82507,16 @@ var ACTUATOR_CAP_VOCABULARY = {
81384
82507
  label: t("Brightness off", "Luminosità a zero"),
81385
82508
  phrase: t("Dim {device} to zero", "Abbassa {device} a zero"),
81386
82509
  icon: "lightbulb-off",
81387
- args: { percentage: 0 }
82510
+ args: { level: 0 }
81388
82511
  },
81389
82512
  {
81390
82513
  id: "brightness.set",
81391
82514
  method: "setBrightness",
81392
82515
  label: t("Set brightness", "Imposta la luminosità"),
81393
- phrase: t("Set the brightness of {device} to {percentage}", "Imposta la luminosità di {device} a {percentage}"),
82516
+ phrase: t("Set the brightness of {device} to {level}", "Imposta la luminosità di {device} a {level}"),
81394
82517
  icon: "sun-medium",
81395
82518
  openArgs: [{
81396
- key: "percentage",
82519
+ key: "level",
81397
82520
  label: PERCENT,
81398
82521
  offer: {
81399
82522
  kind: "number",
@@ -81407,13 +82530,13 @@ var ACTUATOR_CAP_VOCABULARY = {
81407
82530
  ],
81408
82531
  state: [{
81409
82532
  kind: "numeric",
81410
- field: "percentage",
82533
+ field: "level",
81411
82534
  quantity: t("the brightness", "la luminosità"),
81412
82535
  label: t("Brightness", "Luminosità"),
81413
82536
  unit: "%",
81414
82537
  icon: "sun"
81415
82538
  }],
81416
- events: { onBrightnessChanged: { notOperatorFacing: "the `percentage` state trigger reads the same change" } }
82539
+ events: { onBrightnessChanged: { notOperatorFacing: "the `level` state trigger reads the same change" } }
81417
82540
  },
81418
82541
  color: {
81419
82542
  scope: "device",
@@ -81427,7 +82550,7 @@ var ACTUATOR_CAP_VOCABULARY = {
81427
82550
  icon: "sun",
81428
82551
  args: { color: {
81429
82552
  mode: "mired",
81430
- mireds: 370
82553
+ colorTemperature: 370
81431
82554
  } }
81432
82555
  },
81433
82556
  {
@@ -81438,7 +82561,7 @@ var ACTUATOR_CAP_VOCABULARY = {
81438
82561
  icon: "snowflake",
81439
82562
  args: { color: {
81440
82563
  mode: "mired",
81441
- mireds: 200
82564
+ colorTemperature: 200
81442
82565
  } }
81443
82566
  },
81444
82567
  {
@@ -82052,7 +83175,7 @@ var ACTUATOR_CAP_VOCABULARY = {
82052
83175
  label: t("Fan off", "Ventilatore spento"),
82053
83176
  phrase: t("Stop {device}", "Ferma {device}"),
82054
83177
  icon: "power-off",
82055
- args: { percentage: 0 }
83178
+ args: { speed: 0 }
82056
83179
  },
82057
83180
  {
82058
83181
  id: "fan.full",
@@ -82060,16 +83183,16 @@ var ACTUATOR_CAP_VOCABULARY = {
82060
83183
  label: t("Fan full", "Ventilatore al massimo"),
82061
83184
  phrase: t("Run {device} at full speed", "Porta {device} alla massima velocità"),
82062
83185
  icon: "fan",
82063
- args: { percentage: 100 }
83186
+ args: { speed: 100 }
82064
83187
  },
82065
83188
  {
82066
83189
  id: "fan.speed",
82067
83190
  method: "setPercentage",
82068
83191
  label: t("Set speed", "Imposta la velocità"),
82069
- phrase: t("Set the speed of {device} to {percentage}", "Imposta la velocità di {device} al {percentage}"),
83192
+ phrase: t("Set the speed of {device} to {speed}", "Imposta la velocità di {device} al {speed}"),
82070
83193
  icon: "fan",
82071
83194
  openArgs: [{
82072
- key: "percentage",
83195
+ key: "speed",
82073
83196
  label: t("Speed", "Velocità"),
82074
83197
  offer: {
82075
83198
  kind: "number",
@@ -82131,7 +83254,7 @@ var ACTUATOR_CAP_VOCABULARY = {
82131
83254
  ],
82132
83255
  state: [{
82133
83256
  kind: "numeric",
82134
- field: "percentage",
83257
+ field: "speed",
82135
83258
  quantity: t("the speed", "la velocità"),
82136
83259
  label: t("Speed", "Velocità"),
82137
83260
  unit: "%",
@@ -82929,7 +84052,7 @@ var APPLIANCE_CAP_VOCABULARY = {
82929
84052
  },
82930
84053
  {
82931
84054
  kind: "numeric",
82932
- field: "progressPercent",
84055
+ field: "progress",
82933
84056
  quantity: t("the cycle progress", "l’avanzamento del lavaggio"),
82934
84057
  label: t("Progress", "Avanzamento"),
82935
84058
  unit: "%",
@@ -83287,10 +84410,10 @@ var APPLIANCE_CAP_VOCABULARY = {
83287
84410
  id: "media.seek",
83288
84411
  method: "seek",
83289
84412
  label: t("Seek", "Vai al punto"),
83290
- phrase: t("Move {device} to {positionMs}", "Porta {device} al punto {positionMs}"),
84413
+ phrase: t("Move {device} to {position}", "Porta {device} al punto {position}"),
83291
84414
  icon: "fast-forward",
83292
84415
  openArgs: [{
83293
- key: "positionMs",
84416
+ key: "position",
83294
84417
  label: t("Position", "Posizione"),
83295
84418
  offer: {
83296
84419
  kind: "number",
@@ -83742,7 +84865,39 @@ var APPLIANCE_CAP_VOCABULARY = {
83742
84865
  phrase: t("When {device} finishes", "Quando {device} finisce"),
83743
84866
  icon: "square"
83744
84867
  }
83745
- }
84868
+ },
84869
+ overrides: { byType: {
84870
+ [DeviceType.Washer]: {
84871
+ state: { running: {
84872
+ whenTrue: t("When the washing machine starts", "Quando la lavatrice inizia"),
84873
+ whenFalse: t("When the washing machine has finished", "Quando la lavatrice ha finito")
84874
+ } },
84875
+ events: {
84876
+ onStarted: { phrase: t("When the washing machine starts", "Quando la lavatrice inizia") },
84877
+ onFinished: { phrase: t("When the washing machine has finished", "Quando la lavatrice ha finito") }
84878
+ }
84879
+ },
84880
+ [DeviceType.Dryer]: {
84881
+ state: { running: {
84882
+ whenTrue: t("When the dryer starts", "Quando l'asciugatrice inizia"),
84883
+ whenFalse: t("When the dryer has finished", "Quando l'asciugatrice ha finito")
84884
+ } },
84885
+ events: {
84886
+ onStarted: { phrase: t("When the dryer starts", "Quando l'asciugatrice inizia") },
84887
+ onFinished: { phrase: t("When the dryer has finished", "Quando l'asciugatrice ha finito") }
84888
+ }
84889
+ },
84890
+ [DeviceType.Dishwasher]: {
84891
+ state: { running: {
84892
+ whenTrue: t("When the dishwasher starts", "Quando la lavastoviglie inizia"),
84893
+ whenFalse: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito")
84894
+ } },
84895
+ events: {
84896
+ onStarted: { phrase: t("When the dishwasher starts", "Quando la lavastoviglie inizia") },
84897
+ onFinished: { phrase: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito") }
84898
+ }
84899
+ }
84900
+ } }
83746
84901
  },
83747
84902
  update: {
83748
84903
  scope: "device",
@@ -84307,7 +85462,7 @@ var SENSOR_CAP_VOCABULARY = {
84307
85462
  state: [
84308
85463
  {
84309
85464
  kind: "numeric",
84310
- field: "co2Ppm",
85465
+ field: "co2",
84311
85466
  quantity: t("the CO₂", "la CO₂"),
84312
85467
  label: t("CO₂", "CO₂"),
84313
85468
  unit: "ppm",
@@ -84315,7 +85470,7 @@ var SENSOR_CAP_VOCABULARY = {
84315
85470
  },
84316
85471
  {
84317
85472
  kind: "numeric",
84318
- field: "vocPpb",
85473
+ field: "voc",
84319
85474
  quantity: t("the VOC level", "il livello di COV"),
84320
85475
  label: t("VOC", "COV"),
84321
85476
  unit: "ppb",
@@ -84351,7 +85506,7 @@ var SENSOR_CAP_VOCABULARY = {
84351
85506
  noun: t("Light level", "Luminosità ambiente"),
84352
85507
  state: [{
84353
85508
  kind: "numeric",
84354
- field: "lux",
85509
+ field: "illuminance",
84355
85510
  quantity: t("the light level", "la luminosità"),
84356
85511
  label: t("Illuminance", "Illuminamento"),
84357
85512
  unit: "lx",
@@ -84514,6 +85669,10 @@ var SENSOR_CAP_VOCABULARY = {
84514
85669
  icon: "flame"
84515
85670
  }]
84516
85671
  },
85672
+ "group-active": {
85673
+ scope: "device",
85674
+ noun: t("Group state", "Stato del gruppo")
85675
+ },
84517
85676
  "group-commands": {
84518
85677
  scope: "device",
84519
85678
  noun: t("Group command", "Comando di gruppo")
@@ -84550,7 +85709,7 @@ var SENSOR_CAP_VOCABULARY = {
84550
85709
  noun: t("Humidity", "Umidità"),
84551
85710
  state: [{
84552
85711
  kind: "numeric",
84553
- field: "percent",
85712
+ field: "humidity",
84554
85713
  quantity: t("the humidity", "l’umidità"),
84555
85714
  label: t("Humidity", "Umidità"),
84556
85715
  unit: "%",
@@ -84570,6 +85729,17 @@ var SENSOR_CAP_VOCABULARY = {
84570
85729
  }],
84571
85730
  events: { onMotionChanged: { notOperatorFacing: "the `detected` state trigger phrases both edges, from the mirror that survives a dropped event (D49)" } }
84572
85731
  },
85732
+ meter: {
85733
+ scope: "device",
85734
+ noun: t("Meter", "Contatore"),
85735
+ state: [{
85736
+ kind: "numeric",
85737
+ field: "value",
85738
+ quantity: t("the total", "il totale"),
85739
+ label: t("Total", "Totale"),
85740
+ icon: "gauge"
85741
+ }]
85742
+ },
84573
85743
  "numeric-sensor": {
84574
85744
  scope: "device",
84575
85745
  noun: t("Sensor", "Sensore"),
@@ -84587,7 +85757,7 @@ var SENSOR_CAP_VOCABULARY = {
84587
85757
  state: [
84588
85758
  {
84589
85759
  kind: "numeric",
84590
- field: "watts",
85760
+ field: "power",
84591
85761
  quantity: t("the power draw", "il consumo"),
84592
85762
  label: t("Power", "Potenza"),
84593
85763
  unit: "W",
@@ -84595,7 +85765,7 @@ var SENSOR_CAP_VOCABULARY = {
84595
85765
  },
84596
85766
  {
84597
85767
  kind: "numeric",
84598
- field: "kwhTotal",
85768
+ field: "energy",
84599
85769
  quantity: t("the energy used", "l’energia consumata"),
84600
85770
  label: t("Energy", "Energia"),
84601
85771
  unit: "kWh",
@@ -84603,7 +85773,7 @@ var SENSOR_CAP_VOCABULARY = {
84603
85773
  },
84604
85774
  {
84605
85775
  kind: "numeric",
84606
- field: "volts",
85776
+ field: "voltage",
84607
85777
  quantity: t("the voltage", "la tensione"),
84608
85778
  label: t("Voltage", "Tensione"),
84609
85779
  unit: "V",
@@ -84611,7 +85781,7 @@ var SENSOR_CAP_VOCABULARY = {
84611
85781
  },
84612
85782
  {
84613
85783
  kind: "numeric",
84614
- field: "amps",
85784
+ field: "current",
84615
85785
  quantity: t("the current", "la corrente"),
84616
85786
  label: t("Current", "Corrente"),
84617
85787
  unit: "A",
@@ -84628,13 +85798,6 @@ var SENSOR_CAP_VOCABULARY = {
84628
85798
  label: t("Where", "Posizione"),
84629
85799
  phrase: t("When {device} is at {value}", "Quando {device} è in {value}"),
84630
85800
  icon: "map-pin"
84631
- }, {
84632
- kind: "numeric",
84633
- field: "batteryPercent",
84634
- quantity: t("the phone battery", "la batteria del telefono"),
84635
- label: t("Battery", "Batteria"),
84636
- unit: "%",
84637
- icon: "battery"
84638
85801
  }]
84639
85802
  },
84640
85803
  "pressure-sensor": {
@@ -84642,7 +85805,7 @@ var SENSOR_CAP_VOCABULARY = {
84642
85805
  noun: t("Pressure", "Pressione"),
84643
85806
  state: [{
84644
85807
  kind: "numeric",
84645
- field: "hpa",
85808
+ field: "pressure",
84646
85809
  quantity: t("the pressure", "la pressione"),
84647
85810
  label: t("Pressure", "Pressione"),
84648
85811
  unit: "hPa",
@@ -84678,7 +85841,7 @@ var SENSOR_CAP_VOCABULARY = {
84678
85841
  noun: t("Temperature", "Temperatura"),
84679
85842
  state: [{
84680
85843
  kind: "numeric",
84681
- field: "celsius",
85844
+ field: "temperature",
84682
85845
  quantity: t("the temperature", "la temperatura"),
84683
85846
  label: t("Temperature", "Temperatura"),
84684
85847
  unit: "°C",
@@ -84749,7 +85912,7 @@ var SENSOR_CAP_VOCABULARY = {
84749
85912
  state: [
84750
85913
  {
84751
85914
  kind: "numeric",
84752
- field: "percentage",
85915
+ field: "level",
84753
85916
  quantity: t("the battery level", "il livello della batteria"),
84754
85917
  label: t("Battery", "Batteria"),
84755
85918
  unit: "%",
@@ -84791,7 +85954,7 @@ var SENSOR_CAP_VOCABULARY = {
84791
85954
  icon: "battery-charging"
84792
85955
  }
84793
85956
  ],
84794
- events: { onStatusChanged: { notOperatorFacing: "the percentage / sleeping / charging state triggers each read one field of this change" } },
85957
+ events: { onStatusChanged: { notOperatorFacing: "the level / sleeping / charging state triggers each read one field of this change" } },
84795
85958
  extraEvents: [{
84796
85959
  category: EventCategory.DeviceAwake,
84797
85960
  label: t("Woke up", "Risvegliato"),
@@ -84809,7 +85972,7 @@ var SENSOR_CAP_VOCABULARY = {
84809
85972
  noun: t("Network link", "Collegamento di rete"),
84810
85973
  state: [{
84811
85974
  kind: "numeric",
84812
- field: "signalPercent",
85975
+ field: "signal",
84813
85976
  quantity: t("the signal strength", "l’intensità del segnale"),
84814
85977
  label: t("Signal", "Segnale"),
84815
85978
  unit: "%",
@@ -84902,6 +86065,8 @@ var ML = "model and training machinery — no device action";
84902
86065
  var INTERNAL = "internal plumbing between addons — never operator-facing";
84903
86066
  /** Every system-scoped cap, with why it is not a device's vocabulary. */
84904
86067
  var SYSTEM_CAP_VOCABULARY = {
86068
+ "action-source": system(INTERNAL),
86069
+ actions: system("declared actions — run from the Actions page and from the cameras that link them"),
84905
86070
  "addon-pages": system(UI_PLUMBING),
84906
86071
  "addon-pages-source": system(UI_PLUMBING),
84907
86072
  "addon-routes": system(UI_PLUMBING),
@@ -85042,8 +86207,47 @@ function projectAction(cap, action) {
85042
86207
  };
85043
86208
  }
85044
86209
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
85045
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher;
86210
+ DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
85046
86211
  DeviceFeature.BatteryOperated;
86212
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
86213
+ var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";
86214
+ var HOMEKIT_REASON = "HomeKit has no laundry-appliance service. The export tab would pair an accessory with nothing on it: running-state is withheld until a null reading can be unavailable, and power is not a HomeKit service this export honours.";
86215
+ var ALEXA_REASON = "Alexa has no laundry category in this export, and neither power nor running has an Alexa interface.";
86216
+ var GOOGLE_REASON = "Google WASHER, DRYER and DISHWASHER require RunCycle. A power threshold is not that trait, and this export does not publish RunCycle.";
86217
+ var MATTER_REASON = "No Matter export is built. The closest device types are Laundry Washer (0x0073) and Laundry Dryer (0x0074); a power threshold is not the Laundry Washer Mode cluster.";
86218
+ function withheld(reason) {
86219
+ return {
86220
+ [DeviceType.Washer]: {
86221
+ exported: false,
86222
+ reason
86223
+ },
86224
+ [DeviceType.Dryer]: {
86225
+ exported: false,
86226
+ reason
86227
+ },
86228
+ [DeviceType.Dishwasher]: {
86229
+ exported: false,
86230
+ reason
86231
+ }
86232
+ };
86233
+ }
86234
+ function offered(received) {
86235
+ return {
86236
+ [DeviceType.Washer]: {
86237
+ exported: true,
86238
+ as: received
86239
+ },
86240
+ [DeviceType.Dryer]: {
86241
+ exported: true,
86242
+ as: received
86243
+ },
86244
+ [DeviceType.Dishwasher]: {
86245
+ exported: true,
86246
+ as: received
86247
+ }
86248
+ };
86249
+ }
86250
+ offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
85047
86251
  Object.freeze({
85048
86252
  "accessories.getStatus": [{
85049
86253
  name: "deviceId",
@@ -85059,6 +86263,31 @@ Object.freeze({
85059
86263
  form: "single",
85060
86264
  optional: false
85061
86265
  }],
86266
+ "actions.get": [{
86267
+ name: "deviceId",
86268
+ form: "single",
86269
+ optional: true
86270
+ }],
86271
+ "actions.getLinks": [{
86272
+ name: "deviceId",
86273
+ form: "single",
86274
+ optional: false
86275
+ }],
86276
+ "actions.listForCamera": [{
86277
+ name: "deviceId",
86278
+ form: "single",
86279
+ optional: false
86280
+ }],
86281
+ "actions.run": [{
86282
+ name: "deviceId",
86283
+ form: "single",
86284
+ optional: true
86285
+ }],
86286
+ "actions.setLinks": [{
86287
+ name: "deviceId",
86288
+ form: "single",
86289
+ optional: false
86290
+ }],
85062
86291
  "addonSettings.getDeviceSettings": [{
85063
86292
  name: "deviceId",
85064
86293
  form: "single",
@@ -86010,6 +87239,11 @@ Object.freeze({
86010
87239
  form: "single",
86011
87240
  optional: false
86012
87241
  }],
87242
+ "groupActive.getStatus": [{
87243
+ name: "deviceId",
87244
+ form: "single",
87245
+ optional: false
87246
+ }],
86013
87247
  "groupCommands.getStatus": [{
86014
87248
  name: "deviceId",
86015
87249
  form: "single",
@@ -86220,6 +87454,11 @@ Object.freeze({
86220
87454
  form: "single",
86221
87455
  optional: false
86222
87456
  }],
87457
+ "meter.getStatus": [{
87458
+ name: "deviceId",
87459
+ form: "single",
87460
+ optional: false
87461
+ }],
86223
87462
  "motion.getStatus": [{
86224
87463
  name: "deviceId",
86225
87464
  form: "single",
@@ -87827,7 +89066,6 @@ Object.freeze({
87827
89066
  "network-access": "ingress",
87828
89067
  "smtp-provider": "email"
87829
89068
  });
87830
- DeviceType.Camera;
87831
89069
  /**
87832
89070
  * A saved Customize assignment (D936). The schema is the type: callers use
87833
89071
  * `z.infer`, they do not restate the shape.
@@ -89221,6 +90459,7 @@ function ingestReading(input) {
89221
90459
  value: converted
89222
90460
  };
89223
90461
  }
90462
+ String.raw`(?<=^|["'(\s>,\[{])`;
89224
90463
  var MB = 1024 * 1024;
89225
90464
  1024 * MB, 3072 * MB;
89226
90465
  //#endregion
@@ -95653,10 +96892,10 @@ function batteryModeForUnit(unit) {
95653
96892
  /** The canonical "value" field name for a typed sensor cap's slice. */
95654
96893
  function valueFieldForCap(cap) {
95655
96894
  switch (cap) {
95656
- case "temperature-sensor": return "celsius";
95657
- case "humidity-sensor": return "percent";
95658
- case "pressure-sensor": return "hpa";
95659
- case "ambient-light-sensor": return "lux";
96895
+ case "temperature-sensor": return "temperature";
96896
+ case "humidity-sensor": return "humidity";
96897
+ case "pressure-sensor": return "pressure";
96898
+ case "ambient-light-sensor": return "illuminance";
95660
96899
  case "numeric-sensor": return "value";
95661
96900
  }
95662
96901
  }
@@ -95901,7 +97140,7 @@ var SUPPORTED_CAPS = new Set([
95901
97140
  * - `'%'` (binary/bar batteries, already decoded to a percent) → the typed
95902
97141
  * `battery` cap (which models a 0..100 percentage).
95903
97142
  * - `'V'` (raw voltage, e.g. WS90/WH90/WS85 capacitor + soil/leaf push) →
95904
- * `battery.volts`. A voltage has no honest percentage, so `percentage`
97143
+ * `battery.voltage`. A voltage has no honest percentage, so `level`
95905
97144
  * stays null. The field lives on `battery`, so a numeric primary sensor
95906
97145
  * and a voltage battery do not share a slice.
95907
97146
  */
@@ -95915,7 +97154,7 @@ var EcowittSensorDevice = class extends BaseDevice {
95915
97154
  /**
95916
97155
  * Which battery cap (if any) was registered for this sensor:
95917
97156
  * - `'percent'` → the typed `battery` cap (percentage),
95918
- * - `'voltage'` → `battery.volts` (percentage stays null),
97157
+ * - `'voltage'` → `battery.voltage` (level stays null),
95919
97158
  * - `'none'` → no battery cap.
95920
97159
  */
95921
97160
  batteryMode = "none";
@@ -95986,7 +97225,7 @@ var EcowittSensorDevice = class extends BaseDevice {
95986
97225
  this.runtimeState.setCapState(this.capName, slice);
95987
97226
  if (this.batteryMode === "percent" && sensor.batteryUnit === "%" && typeof sensor.battery === "number") {
95988
97227
  const next = {
95989
- percentage: Math.max(0, Math.min(100, sensor.battery)),
97228
+ level: Math.max(0, Math.min(100, sensor.battery)),
95990
97229
  charging: "none",
95991
97230
  sleeping: false,
95992
97231
  lastUpdated: lastFetchedAt
@@ -95995,8 +97234,8 @@ var EcowittSensorDevice = class extends BaseDevice {
95995
97234
  }
95996
97235
  if (this.batteryMode === "voltage" && sensor.batteryUnit === "V" && typeof sensor.battery === "number" && Number.isFinite(sensor.battery)) {
95997
97236
  const next = {
95998
- percentage: null,
95999
- volts: sensor.battery,
97237
+ level: null,
97238
+ voltage: sensor.battery,
96000
97239
  charging: "none",
96001
97240
  sleeping: false,
96002
97241
  lastUpdated: lastFetchedAt
@@ -96008,7 +97247,7 @@ var EcowittSensorDevice = class extends BaseDevice {
96008
97247
  switch (this.capName) {
96009
97248
  case "temperature-sensor": {
96010
97249
  const COLD = {
96011
- celsius: 0,
97250
+ temperature: 0,
96012
97251
  lastFetchedAt: 0
96013
97252
  };
96014
97253
  this.ctx.registerNativeCap(temperatureSensorCapability, { getStatus: async () => this.readSlice(COLD) });
@@ -96017,7 +97256,7 @@ var EcowittSensorDevice = class extends BaseDevice {
96017
97256
  }
96018
97257
  case "humidity-sensor": {
96019
97258
  const COLD = {
96020
- percent: 0,
97259
+ humidity: 0,
96021
97260
  lastFetchedAt: 0
96022
97261
  };
96023
97262
  this.ctx.registerNativeCap(humiditySensorCapability, { getStatus: async () => this.readSlice(COLD) });
@@ -96026,7 +97265,7 @@ var EcowittSensorDevice = class extends BaseDevice {
96026
97265
  }
96027
97266
  case "pressure-sensor": {
96028
97267
  const COLD = {
96029
- hpa: 0,
97268
+ pressure: 0,
96030
97269
  lastFetchedAt: 0
96031
97270
  };
96032
97271
  this.ctx.registerNativeCap(pressureSensorCapability, { getStatus: async () => this.readSlice(COLD) });
@@ -96035,7 +97274,7 @@ var EcowittSensorDevice = class extends BaseDevice {
96035
97274
  }
96036
97275
  case "ambient-light-sensor": {
96037
97276
  const COLD = {
96038
- lux: 0,
97277
+ illuminance: 0,
96039
97278
  lastFetchedAt: 0
96040
97279
  };
96041
97280
  this.ctx.registerNativeCap(ambientLightSensorCapability, { getStatus: async () => this.readSlice(COLD) });
@@ -96054,8 +97293,8 @@ var EcowittSensorDevice = class extends BaseDevice {
96054
97293
  }
96055
97294
  }
96056
97295
  /**
96057
- * Register the `battery` cap. Percent batteries fill `percentage`. Voltage
96058
- * batteries fill `volts` and leave `percentage` null.
97296
+ * Register the `battery` cap. Percent batteries fill `level`. Voltage
97297
+ * batteries fill `voltage` and leave `level` null.
96059
97298
  */
96060
97299
  maybeRegisterBatteryCap() {
96061
97300
  const sensor = this.resolveSensor();
@@ -96067,7 +97306,7 @@ var EcowittSensorDevice = class extends BaseDevice {
96067
97306
  /** Register the typed `battery` (percentage) cap for a `%`-unit battery. */
96068
97307
  registerPercentBatteryCap() {
96069
97308
  const COLD = {
96070
- percentage: 0,
97309
+ level: 0,
96071
97310
  charging: "none",
96072
97311
  sleeping: false,
96073
97312
  lastUpdated: 0
@@ -96082,11 +97321,11 @@ var EcowittSensorDevice = class extends BaseDevice {
96082
97321
  this.runtimeState.setCapState("battery", COLD);
96083
97322
  this.batteryMode = "percent";
96084
97323
  }
96085
- /** Voltage batteries share the `battery` cap. `percentage` stays unknown. */
97324
+ /** Voltage batteries share the `battery` cap. `level` stays unknown. */
96086
97325
  registerVoltageBatteryCap() {
96087
97326
  const COLD = {
96088
- percentage: null,
96089
- volts: null,
97327
+ level: null,
97328
+ voltage: null,
96090
97329
  charging: "unknown",
96091
97330
  sleeping: false,
96092
97331
  lastUpdated: 0