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