@camstack/addon-provider-wyze 0.2.159 → 0.2.160

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
@@ -5453,7 +5453,7 @@ var ZodIssueCode = {
5453
5453
  var ZodFirstPartyTypeKind;
5454
5454
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5455
5455
  //#endregion
5456
- //#region ../types/dist/sleep-bykKrTgu.mjs
5456
+ //#region ../types/dist/sleep--jXS9hWo.mjs
5457
5457
  /**
5458
5458
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5459
5459
  * window to float samples (D455).
@@ -8646,6 +8646,7 @@ var CAP_NAMES = Object.freeze([
8646
8646
  "filesystem-browse",
8647
8647
  "flood",
8648
8648
  "gas",
8649
+ "group-active",
8649
8650
  "group-commands",
8650
8651
  "group-status",
8651
8652
  "heating-valve",
@@ -8668,6 +8669,7 @@ var CAP_NAMES = Object.freeze([
8668
8669
  "login-method",
8669
8670
  "media-player",
8670
8671
  "mesh-network",
8672
+ "meter",
8671
8673
  "metrics-provider",
8672
8674
  "model-convert",
8673
8675
  "model-distributor",
@@ -9466,6 +9468,10 @@ Object.freeze({
9466
9468
  "path": "detected",
9467
9469
  "valueType": "boolean"
9468
9470
  }],
9471
+ "group-active": [{
9472
+ "path": "active",
9473
+ "valueType": "boolean"
9474
+ }],
9469
9475
  "group-status": [
9470
9476
  {
9471
9477
  "path": "included",
@@ -9725,6 +9731,10 @@ Object.freeze({
9725
9731
  ]
9726
9732
  }
9727
9733
  ],
9734
+ "meter": [{
9735
+ "path": "value",
9736
+ "valueType": "number"
9737
+ }],
9728
9738
  "motion": [
9729
9739
  {
9730
9740
  "path": "detected",
@@ -10482,7 +10492,7 @@ function userError(code, params, englishFallback) {
10482
10492
  return new CamstackError(code, params, englishFallback);
10483
10493
  }
10484
10494
  //#endregion
10485
- //#region ../types/dist/err-msg-BXKzRBwg.mjs
10495
+ //#region ../types/dist/err-msg-DIorGYeC.mjs
10486
10496
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10487
10497
  DeviceType["Camera"] = "camera";
10488
10498
  DeviceType["Hub"] = "hub";
@@ -10576,8 +10586,14 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
10576
10586
  DeviceType["PetFeeder"] = "pet-feeder";
10577
10587
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10578
10588
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10579
- * Sources: native Bosch Home Connect, reusable by similar integrations. */
10589
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
10590
+ * A plug-built dishwasher carries running-state and power instead. */
10580
10591
  DeviceType["Dishwasher"] = "dishwasher";
10592
+ /** Washing machine. A plug-built one is running while its power stays
10593
+ * above a threshold. No program cap — that surface is the dishwasher's. */
10594
+ DeviceType["Washer"] = "washer";
10595
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
10596
+ DeviceType["Dryer"] = "dryer";
10581
10597
  return DeviceType;
10582
10598
  }({});
10583
10599
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -10835,7 +10851,7 @@ function errMsg(err) {
10835
10851
  return String(err);
10836
10852
  }
10837
10853
  //#endregion
10838
- //#region ../types/dist/composition-OhpuespA.mjs
10854
+ //#region ../types/dist/composition-Bb8iwHEJ.mjs
10839
10855
  /**
10840
10856
  * The ffmpeg build every CamStack node downloads, per platform and arch.
10841
10857
  *
@@ -11150,6 +11166,13 @@ var table = {
11150
11166
  apply: () => {
11151
11167
  throw new ExpressionEvalError("latchedAt: stateful evaluation context is required");
11152
11168
  }
11169
+ },
11170
+ schmitt: {
11171
+ minArgs: 3,
11172
+ maxArgs: 5,
11173
+ apply: () => {
11174
+ throw new ExpressionEvalError("schmitt: stateful evaluation context is required");
11175
+ }
11153
11176
  }
11154
11177
  };
11155
11178
  Object.freeze(Object.assign(Object.create(null), table));
@@ -11591,6 +11614,99 @@ function validateExpressionSource(src) {
11591
11614
  return null;
11592
11615
  }
11593
11616
  /**
11617
+ * One power reading becomes a washer, dryer or dishwasher (D944 helper).
11618
+ *
11619
+ * Standby of the control board is under 5 W; a motor or a heater is far
11620
+ * above that, so 5 W is the line. A door lock or a short pump spike is not
11621
+ * a cycle, so running starts only after 30 s above the line.
11622
+ *
11623
+ * A wash, dry or dishwasher pause (soak, heat-wait, heater off between
11624
+ * bursts) lasts several minutes. Ending the cycle on the first dip would
11625
+ * report "finished" in the middle of the program, so the stop dwell is
11626
+ * 10 minutes on every type. The number is stored per type so one machine
11627
+ * can move later without moving the others.
11628
+ */
11629
+ var APPLIANCE_TEMPLATE_ID = "appliance";
11630
+ var APPLIANCE_KINDS = [
11631
+ "washer",
11632
+ "dryer",
11633
+ "dishwasher"
11634
+ ];
11635
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
11636
+ /**
11637
+ * Statistics and integrals over a history read (D944).
11638
+ *
11639
+ * Pure: the same points always give the same answer, so a restart recomputes
11640
+ * rather than trusting a buffer. A gauge mean is time-weighted (value × ms),
11641
+ * never the mean of the samples. A hole with no held value is not integrated
11642
+ * as zero. A truncated read is not published.
11643
+ */
11644
+ var STATISTIC_STATS = [
11645
+ "min",
11646
+ "max",
11647
+ "mean",
11648
+ "sum",
11649
+ "count",
11650
+ "change"
11651
+ ];
11652
+ /**
11653
+ * The range a statistics or integral helper asks history for (D944).
11654
+ *
11655
+ * Calendar edges are the hub's timezone, never UTC midnight: a day is 23, 24
11656
+ * or 25 hours across a DST change. A tier is eligible only when its retention
11657
+ * is strictly longer than the longest that range can be. Forever qualifies.
11658
+ * Equal to a tier's retention is a refusal, not a short answer.
11659
+ */
11660
+ var MINUTE_MS = 6e4;
11661
+ var HOUR_MS = 60 * MINUTE_MS;
11662
+ var DAY_MS = 24 * HOUR_MS;
11663
+ var ROLLING_SPANS = [
11664
+ "1h",
11665
+ "24h",
11666
+ "2d",
11667
+ "7d",
11668
+ "30d",
11669
+ "custom"
11670
+ ];
11671
+ var CALENDAR_PERIODS = [
11672
+ "today",
11673
+ "yesterday",
11674
+ "this-week",
11675
+ "this-month"
11676
+ ];
11677
+ var RESET_PERIODS = [
11678
+ "never",
11679
+ "daily",
11680
+ "weekly",
11681
+ "monthly"
11682
+ ];
11683
+ 5 * MINUTE_MS;
11684
+ 24 * HOUR_MS, 2 * DAY_MS, 7 * DAY_MS, 30 * DAY_MS;
11685
+ 7 * DAY_MS, 30 * DAY_MS, 365 * DAY_MS;
11686
+ var rollingWindowObject = object({
11687
+ kind: literal("rolling"),
11688
+ span: _enum(ROLLING_SPANS),
11689
+ customMinutes: number().int().min(5).optional()
11690
+ }).check((payload) => {
11691
+ if (payload.value.span === "custom" && payload.value.customMinutes === void 0) payload.issues.push({
11692
+ code: "custom",
11693
+ message: "a custom range needs a duration in minutes",
11694
+ path: ["customMinutes"],
11695
+ input: payload.value
11696
+ });
11697
+ if (payload.value.span !== "custom" && payload.value.customMinutes !== void 0) payload.issues.push({
11698
+ code: "custom",
11699
+ message: "only a custom range carries a duration",
11700
+ path: ["customMinutes"],
11701
+ input: payload.value
11702
+ });
11703
+ });
11704
+ var RollingWindowSchema = rollingWindowObject;
11705
+ var StatisticWindowSchema = discriminatedUnion("kind", [rollingWindowObject, object({
11706
+ kind: literal("calendar"),
11707
+ period: _enum(CALENDAR_PERIODS)
11708
+ })]);
11709
+ /**
11594
11710
  * A composition source identity: `(addonId, stableId)`, never a numeric device
11595
11711
  * id. Split out so helper schemas can name it without a cycle through
11596
11712
  * `composition.ts` (D659).
@@ -11616,7 +11732,13 @@ var GROUP_TEMPLATE_IDS = [
11616
11732
  "occupancy",
11617
11733
  "binary",
11618
11734
  "switch",
11619
- "light"
11735
+ "light",
11736
+ "fan",
11737
+ "cover",
11738
+ "valve",
11739
+ "lock",
11740
+ "media-player",
11741
+ "climate"
11620
11742
  ];
11621
11743
  var NUMERIC_AGGREGATES = [
11622
11744
  "mean",
@@ -11646,14 +11768,73 @@ var HelperMemberSchema = object({
11646
11768
  cap: string().min(1),
11647
11769
  fieldPath: string().min(1)
11648
11770
  });
11649
- var HelperSpecSchema = discriminatedUnion("kind", [object({
11650
- kind: literal("group"),
11651
- templateId: GroupTemplateIdSchema,
11652
- aggregate: AggregateReducerSchema,
11653
- members: array(HelperMemberSchema).min(1).max(64),
11654
- /** Actuator groups only. Readings leave this unset. */
11655
- active: ActiveCriterionSchema.optional()
11656
- })]);
11771
+ var HelperSpecSchema = discriminatedUnion("kind", [
11772
+ object({
11773
+ kind: literal("group"),
11774
+ templateId: GroupTemplateIdSchema,
11775
+ aggregate: AggregateReducerSchema,
11776
+ members: array(HelperMemberSchema).min(1).max(64),
11777
+ /** Actuator groups only. Readings leave this unset. */
11778
+ active: ActiveCriterionSchema.optional()
11779
+ }),
11780
+ object({
11781
+ kind: literal("appliance"),
11782
+ templateId: literal(APPLIANCE_TEMPLATE_ID),
11783
+ appliance: _enum(APPLIANCE_KINDS),
11784
+ thresholdWatts: number().positive(),
11785
+ startDwellMs: number().int().nonnegative(),
11786
+ stopDwellMs: number().int().nonnegative(),
11787
+ member: HelperMemberSchema
11788
+ }),
11789
+ object({
11790
+ kind: literal("statistic"),
11791
+ stat: _enum(STATISTIC_STATS),
11792
+ window: StatisticWindowSchema,
11793
+ source: HelperMemberSchema
11794
+ }),
11795
+ object({
11796
+ kind: literal("integral"),
11797
+ reset: _enum(RESET_PERIODS),
11798
+ output: _enum(["energy", "meter"]),
11799
+ source: HelperMemberSchema
11800
+ }),
11801
+ object({
11802
+ kind: literal("threshold"),
11803
+ source: HelperMemberSchema,
11804
+ high: number(),
11805
+ low: number(),
11806
+ onForMs: number().int().nonnegative(),
11807
+ offForMs: number().int().nonnegative()
11808
+ }),
11809
+ object({
11810
+ kind: literal("arithmetic"),
11811
+ op: _enum([
11812
+ "sum",
11813
+ "difference",
11814
+ "ratio"
11815
+ ]),
11816
+ inputs: array(HelperMemberSchema).min(2).max(64)
11817
+ }),
11818
+ object({
11819
+ kind: literal("expression"),
11820
+ expr: string().min(1),
11821
+ inputs: array(object({
11822
+ name: string().min(1),
11823
+ member: HelperMemberSchema
11824
+ })).min(1).max(64),
11825
+ output: _enum(["number", "boolean"])
11826
+ }),
11827
+ object({
11828
+ kind: literal("derivative"),
11829
+ window: RollingWindowSchema,
11830
+ per: _enum([
11831
+ "second",
11832
+ "minute",
11833
+ "hour"
11834
+ ]),
11835
+ source: HelperMemberSchema
11836
+ })
11837
+ ]);
11657
11838
  /** A composed device's stableId is this prefix plus the block id. */
11658
11839
  var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11659
11840
  /** Longest snippet a `code` source may carry (slice 4). */
@@ -11714,6 +11895,12 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11714
11895
  "all"
11715
11896
  ]),
11716
11897
  reads: array(CompositionFieldReadSchema).min(1).max(64),
11898
+ /**
11899
+ * Set when the reads are an enum state, not a yes/no: the listed states
11900
+ * count as active (a cover is `open`). A state outside the list is inactive;
11901
+ * a read that is not a state at all is left out. Only `any` / `all` use it.
11902
+ */
11903
+ activeWhen: array(string().min(1)).min(1).optional(),
11717
11904
  /** `value` is the reading; the others are the member counts. */
11718
11905
  publish: _enum([
11719
11906
  "value",
@@ -11727,6 +11914,26 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11727
11914
  "omit",
11728
11915
  "offline"
11729
11916
  ])
11917
+ }),
11918
+ object({
11919
+ kind: literal("series"),
11920
+ read: CompositionFieldReadSchema,
11921
+ /** `value` is the statistic or the integral. The others are the gap counts. */
11922
+ publish: _enum([
11923
+ "value",
11924
+ "included",
11925
+ "excluded",
11926
+ "total"
11927
+ ]),
11928
+ /**
11929
+ * What an unanswered `value` does. Counts ignore this. `offline` leaves the
11930
+ * device unseeded until history answers, so a restart cannot publish 0.
11931
+ */
11932
+ empty: _enum([
11933
+ "null",
11934
+ "omit",
11935
+ "offline"
11936
+ ])
11730
11937
  })
11731
11938
  ]);
11732
11939
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
@@ -16906,7 +17113,22 @@ var BrokerProviderInfoSchema = object({
16906
17113
  */
16907
17114
  interactiveConnectPath: string().min(1).optional(),
16908
17115
  /** Relative hub path to disconnect the linked account (same popup pattern). */
16909
- interactiveDisconnectPath: string().min(1).optional()
17116
+ interactiveDisconnectPath: string().min(1).optional(),
17117
+ /**
17118
+ * The kind signs in through a server-held interactive session instead of
17119
+ * (or before) typed credentials. `basePath` is a hub-relative route set
17120
+ * served by the owning addon (`/addon/<id>/...`) speaking the sign-in
17121
+ * contract: `POST {basePath}/start` (body: the form's settings) answers
17122
+ * `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
17123
+ * `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
17124
+ * 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
17125
+ * (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
17126
+ * existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
17127
+ * (`{ sessionId }`) drops the session. The dialog that edits a broker
17128
+ * offers it as the default path; the fields the session fills are
17129
+ * marked `filledBySignIn` in the kind's settings schema.
17130
+ */
17131
+ signIn: object({ basePath: string().min(1) }).optional()
16910
17132
  }))
16911
17133
  });
16912
17134
  var ListInputSchema = object({
@@ -18011,7 +18233,8 @@ var CompositionProblemSchema = object({
18011
18233
  "native-capability-replace",
18012
18234
  "mixed-quantity",
18013
18235
  "helper-mismatch",
18014
- "member-constraint"
18236
+ "member-constraint",
18237
+ "retention"
18015
18238
  ]),
18016
18239
  path: string(),
18017
18240
  message: string()
@@ -18245,13 +18468,17 @@ Object.freeze({
18245
18468
  latchedAt: {
18246
18469
  kind: "fixed",
18247
18470
  result: ["number", "null"]
18471
+ },
18472
+ schmitt: {
18473
+ kind: "fixed",
18474
+ result: ["boolean", "null"]
18248
18475
  }
18249
18476
  });
18250
18477
  /**
18251
18478
  * One catalog row per base reading a group helper can build (D944 §3).
18252
18479
  * Data, not a form default written into the block.
18253
18480
  */
18254
- var NOW = {
18481
+ var NOW$2 = {
18255
18482
  fieldPath: "lastFetchedAt",
18256
18483
  expr: "now"
18257
18484
  };
@@ -18265,6 +18492,8 @@ function numeric(input) {
18265
18492
  family: "numeric",
18266
18493
  deviceType: DeviceType.Sensor,
18267
18494
  memberDeviceType: DeviceType.Sensor,
18495
+ memberDeviceTypes: [DeviceType.Sensor],
18496
+ activeWhen: void 0,
18268
18497
  commandCaps: [],
18269
18498
  baseUnit: baseUnitOf(input.quantity),
18270
18499
  aggregates: NUMERIC_AGGREGATES,
@@ -18280,6 +18509,8 @@ function binary(input) {
18280
18509
  family: "binary",
18281
18510
  deviceType: DeviceType.Sensor,
18282
18511
  memberDeviceType: DeviceType.Sensor,
18512
+ memberDeviceTypes: [DeviceType.Sensor],
18513
+ activeWhen: void 0,
18283
18514
  commandCaps: [],
18284
18515
  quantity: "none",
18285
18516
  baseUnit: void 0,
@@ -18297,7 +18528,7 @@ var GROUP_TEMPLATES = [
18297
18528
  quantity: "temperature",
18298
18529
  defaultAggregate: "mean",
18299
18530
  empty: "null",
18300
- companions: [NOW]
18531
+ companions: [NOW$2]
18301
18532
  }),
18302
18533
  numeric({
18303
18534
  id: "humidity",
@@ -18308,7 +18539,7 @@ var GROUP_TEMPLATES = [
18308
18539
  quantity: "humidity",
18309
18540
  defaultAggregate: "mean",
18310
18541
  empty: "null",
18311
- companions: [NOW]
18542
+ companions: [NOW$2]
18312
18543
  }),
18313
18544
  numeric({
18314
18545
  id: "pressure",
@@ -18319,7 +18550,7 @@ var GROUP_TEMPLATES = [
18319
18550
  quantity: "pressure",
18320
18551
  defaultAggregate: "mean",
18321
18552
  empty: "null",
18322
- companions: [NOW]
18553
+ companions: [NOW$2]
18323
18554
  }),
18324
18555
  numeric({
18325
18556
  id: "illuminance",
@@ -18330,7 +18561,7 @@ var GROUP_TEMPLATES = [
18330
18561
  quantity: "illuminance",
18331
18562
  defaultAggregate: "mean",
18332
18563
  empty: "null",
18333
- companions: [NOW]
18564
+ companions: [NOW$2]
18334
18565
  }),
18335
18566
  numeric({
18336
18567
  id: "power",
@@ -18341,7 +18572,7 @@ var GROUP_TEMPLATES = [
18341
18572
  quantity: "power",
18342
18573
  defaultAggregate: "sum",
18343
18574
  empty: "omit",
18344
- companions: [NOW]
18575
+ companions: [NOW$2]
18345
18576
  }),
18346
18577
  numeric({
18347
18578
  id: "energy",
@@ -18352,7 +18583,7 @@ var GROUP_TEMPLATES = [
18352
18583
  quantity: "energy",
18353
18584
  defaultAggregate: "sum",
18354
18585
  empty: "omit",
18355
- companions: [NOW]
18586
+ companions: [NOW$2]
18356
18587
  }),
18357
18588
  numeric({
18358
18589
  id: "battery",
@@ -18387,7 +18618,7 @@ var GROUP_TEMPLATES = [
18387
18618
  quantity: "device",
18388
18619
  defaultAggregate: "mean",
18389
18620
  empty: "null",
18390
- companions: [NOW]
18621
+ companions: [NOW$2]
18391
18622
  }),
18392
18623
  binary({
18393
18624
  id: "motion",
@@ -18481,6 +18712,11 @@ var GROUP_TEMPLATES = [
18481
18712
  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.",
18482
18713
  deviceType: DeviceType.Switch,
18483
18714
  outputCap: "switch",
18715
+ outputField: "on",
18716
+ member: {
18717
+ cap: "switch",
18718
+ fieldPath: "on"
18719
+ },
18484
18720
  commandCaps: ["switch"]
18485
18721
  }),
18486
18722
  actuator({
@@ -18488,34 +18724,136 @@ var GROUP_TEMPLATES = [
18488
18724
  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.",
18489
18725
  deviceType: DeviceType.Light,
18490
18726
  outputCap: "switch",
18727
+ outputField: "on",
18728
+ member: {
18729
+ cap: "switch",
18730
+ fieldPath: "on"
18731
+ },
18491
18732
  commandCaps: [
18492
18733
  "switch",
18493
18734
  "brightness",
18494
18735
  "color"
18495
18736
  ]
18737
+ }),
18738
+ actuator({
18739
+ id: "fan",
18740
+ 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.",
18741
+ deviceType: DeviceType.Fan,
18742
+ outputCap: "switch",
18743
+ outputField: "on",
18744
+ member: {
18745
+ cap: "switch",
18746
+ fieldPath: "on"
18747
+ },
18748
+ commandCaps: ["switch", "fan-control"]
18749
+ }),
18750
+ actuator({
18751
+ id: "cover",
18752
+ 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.",
18753
+ deviceType: DeviceType.Cover,
18754
+ outputCap: "group-active",
18755
+ outputField: "active",
18756
+ member: {
18757
+ cap: "cover",
18758
+ fieldPath: "state"
18759
+ },
18760
+ activeWhen: [
18761
+ "open",
18762
+ "opening",
18763
+ "stopped"
18764
+ ],
18765
+ commandCaps: ["cover"]
18766
+ }),
18767
+ actuator({
18768
+ id: "valve",
18769
+ 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.",
18770
+ deviceType: DeviceType.Valve,
18771
+ outputCap: "group-active",
18772
+ outputField: "active",
18773
+ member: {
18774
+ cap: "valve",
18775
+ fieldPath: "state"
18776
+ },
18777
+ activeWhen: [
18778
+ "open",
18779
+ "opening",
18780
+ "stopped"
18781
+ ],
18782
+ commandCaps: ["valve"]
18783
+ }),
18784
+ actuator({
18785
+ id: "lock",
18786
+ 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.",
18787
+ deviceType: DeviceType.Lock,
18788
+ outputCap: "group-active",
18789
+ outputField: "active",
18790
+ member: {
18791
+ cap: "lock-control",
18792
+ fieldPath: "state"
18793
+ },
18794
+ activeWhen: ["locked"],
18795
+ commandCaps: ["lock-control"]
18796
+ }),
18797
+ actuator({
18798
+ id: "media-player",
18799
+ 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.",
18800
+ deviceType: DeviceType.MediaPlayer,
18801
+ outputCap: "group-active",
18802
+ outputField: "active",
18803
+ member: {
18804
+ cap: "media-player",
18805
+ fieldPath: "state"
18806
+ },
18807
+ activeWhen: ["playing", "buffering"],
18808
+ commandCaps: ["media-player"]
18809
+ }),
18810
+ actuator({
18811
+ id: "climate",
18812
+ 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.",
18813
+ deviceType: DeviceType.Thermostat,
18814
+ memberDeviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
18815
+ outputCap: "group-active",
18816
+ outputField: "active",
18817
+ member: {
18818
+ cap: "climate-control",
18819
+ fieldPath: "mode"
18820
+ },
18821
+ activeWhen: [
18822
+ "heat",
18823
+ "cool",
18824
+ "auto",
18825
+ "heat_cool",
18826
+ "fan_only",
18827
+ "dry"
18828
+ ],
18829
+ commandCaps: ["climate-control"]
18496
18830
  })
18497
18831
  ];
18498
18832
  function actuator(input) {
18499
18833
  return {
18500
- ...input,
18834
+ id: input.id,
18835
+ summary: input.summary,
18836
+ deviceType: input.deviceType,
18837
+ outputCap: input.outputCap,
18838
+ outputField: input.outputField,
18839
+ commandCaps: input.commandCaps,
18501
18840
  family: "actuator",
18502
18841
  role: void 0,
18503
18842
  memberDeviceType: input.deviceType,
18504
- outputField: "on",
18843
+ memberDeviceTypes: input.memberDeviceTypes ?? [input.deviceType],
18844
+ activeWhen: input.activeWhen,
18505
18845
  quantity: "none",
18506
18846
  baseUnit: void 0,
18507
18847
  aggregates: BINARY_AGGREGATES,
18508
18848
  defaultAggregate: "any",
18509
18849
  empty: "offline",
18510
- accept: [{
18511
- cap: "switch",
18512
- fieldPath: "on"
18513
- }],
18850
+ accept: [input.member],
18514
18851
  companions: [CHANGED]
18515
18852
  };
18516
18853
  }
18517
18854
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18518
18855
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18856
+ DeviceType.Camera;
18519
18857
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18520
18858
  /**
18521
18859
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -43739,7 +44077,7 @@ var NetworkAccessStatusSchema = object({
43739
44077
  });
43740
44078
  /**
43741
44079
  * Optional, richer endpoint shape returned by providers that expose
43742
- * MORE than one ingress concurrently (Tailscale Ingress with mixed
44080
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
43743
44081
  * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
43744
44082
  * the originating provider config (mode + sourcePort) so the
43745
44083
  * orchestrator UI can label rows distinctly. Providers that expose only
@@ -46426,7 +46764,20 @@ var NcRuleInputSchema = object({
46426
46764
  * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
46427
46765
  * path does not run Zod defaults.
46428
46766
  */
46429
- groupingSec: number().int().min(5).max(60).optional()
46767
+ groupingSec: number().int().min(5).max(60).optional(),
46768
+ /**
46769
+ * ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
46770
+ * carries "Correct" / "Wrong" buttons (and, where the channel has room, a
46771
+ * "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
46772
+ * same mark the Library writes, and records whose notification it was and
46773
+ * when. A channel with no buttons gets the same two choices as links.
46774
+ *
46775
+ * `.optional()`, never `.default()` (a Zod default does not run on the addon
46776
+ * cap path): absent and `false` both mean off, which is what every rule
46777
+ * authored before this field meant. An editor turns it off by writing
46778
+ * `false`, so it needs no `clear` key.
46779
+ */
46780
+ askVerdict: boolean().optional()
46430
46781
  });
46431
46782
  /**
46432
46783
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -57250,7 +57601,8 @@ var BATTERY_DEVICE_TYPES = [
57250
57601
  DeviceType.Switch,
57251
57602
  DeviceType.PetFeeder,
57252
57603
  DeviceType.Container,
57253
- DeviceType.Thermostat
57604
+ DeviceType.Thermostat,
57605
+ DeviceType.EventEmitter
57254
57606
  ];
57255
57607
  /** Generic Customize target for the percentage reading (D936). */
57256
57608
  var batteryCustomize = {
@@ -60118,7 +60470,17 @@ var groupCommandsCapability = {
60118
60470
  scope: "device",
60119
60471
  deviceNative: true,
60120
60472
  mode: "singleton",
60121
- deviceTypes: [DeviceType.Switch, DeviceType.Light],
60473
+ deviceTypes: [
60474
+ DeviceType.Switch,
60475
+ DeviceType.Light,
60476
+ DeviceType.Fan,
60477
+ DeviceType.Cover,
60478
+ DeviceType.Valve,
60479
+ DeviceType.Lock,
60480
+ DeviceType.MediaPlayer,
60481
+ DeviceType.Thermostat,
60482
+ DeviceType.Climate
60483
+ ],
60122
60484
  methods: {},
60123
60485
  status: {
60124
60486
  schema: GroupCommandsSchema,
@@ -60136,6 +60498,49 @@ var groupCommandsCapability = {
60136
60498
  history: "none"
60137
60499
  };
60138
60500
  /**
60501
+ * Whether a helper actuator group counts as active (D944): at least one
60502
+ * included member is, or every included member is. What "active" means is the
60503
+ * type's: a cover is open, a lock is locked, a media player is playing, a
60504
+ * climate device is not off. A group of lights, switches and fans reports
60505
+ * `switch.on` instead; this cap is for the types whose own state is not a
60506
+ * yes/no. Unknown members are left out (`group-status` counts them) and an
60507
+ * empty included set takes the device offline, never `active: false`.
60508
+ */
60509
+ var GroupActiveSchema = object({
60510
+ active: boolean().meta({ label: "Active" }),
60511
+ /** Ms epoch when the slice was last updated. */
60512
+ lastChangedAt: number()
60513
+ });
60514
+ var groupActiveCapability = {
60515
+ name: "group-active",
60516
+ scope: "device",
60517
+ deviceNative: true,
60518
+ mode: "singleton",
60519
+ deviceTypes: [
60520
+ DeviceType.Cover,
60521
+ DeviceType.Valve,
60522
+ DeviceType.Lock,
60523
+ DeviceType.MediaPlayer,
60524
+ DeviceType.Climate,
60525
+ DeviceType.Thermostat
60526
+ ],
60527
+ methods: {},
60528
+ status: {
60529
+ schema: GroupActiveSchema,
60530
+ kind: "push"
60531
+ },
60532
+ runtimeState: GroupActiveSchema,
60533
+ /**
60534
+ * Runtime-state durability: **session** — recomputed from the members on the
60535
+ * next source event; a restored value would outlive the members it named.
60536
+ *
60537
+ * See `RuntimeStateDurability`. Enforced by
60538
+ * `scripts/check-runtime-state-durability.ts`.
60539
+ */
60540
+ durability: "session",
60541
+ history: "none"
60542
+ };
60543
+ /**
60139
60544
  * How many members of a helper group answered (D944). Counts are integers,
60140
60545
  * not measurements: zero included is a real count, never a stand-in reading.
60141
60546
  * The reading itself stays on the output capability. History is none — the
@@ -60166,7 +60571,14 @@ var groupStatusCapability = {
60166
60571
  deviceTypes: [
60167
60572
  DeviceType.Sensor,
60168
60573
  DeviceType.Switch,
60169
- DeviceType.Light
60574
+ DeviceType.Light,
60575
+ DeviceType.Fan,
60576
+ DeviceType.Cover,
60577
+ DeviceType.Valve,
60578
+ DeviceType.Lock,
60579
+ DeviceType.MediaPlayer,
60580
+ DeviceType.Thermostat,
60581
+ DeviceType.Climate
60170
60582
  ],
60171
60583
  methods: {},
60172
60584
  status: {
@@ -61439,7 +61851,14 @@ var lockControlCapability = {
61439
61851
  }),
61440
61852
  unlock: method(object({
61441
61853
  deviceId: number().int().nonnegative(),
61442
- code: string().min(1).optional()
61854
+ code: string().min(1).optional(),
61855
+ /**
61856
+ * Honoured by a helper lock GROUP only (D944): unlocking several doors
61857
+ * with one call is refused unless the caller says it was confirmed. A
61858
+ * single lock ignores it. The refusal lives in the callee, so no
61859
+ * automation or API caller reaches the doors by skipping a dialog.
61860
+ */
61861
+ confirm: boolean().optional()
61443
61862
  }), _void(), {
61444
61863
  kind: "mutation",
61445
61864
  auth: "protected"
@@ -61476,6 +61895,44 @@ var lockControlCapability = {
61476
61895
  volatileStateFields: ["lastChangedAt"]
61477
61896
  };
61478
61897
  /**
61898
+ * A monotonic total between resets (D944). Energy uses `power-meter.kwhTotal`
61899
+ * so dashboards keep one series; every other instantaneous reading lands here.
61900
+ * `value` is the time integral in the source quantity's accumulated base
61901
+ * (base-unit seconds, or kWh on the power meter). `null` is a gap, never zero.
61902
+ */
61903
+ var MeterStatusSchema = object({
61904
+ value: number().nullable().meta(readingMeta("device", { label: "Total" })),
61905
+ lastFetchedAt: number()
61906
+ });
61907
+ var meterCapability = {
61908
+ name: "meter",
61909
+ scope: "device",
61910
+ deviceNative: true,
61911
+ mode: "singleton",
61912
+ deviceTypes: [DeviceType.Sensor],
61913
+ methods: {},
61914
+ status: {
61915
+ schema: MeterStatusSchema,
61916
+ kind: "push"
61917
+ },
61918
+ runtimeState: MeterStatusSchema,
61919
+ /**
61920
+ * Runtime-state durability: **restored** — the total is a cache of the
61921
+ * history integral. A restart shows it only until the history pass
61922
+ * recomputes the open period; it is not an in-memory buffer.
61923
+ *
61924
+ * See `RuntimeStateDurability`. Enforced by
61925
+ * `scripts/check-runtime-state-durability.ts`.
61926
+ */
61927
+ durability: "restored",
61928
+ history: { fields: { value: {
61929
+ kind: "counter",
61930
+ deadband: .001,
61931
+ minIntervalMs: 6e4
61932
+ } } },
61933
+ volatileStateFields: ["lastFetchedAt"]
61934
+ };
61935
+ /**
61479
61936
  * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
61480
61937
  * Apple TV, Spotify, Roku, generic OTT receivers, …) on
61481
61938
  * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
@@ -63987,9 +64444,9 @@ var PowerMeterStatusSchema = object({
63987
64444
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
63988
64445
  kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
63989
64446
  /** Voltage in volts. */
63990
- volts: number().optional().meta(readingMeta("voltage")),
64447
+ volts: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
63991
64448
  /** Current in amperes. */
63992
- amps: number().optional().meta(readingMeta("current")),
64449
+ amps: number().optional().meta(readingMeta("current", { label: "Current" })),
63993
64450
  /** Apparent power in volt-amperes. */
63994
64451
  va: number().optional().meta(readingMeta("apparent-power")),
63995
64452
  /** Power factor (0..1) if reported. */
@@ -63997,12 +64454,74 @@ var PowerMeterStatusSchema = object({
63997
64454
  /** Ms epoch when the slice was last updated. */
63998
64455
  lastFetchedAt: number()
63999
64456
  });
64457
+ /**
64458
+ * Loads that draw metered mains power (D951). The reading is a feature of the
64459
+ * device itself — a plug, a lamp, a fan, an air conditioner, a boiler, a
64460
+ * vacuum dock — not a separate sensor beside it. A `Sensor` stays: a clamp
64461
+ * meter or a standalone energy monitor IS the device. Cover, lock, valve,
64462
+ * camera and the like are left out: their draw is not what anyone meters.
64463
+ */
64464
+ var POWER_METER_DEVICE_TYPES = [
64465
+ DeviceType.Sensor,
64466
+ DeviceType.Switch,
64467
+ DeviceType.Light,
64468
+ DeviceType.Fan,
64469
+ DeviceType.Climate,
64470
+ DeviceType.Thermostat,
64471
+ DeviceType.Humidifier,
64472
+ DeviceType.WaterHeater,
64473
+ DeviceType.MediaPlayer,
64474
+ DeviceType.Washer,
64475
+ DeviceType.Dryer,
64476
+ DeviceType.Dishwasher,
64477
+ DeviceType.Vacuum,
64478
+ DeviceType.LawnMower,
64479
+ DeviceType.PetFeeder
64480
+ ];
64481
+ /** Everything but a bare `Sensor`: a sensor that takes its reading from another sensor adds nothing. */
64482
+ var POWER_METER_CUSTOMIZE_DEVICE_TYPES = POWER_METER_DEVICE_TYPES.filter((type) => type !== DeviceType.Sensor);
64483
+ /** One scalar target per reading; each is sourced from a device of its own (D951). */
64484
+ function powerTarget(id, unit) {
64485
+ return {
64486
+ id,
64487
+ cardinality: "scalar",
64488
+ vocabulary: {
64489
+ cap: "power-meter",
64490
+ stateField: id
64491
+ },
64492
+ deviceTypes: POWER_METER_CUSTOMIZE_DEVICE_TYPES,
64493
+ apply: "add",
64494
+ path: id,
64495
+ sources: [{
64496
+ kind: "numeric",
64497
+ requiredUnit: unit,
64498
+ transforms: [{ kind: "identity" }]
64499
+ }],
64500
+ companions: [{
64501
+ kind: "clock",
64502
+ path: "lastFetchedAt",
64503
+ when: "always"
64504
+ }],
64505
+ sourceReport: { status: "assignment-status" },
64506
+ advancedExpression: true
64507
+ };
64508
+ }
64509
+ /** "Power and energy" extension: watts, energy, voltage and current from other devices (D951). */
64510
+ var powerMeterCustomize = {
64511
+ version: 1,
64512
+ targets: [
64513
+ powerTarget("watts", "W"),
64514
+ powerTarget("kwhTotal", "kWh"),
64515
+ powerTarget("volts", "V"),
64516
+ powerTarget("amps", "A")
64517
+ ]
64518
+ };
64000
64519
  var powerMeterCapability = {
64001
64520
  name: "power-meter",
64002
64521
  scope: "device",
64003
64522
  deviceNative: true,
64004
64523
  mode: "singleton",
64005
- deviceTypes: [DeviceType.Sensor],
64524
+ deviceTypes: POWER_METER_DEVICE_TYPES,
64006
64525
  methods: {},
64007
64526
  status: {
64008
64527
  schema: PowerMeterStatusSchema,
@@ -64059,7 +64578,8 @@ var powerMeterCapability = {
64059
64578
  } },
64060
64579
  /** Clock fields: written, but excluded from the compare that decides
64061
64580
  * whether persisting is worth a SQLite commit. */
64062
- volatileStateFields: ["lastFetchedAt"]
64581
+ volatileStateFields: ["lastFetchedAt"],
64582
+ customize: powerMeterCustomize
64063
64583
  };
64064
64584
  /**
64065
64585
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -64740,6 +65260,8 @@ var runningCustomize = {
64740
65260
  stateField: "running"
64741
65261
  },
64742
65262
  deviceTypes: [
65263
+ DeviceType.Washer,
65264
+ DeviceType.Dryer,
64743
65265
  DeviceType.Dishwasher,
64744
65266
  DeviceType.Vacuum,
64745
65267
  DeviceType.LawnMower,
@@ -67229,7 +67751,8 @@ var switchCapability = {
67229
67751
  deviceTypes: [
67230
67752
  DeviceType.Switch,
67231
67753
  DeviceType.Siren,
67232
- DeviceType.Light
67754
+ DeviceType.Light,
67755
+ DeviceType.Fan
67233
67756
  ],
67234
67757
  methods: { setState: method(object({
67235
67758
  deviceId: number(),
@@ -69442,8 +69965,9 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
69442
69965
  kind: "mutation",
69443
69966
  auth: "admin"
69444
69967
  });
69968
+ DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
69445
69969
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
69446
- 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;
69970
+ 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;
69447
69971
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
69448
69972
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
69449
69973
  new Set(CAP_NAMES);
@@ -69772,6 +70296,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
69772
70296
  featureProbe: featureProbeCapability,
69773
70297
  flood: floodCapability,
69774
70298
  gas: gasCapability,
70299
+ groupActive: groupActiveCapability,
69775
70300
  groupCommands: groupCommandsCapability,
69776
70301
  groupStatus: groupStatusCapability,
69777
70302
  heatingValve: heatingValveCapability,
@@ -69783,6 +70308,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
69783
70308
  lawnMowerControl: lawnMowerControlCapability,
69784
70309
  lockControl: lockControlCapability,
69785
70310
  mediaPlayer: mediaPlayerCapability,
70311
+ meter: meterCapability,
69786
70312
  motion: motionCapability,
69787
70313
  motionTrigger: motionTriggerCapability,
69788
70314
  motionZones: motionZonesCapability,
@@ -71042,7 +71568,7 @@ var BaseDeviceProvider = class extends BaseAddon {
71042
71568
  return toDeviceSummary(device, this.addonId);
71043
71569
  }
71044
71570
  };
71045
- 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;
71571
+ 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;
71046
71572
  new Set(Object.values(DeviceType));
71047
71573
  Object.freeze({
71048
71574
  "accessories.getStatus": {
@@ -74265,6 +74791,13 @@ Object.freeze({
74265
74791
  access: "view",
74266
74792
  role: "viewer"
74267
74793
  },
74794
+ "groupActive.getStatus": {
74795
+ capName: "group-active",
74796
+ capScope: "device",
74797
+ addonId: null,
74798
+ access: "view",
74799
+ role: "viewer"
74800
+ },
74268
74801
  "groupCommands.getStatus": {
74269
74802
  capName: "group-commands",
74270
74803
  capScope: "device",
@@ -75231,6 +75764,13 @@ Object.freeze({
75231
75764
  access: "create",
75232
75765
  role: "admin"
75233
75766
  },
75767
+ "meter.getStatus": {
75768
+ capName: "meter",
75769
+ capScope: "device",
75770
+ addonId: null,
75771
+ access: "view",
75772
+ role: "viewer"
75773
+ },
75234
75774
  "metricsProvider.collectSnapshot": {
75235
75775
  capName: "metrics-provider",
75236
75776
  capScope: "system",
@@ -83329,7 +83869,39 @@ var APPLIANCE_CAP_VOCABULARY = {
83329
83869
  phrase: t("When {device} finishes", "Quando {device} finisce"),
83330
83870
  icon: "square"
83331
83871
  }
83332
- }
83872
+ },
83873
+ overrides: { byType: {
83874
+ [DeviceType.Washer]: {
83875
+ state: { running: {
83876
+ whenTrue: t("When the washing machine starts", "Quando la lavatrice inizia"),
83877
+ whenFalse: t("When the washing machine has finished", "Quando la lavatrice ha finito")
83878
+ } },
83879
+ events: {
83880
+ onStarted: { phrase: t("When the washing machine starts", "Quando la lavatrice inizia") },
83881
+ onFinished: { phrase: t("When the washing machine has finished", "Quando la lavatrice ha finito") }
83882
+ }
83883
+ },
83884
+ [DeviceType.Dryer]: {
83885
+ state: { running: {
83886
+ whenTrue: t("When the dryer starts", "Quando l'asciugatrice inizia"),
83887
+ whenFalse: t("When the dryer has finished", "Quando l'asciugatrice ha finito")
83888
+ } },
83889
+ events: {
83890
+ onStarted: { phrase: t("When the dryer starts", "Quando l'asciugatrice inizia") },
83891
+ onFinished: { phrase: t("When the dryer has finished", "Quando l'asciugatrice ha finito") }
83892
+ }
83893
+ },
83894
+ [DeviceType.Dishwasher]: {
83895
+ state: { running: {
83896
+ whenTrue: t("When the dishwasher starts", "Quando la lavastoviglie inizia"),
83897
+ whenFalse: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito")
83898
+ } },
83899
+ events: {
83900
+ onStarted: { phrase: t("When the dishwasher starts", "Quando la lavastoviglie inizia") },
83901
+ onFinished: { phrase: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito") }
83902
+ }
83903
+ }
83904
+ } }
83333
83905
  },
83334
83906
  update: {
83335
83907
  scope: "device",
@@ -84101,6 +84673,10 @@ var SENSOR_CAP_VOCABULARY = {
84101
84673
  icon: "flame"
84102
84674
  }]
84103
84675
  },
84676
+ "group-active": {
84677
+ scope: "device",
84678
+ noun: t("Group state", "Stato del gruppo")
84679
+ },
84104
84680
  "group-commands": {
84105
84681
  scope: "device",
84106
84682
  noun: t("Group command", "Comando di gruppo")
@@ -84157,6 +84733,17 @@ var SENSOR_CAP_VOCABULARY = {
84157
84733
  }],
84158
84734
  events: { onMotionChanged: { notOperatorFacing: "the `detected` state trigger phrases both edges, from the mirror that survives a dropped event (D49)" } }
84159
84735
  },
84736
+ meter: {
84737
+ scope: "device",
84738
+ noun: t("Meter", "Contatore"),
84739
+ state: [{
84740
+ kind: "numeric",
84741
+ field: "value",
84742
+ quantity: t("the total", "il totale"),
84743
+ label: t("Total", "Totale"),
84744
+ icon: "gauge"
84745
+ }]
84746
+ },
84160
84747
  "numeric-sensor": {
84161
84748
  scope: "device",
84162
84749
  noun: t("Sensor", "Sensore"),
@@ -84629,8 +85216,47 @@ function projectAction(cap, action) {
84629
85216
  };
84630
85217
  }
84631
85218
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
84632
- 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;
85219
+ 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;
84633
85220
  DeviceFeature.BatteryOperated;
85221
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
85222
+ 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.";
85223
+ 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.";
85224
+ var ALEXA_REASON = "Alexa has no laundry category in this export, and neither power nor running has an Alexa interface.";
85225
+ var GOOGLE_REASON = "Google WASHER, DRYER and DISHWASHER require RunCycle. A power threshold is not that trait, and this export does not publish RunCycle.";
85226
+ 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.";
85227
+ function withheld(reason) {
85228
+ return {
85229
+ [DeviceType.Washer]: {
85230
+ exported: false,
85231
+ reason
85232
+ },
85233
+ [DeviceType.Dryer]: {
85234
+ exported: false,
85235
+ reason
85236
+ },
85237
+ [DeviceType.Dishwasher]: {
85238
+ exported: false,
85239
+ reason
85240
+ }
85241
+ };
85242
+ }
85243
+ function offered(received) {
85244
+ return {
85245
+ [DeviceType.Washer]: {
85246
+ exported: true,
85247
+ as: received
85248
+ },
85249
+ [DeviceType.Dryer]: {
85250
+ exported: true,
85251
+ as: received
85252
+ },
85253
+ [DeviceType.Dishwasher]: {
85254
+ exported: true,
85255
+ as: received
85256
+ }
85257
+ };
85258
+ }
85259
+ offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
84634
85260
  Object.freeze({
84635
85261
  "accessories.getStatus": [{
84636
85262
  name: "deviceId",
@@ -85597,6 +86223,11 @@ Object.freeze({
85597
86223
  form: "single",
85598
86224
  optional: false
85599
86225
  }],
86226
+ "groupActive.getStatus": [{
86227
+ name: "deviceId",
86228
+ form: "single",
86229
+ optional: false
86230
+ }],
85600
86231
  "groupCommands.getStatus": [{
85601
86232
  name: "deviceId",
85602
86233
  form: "single",
@@ -85807,6 +86438,11 @@ Object.freeze({
85807
86438
  form: "single",
85808
86439
  optional: false
85809
86440
  }],
86441
+ "meter.getStatus": [{
86442
+ name: "deviceId",
86443
+ form: "single",
86444
+ optional: false
86445
+ }],
85810
86446
  "motion.getStatus": [{
85811
86447
  name: "deviceId",
85812
86448
  form: "single",
@@ -87414,7 +88050,6 @@ Object.freeze({
87414
88050
  "network-access": "ingress",
87415
88051
  "smtp-provider": "email"
87416
88052
  });
87417
- DeviceType.Camera;
87418
88053
  /**
87419
88054
  * A saved Customize assignment (D936). The schema is the type: callers use
87420
88055
  * `z.infer`, they do not restate the shape.