@camstack/addon-provider-amcrest 0.2.161 → 0.2.162

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -5478,7 +5478,7 @@ var ZodIssueCode = {
5478
5478
  var ZodFirstPartyTypeKind;
5479
5479
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5480
5480
  //#endregion
5481
- //#region ../types/dist/sleep-bykKrTgu.mjs
5481
+ //#region ../types/dist/sleep--jXS9hWo.mjs
5482
5482
  /**
5483
5483
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5484
5484
  * window to float samples (D455).
@@ -8671,6 +8671,7 @@ var CAP_NAMES = Object.freeze([
8671
8671
  "filesystem-browse",
8672
8672
  "flood",
8673
8673
  "gas",
8674
+ "group-active",
8674
8675
  "group-commands",
8675
8676
  "group-status",
8676
8677
  "heating-valve",
@@ -8693,6 +8694,7 @@ var CAP_NAMES = Object.freeze([
8693
8694
  "login-method",
8694
8695
  "media-player",
8695
8696
  "mesh-network",
8697
+ "meter",
8696
8698
  "metrics-provider",
8697
8699
  "model-convert",
8698
8700
  "model-distributor",
@@ -9491,6 +9493,10 @@ Object.freeze({
9491
9493
  "path": "detected",
9492
9494
  "valueType": "boolean"
9493
9495
  }],
9496
+ "group-active": [{
9497
+ "path": "active",
9498
+ "valueType": "boolean"
9499
+ }],
9494
9500
  "group-status": [
9495
9501
  {
9496
9502
  "path": "included",
@@ -9750,6 +9756,10 @@ Object.freeze({
9750
9756
  ]
9751
9757
  }
9752
9758
  ],
9759
+ "meter": [{
9760
+ "path": "value",
9761
+ "valueType": "number"
9762
+ }],
9753
9763
  "motion": [
9754
9764
  {
9755
9765
  "path": "detected",
@@ -10507,7 +10517,7 @@ function userError(code, params, englishFallback) {
10507
10517
  return new CamstackError(code, params, englishFallback);
10508
10518
  }
10509
10519
  //#endregion
10510
- //#region ../types/dist/err-msg-BXKzRBwg.mjs
10520
+ //#region ../types/dist/err-msg-DIorGYeC.mjs
10511
10521
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10512
10522
  DeviceType["Camera"] = "camera";
10513
10523
  DeviceType["Hub"] = "hub";
@@ -10601,8 +10611,14 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
10601
10611
  DeviceType["PetFeeder"] = "pet-feeder";
10602
10612
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10603
10613
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10604
- * Sources: native Bosch Home Connect, reusable by similar integrations. */
10614
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
10615
+ * A plug-built dishwasher carries running-state and power instead. */
10605
10616
  DeviceType["Dishwasher"] = "dishwasher";
10617
+ /** Washing machine. A plug-built one is running while its power stays
10618
+ * above a threshold. No program cap — that surface is the dishwasher's. */
10619
+ DeviceType["Washer"] = "washer";
10620
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
10621
+ DeviceType["Dryer"] = "dryer";
10606
10622
  return DeviceType;
10607
10623
  }({});
10608
10624
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -10850,7 +10866,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
10850
10866
  return DeviceRole;
10851
10867
  }({});
10852
10868
  //#endregion
10853
- //#region ../types/dist/composition-OhpuespA.mjs
10869
+ //#region ../types/dist/composition-Bb8iwHEJ.mjs
10854
10870
  /**
10855
10871
  * The ffmpeg build every CamStack node downloads, per platform and arch.
10856
10872
  *
@@ -11165,6 +11181,13 @@ var table = {
11165
11181
  apply: () => {
11166
11182
  throw new ExpressionEvalError("latchedAt: stateful evaluation context is required");
11167
11183
  }
11184
+ },
11185
+ schmitt: {
11186
+ minArgs: 3,
11187
+ maxArgs: 5,
11188
+ apply: () => {
11189
+ throw new ExpressionEvalError("schmitt: stateful evaluation context is required");
11190
+ }
11168
11191
  }
11169
11192
  };
11170
11193
  Object.freeze(Object.assign(Object.create(null), table));
@@ -11606,6 +11629,99 @@ function validateExpressionSource(src) {
11606
11629
  return null;
11607
11630
  }
11608
11631
  /**
11632
+ * One power reading becomes a washer, dryer or dishwasher (D944 helper).
11633
+ *
11634
+ * Standby of the control board is under 5 W; a motor or a heater is far
11635
+ * above that, so 5 W is the line. A door lock or a short pump spike is not
11636
+ * a cycle, so running starts only after 30 s above the line.
11637
+ *
11638
+ * A wash, dry or dishwasher pause (soak, heat-wait, heater off between
11639
+ * bursts) lasts several minutes. Ending the cycle on the first dip would
11640
+ * report "finished" in the middle of the program, so the stop dwell is
11641
+ * 10 minutes on every type. The number is stored per type so one machine
11642
+ * can move later without moving the others.
11643
+ */
11644
+ var APPLIANCE_TEMPLATE_ID = "appliance";
11645
+ var APPLIANCE_KINDS = [
11646
+ "washer",
11647
+ "dryer",
11648
+ "dishwasher"
11649
+ ];
11650
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
11651
+ /**
11652
+ * Statistics and integrals over a history read (D944).
11653
+ *
11654
+ * Pure: the same points always give the same answer, so a restart recomputes
11655
+ * rather than trusting a buffer. A gauge mean is time-weighted (value × ms),
11656
+ * never the mean of the samples. A hole with no held value is not integrated
11657
+ * as zero. A truncated read is not published.
11658
+ */
11659
+ var STATISTIC_STATS = [
11660
+ "min",
11661
+ "max",
11662
+ "mean",
11663
+ "sum",
11664
+ "count",
11665
+ "change"
11666
+ ];
11667
+ /**
11668
+ * The range a statistics or integral helper asks history for (D944).
11669
+ *
11670
+ * Calendar edges are the hub's timezone, never UTC midnight: a day is 23, 24
11671
+ * or 25 hours across a DST change. A tier is eligible only when its retention
11672
+ * is strictly longer than the longest that range can be. Forever qualifies.
11673
+ * Equal to a tier's retention is a refusal, not a short answer.
11674
+ */
11675
+ var MINUTE_MS = 6e4;
11676
+ var HOUR_MS = 60 * MINUTE_MS;
11677
+ var DAY_MS = 24 * HOUR_MS;
11678
+ var ROLLING_SPANS = [
11679
+ "1h",
11680
+ "24h",
11681
+ "2d",
11682
+ "7d",
11683
+ "30d",
11684
+ "custom"
11685
+ ];
11686
+ var CALENDAR_PERIODS = [
11687
+ "today",
11688
+ "yesterday",
11689
+ "this-week",
11690
+ "this-month"
11691
+ ];
11692
+ var RESET_PERIODS = [
11693
+ "never",
11694
+ "daily",
11695
+ "weekly",
11696
+ "monthly"
11697
+ ];
11698
+ 5 * MINUTE_MS;
11699
+ 24 * HOUR_MS, 2 * DAY_MS, 7 * DAY_MS, 30 * DAY_MS;
11700
+ 7 * DAY_MS, 30 * DAY_MS, 365 * DAY_MS;
11701
+ var rollingWindowObject = object({
11702
+ kind: literal("rolling"),
11703
+ span: _enum(ROLLING_SPANS),
11704
+ customMinutes: number().int().min(5).optional()
11705
+ }).check((payload) => {
11706
+ if (payload.value.span === "custom" && payload.value.customMinutes === void 0) payload.issues.push({
11707
+ code: "custom",
11708
+ message: "a custom range needs a duration in minutes",
11709
+ path: ["customMinutes"],
11710
+ input: payload.value
11711
+ });
11712
+ if (payload.value.span !== "custom" && payload.value.customMinutes !== void 0) payload.issues.push({
11713
+ code: "custom",
11714
+ message: "only a custom range carries a duration",
11715
+ path: ["customMinutes"],
11716
+ input: payload.value
11717
+ });
11718
+ });
11719
+ var RollingWindowSchema = rollingWindowObject;
11720
+ var StatisticWindowSchema = discriminatedUnion("kind", [rollingWindowObject, object({
11721
+ kind: literal("calendar"),
11722
+ period: _enum(CALENDAR_PERIODS)
11723
+ })]);
11724
+ /**
11609
11725
  * A composition source identity: `(addonId, stableId)`, never a numeric device
11610
11726
  * id. Split out so helper schemas can name it without a cycle through
11611
11727
  * `composition.ts` (D659).
@@ -11631,7 +11747,13 @@ var GROUP_TEMPLATE_IDS = [
11631
11747
  "occupancy",
11632
11748
  "binary",
11633
11749
  "switch",
11634
- "light"
11750
+ "light",
11751
+ "fan",
11752
+ "cover",
11753
+ "valve",
11754
+ "lock",
11755
+ "media-player",
11756
+ "climate"
11635
11757
  ];
11636
11758
  var NUMERIC_AGGREGATES = [
11637
11759
  "mean",
@@ -11661,14 +11783,73 @@ var HelperMemberSchema = object({
11661
11783
  cap: string().min(1),
11662
11784
  fieldPath: string().min(1)
11663
11785
  });
11664
- var HelperSpecSchema = discriminatedUnion("kind", [object({
11665
- kind: literal("group"),
11666
- templateId: GroupTemplateIdSchema,
11667
- aggregate: AggregateReducerSchema,
11668
- members: array(HelperMemberSchema).min(1).max(64),
11669
- /** Actuator groups only. Readings leave this unset. */
11670
- active: ActiveCriterionSchema.optional()
11671
- })]);
11786
+ var HelperSpecSchema = discriminatedUnion("kind", [
11787
+ object({
11788
+ kind: literal("group"),
11789
+ templateId: GroupTemplateIdSchema,
11790
+ aggregate: AggregateReducerSchema,
11791
+ members: array(HelperMemberSchema).min(1).max(64),
11792
+ /** Actuator groups only. Readings leave this unset. */
11793
+ active: ActiveCriterionSchema.optional()
11794
+ }),
11795
+ object({
11796
+ kind: literal("appliance"),
11797
+ templateId: literal(APPLIANCE_TEMPLATE_ID),
11798
+ appliance: _enum(APPLIANCE_KINDS),
11799
+ thresholdWatts: number().positive(),
11800
+ startDwellMs: number().int().nonnegative(),
11801
+ stopDwellMs: number().int().nonnegative(),
11802
+ member: HelperMemberSchema
11803
+ }),
11804
+ object({
11805
+ kind: literal("statistic"),
11806
+ stat: _enum(STATISTIC_STATS),
11807
+ window: StatisticWindowSchema,
11808
+ source: HelperMemberSchema
11809
+ }),
11810
+ object({
11811
+ kind: literal("integral"),
11812
+ reset: _enum(RESET_PERIODS),
11813
+ output: _enum(["energy", "meter"]),
11814
+ source: HelperMemberSchema
11815
+ }),
11816
+ object({
11817
+ kind: literal("threshold"),
11818
+ source: HelperMemberSchema,
11819
+ high: number(),
11820
+ low: number(),
11821
+ onForMs: number().int().nonnegative(),
11822
+ offForMs: number().int().nonnegative()
11823
+ }),
11824
+ object({
11825
+ kind: literal("arithmetic"),
11826
+ op: _enum([
11827
+ "sum",
11828
+ "difference",
11829
+ "ratio"
11830
+ ]),
11831
+ inputs: array(HelperMemberSchema).min(2).max(64)
11832
+ }),
11833
+ object({
11834
+ kind: literal("expression"),
11835
+ expr: string().min(1),
11836
+ inputs: array(object({
11837
+ name: string().min(1),
11838
+ member: HelperMemberSchema
11839
+ })).min(1).max(64),
11840
+ output: _enum(["number", "boolean"])
11841
+ }),
11842
+ object({
11843
+ kind: literal("derivative"),
11844
+ window: RollingWindowSchema,
11845
+ per: _enum([
11846
+ "second",
11847
+ "minute",
11848
+ "hour"
11849
+ ]),
11850
+ source: HelperMemberSchema
11851
+ })
11852
+ ]);
11672
11853
  /** A composed device's stableId is this prefix plus the block id. */
11673
11854
  var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11674
11855
  /** Longest snippet a `code` source may carry (slice 4). */
@@ -11729,6 +11910,12 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11729
11910
  "all"
11730
11911
  ]),
11731
11912
  reads: array(CompositionFieldReadSchema).min(1).max(64),
11913
+ /**
11914
+ * Set when the reads are an enum state, not a yes/no: the listed states
11915
+ * count as active (a cover is `open`). A state outside the list is inactive;
11916
+ * a read that is not a state at all is left out. Only `any` / `all` use it.
11917
+ */
11918
+ activeWhen: array(string().min(1)).min(1).optional(),
11732
11919
  /** `value` is the reading; the others are the member counts. */
11733
11920
  publish: _enum([
11734
11921
  "value",
@@ -11742,6 +11929,26 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11742
11929
  "omit",
11743
11930
  "offline"
11744
11931
  ])
11932
+ }),
11933
+ object({
11934
+ kind: literal("series"),
11935
+ read: CompositionFieldReadSchema,
11936
+ /** `value` is the statistic or the integral. The others are the gap counts. */
11937
+ publish: _enum([
11938
+ "value",
11939
+ "included",
11940
+ "excluded",
11941
+ "total"
11942
+ ]),
11943
+ /**
11944
+ * What an unanswered `value` does. Counts ignore this. `offline` leaves the
11945
+ * device unseeded until history answers, so a restart cannot publish 0.
11946
+ */
11947
+ empty: _enum([
11948
+ "null",
11949
+ "omit",
11950
+ "offline"
11951
+ ])
11745
11952
  })
11746
11953
  ]);
11747
11954
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
@@ -16939,7 +17146,22 @@ var BrokerProviderInfoSchema = object({
16939
17146
  */
16940
17147
  interactiveConnectPath: string().min(1).optional(),
16941
17148
  /** Relative hub path to disconnect the linked account (same popup pattern). */
16942
- interactiveDisconnectPath: string().min(1).optional()
17149
+ interactiveDisconnectPath: string().min(1).optional(),
17150
+ /**
17151
+ * The kind signs in through a server-held interactive session instead of
17152
+ * (or before) typed credentials. `basePath` is a hub-relative route set
17153
+ * served by the owning addon (`/addon/<id>/...`) speaking the sign-in
17154
+ * contract: `POST {basePath}/start` (body: the form's settings) answers
17155
+ * `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
17156
+ * `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
17157
+ * 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
17158
+ * (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
17159
+ * existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
17160
+ * (`{ sessionId }`) drops the session. The dialog that edits a broker
17161
+ * offers it as the default path; the fields the session fills are
17162
+ * marked `filledBySignIn` in the kind's settings schema.
17163
+ */
17164
+ signIn: object({ basePath: string().min(1) }).optional()
16943
17165
  }))
16944
17166
  });
16945
17167
  var ListInputSchema = object({
@@ -18044,7 +18266,8 @@ var CompositionProblemSchema = object({
18044
18266
  "native-capability-replace",
18045
18267
  "mixed-quantity",
18046
18268
  "helper-mismatch",
18047
- "member-constraint"
18269
+ "member-constraint",
18270
+ "retention"
18048
18271
  ]),
18049
18272
  path: string(),
18050
18273
  message: string()
@@ -18278,13 +18501,17 @@ Object.freeze({
18278
18501
  latchedAt: {
18279
18502
  kind: "fixed",
18280
18503
  result: ["number", "null"]
18504
+ },
18505
+ schmitt: {
18506
+ kind: "fixed",
18507
+ result: ["boolean", "null"]
18281
18508
  }
18282
18509
  });
18283
18510
  /**
18284
18511
  * One catalog row per base reading a group helper can build (D944 §3).
18285
18512
  * Data, not a form default written into the block.
18286
18513
  */
18287
- var NOW = {
18514
+ var NOW$2 = {
18288
18515
  fieldPath: "lastFetchedAt",
18289
18516
  expr: "now"
18290
18517
  };
@@ -18298,6 +18525,8 @@ function numeric(input) {
18298
18525
  family: "numeric",
18299
18526
  deviceType: DeviceType.Sensor,
18300
18527
  memberDeviceType: DeviceType.Sensor,
18528
+ memberDeviceTypes: [DeviceType.Sensor],
18529
+ activeWhen: void 0,
18301
18530
  commandCaps: [],
18302
18531
  baseUnit: baseUnitOf(input.quantity),
18303
18532
  aggregates: NUMERIC_AGGREGATES,
@@ -18313,6 +18542,8 @@ function binary(input) {
18313
18542
  family: "binary",
18314
18543
  deviceType: DeviceType.Sensor,
18315
18544
  memberDeviceType: DeviceType.Sensor,
18545
+ memberDeviceTypes: [DeviceType.Sensor],
18546
+ activeWhen: void 0,
18316
18547
  commandCaps: [],
18317
18548
  quantity: "none",
18318
18549
  baseUnit: void 0,
@@ -18330,7 +18561,7 @@ var GROUP_TEMPLATES = [
18330
18561
  quantity: "temperature",
18331
18562
  defaultAggregate: "mean",
18332
18563
  empty: "null",
18333
- companions: [NOW]
18564
+ companions: [NOW$2]
18334
18565
  }),
18335
18566
  numeric({
18336
18567
  id: "humidity",
@@ -18341,7 +18572,7 @@ var GROUP_TEMPLATES = [
18341
18572
  quantity: "humidity",
18342
18573
  defaultAggregate: "mean",
18343
18574
  empty: "null",
18344
- companions: [NOW]
18575
+ companions: [NOW$2]
18345
18576
  }),
18346
18577
  numeric({
18347
18578
  id: "pressure",
@@ -18352,7 +18583,7 @@ var GROUP_TEMPLATES = [
18352
18583
  quantity: "pressure",
18353
18584
  defaultAggregate: "mean",
18354
18585
  empty: "null",
18355
- companions: [NOW]
18586
+ companions: [NOW$2]
18356
18587
  }),
18357
18588
  numeric({
18358
18589
  id: "illuminance",
@@ -18363,7 +18594,7 @@ var GROUP_TEMPLATES = [
18363
18594
  quantity: "illuminance",
18364
18595
  defaultAggregate: "mean",
18365
18596
  empty: "null",
18366
- companions: [NOW]
18597
+ companions: [NOW$2]
18367
18598
  }),
18368
18599
  numeric({
18369
18600
  id: "power",
@@ -18374,7 +18605,7 @@ var GROUP_TEMPLATES = [
18374
18605
  quantity: "power",
18375
18606
  defaultAggregate: "sum",
18376
18607
  empty: "omit",
18377
- companions: [NOW]
18608
+ companions: [NOW$2]
18378
18609
  }),
18379
18610
  numeric({
18380
18611
  id: "energy",
@@ -18385,7 +18616,7 @@ var GROUP_TEMPLATES = [
18385
18616
  quantity: "energy",
18386
18617
  defaultAggregate: "sum",
18387
18618
  empty: "omit",
18388
- companions: [NOW]
18619
+ companions: [NOW$2]
18389
18620
  }),
18390
18621
  numeric({
18391
18622
  id: "battery",
@@ -18420,7 +18651,7 @@ var GROUP_TEMPLATES = [
18420
18651
  quantity: "device",
18421
18652
  defaultAggregate: "mean",
18422
18653
  empty: "null",
18423
- companions: [NOW]
18654
+ companions: [NOW$2]
18424
18655
  }),
18425
18656
  binary({
18426
18657
  id: "motion",
@@ -18514,6 +18745,11 @@ var GROUP_TEMPLATES = [
18514
18745
  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.",
18515
18746
  deviceType: DeviceType.Switch,
18516
18747
  outputCap: "switch",
18748
+ outputField: "on",
18749
+ member: {
18750
+ cap: "switch",
18751
+ fieldPath: "on"
18752
+ },
18517
18753
  commandCaps: ["switch"]
18518
18754
  }),
18519
18755
  actuator({
@@ -18521,34 +18757,136 @@ var GROUP_TEMPLATES = [
18521
18757
  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.",
18522
18758
  deviceType: DeviceType.Light,
18523
18759
  outputCap: "switch",
18760
+ outputField: "on",
18761
+ member: {
18762
+ cap: "switch",
18763
+ fieldPath: "on"
18764
+ },
18524
18765
  commandCaps: [
18525
18766
  "switch",
18526
18767
  "brightness",
18527
18768
  "color"
18528
18769
  ]
18770
+ }),
18771
+ actuator({
18772
+ id: "fan",
18773
+ 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.",
18774
+ deviceType: DeviceType.Fan,
18775
+ outputCap: "switch",
18776
+ outputField: "on",
18777
+ member: {
18778
+ cap: "switch",
18779
+ fieldPath: "on"
18780
+ },
18781
+ commandCaps: ["switch", "fan-control"]
18782
+ }),
18783
+ actuator({
18784
+ id: "cover",
18785
+ 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.",
18786
+ deviceType: DeviceType.Cover,
18787
+ outputCap: "group-active",
18788
+ outputField: "active",
18789
+ member: {
18790
+ cap: "cover",
18791
+ fieldPath: "state"
18792
+ },
18793
+ activeWhen: [
18794
+ "open",
18795
+ "opening",
18796
+ "stopped"
18797
+ ],
18798
+ commandCaps: ["cover"]
18799
+ }),
18800
+ actuator({
18801
+ id: "valve",
18802
+ 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.",
18803
+ deviceType: DeviceType.Valve,
18804
+ outputCap: "group-active",
18805
+ outputField: "active",
18806
+ member: {
18807
+ cap: "valve",
18808
+ fieldPath: "state"
18809
+ },
18810
+ activeWhen: [
18811
+ "open",
18812
+ "opening",
18813
+ "stopped"
18814
+ ],
18815
+ commandCaps: ["valve"]
18816
+ }),
18817
+ actuator({
18818
+ id: "lock",
18819
+ 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.",
18820
+ deviceType: DeviceType.Lock,
18821
+ outputCap: "group-active",
18822
+ outputField: "active",
18823
+ member: {
18824
+ cap: "lock-control",
18825
+ fieldPath: "state"
18826
+ },
18827
+ activeWhen: ["locked"],
18828
+ commandCaps: ["lock-control"]
18829
+ }),
18830
+ actuator({
18831
+ id: "media-player",
18832
+ 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.",
18833
+ deviceType: DeviceType.MediaPlayer,
18834
+ outputCap: "group-active",
18835
+ outputField: "active",
18836
+ member: {
18837
+ cap: "media-player",
18838
+ fieldPath: "state"
18839
+ },
18840
+ activeWhen: ["playing", "buffering"],
18841
+ commandCaps: ["media-player"]
18842
+ }),
18843
+ actuator({
18844
+ id: "climate",
18845
+ 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.",
18846
+ deviceType: DeviceType.Thermostat,
18847
+ memberDeviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
18848
+ outputCap: "group-active",
18849
+ outputField: "active",
18850
+ member: {
18851
+ cap: "climate-control",
18852
+ fieldPath: "mode"
18853
+ },
18854
+ activeWhen: [
18855
+ "heat",
18856
+ "cool",
18857
+ "auto",
18858
+ "heat_cool",
18859
+ "fan_only",
18860
+ "dry"
18861
+ ],
18862
+ commandCaps: ["climate-control"]
18529
18863
  })
18530
18864
  ];
18531
18865
  function actuator(input) {
18532
18866
  return {
18533
- ...input,
18867
+ id: input.id,
18868
+ summary: input.summary,
18869
+ deviceType: input.deviceType,
18870
+ outputCap: input.outputCap,
18871
+ outputField: input.outputField,
18872
+ commandCaps: input.commandCaps,
18534
18873
  family: "actuator",
18535
18874
  role: void 0,
18536
18875
  memberDeviceType: input.deviceType,
18537
- outputField: "on",
18876
+ memberDeviceTypes: input.memberDeviceTypes ?? [input.deviceType],
18877
+ activeWhen: input.activeWhen,
18538
18878
  quantity: "none",
18539
18879
  baseUnit: void 0,
18540
18880
  aggregates: BINARY_AGGREGATES,
18541
18881
  defaultAggregate: "any",
18542
18882
  empty: "offline",
18543
- accept: [{
18544
- cap: "switch",
18545
- fieldPath: "on"
18546
- }],
18883
+ accept: [input.member],
18547
18884
  companions: [CHANGED]
18548
18885
  };
18549
18886
  }
18550
18887
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18551
18888
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18889
+ DeviceType.Camera;
18552
18890
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18553
18891
  /**
18554
18892
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -43868,7 +44206,7 @@ var NetworkAccessStatusSchema = object({
43868
44206
  });
43869
44207
  /**
43870
44208
  * Optional, richer endpoint shape returned by providers that expose
43871
- * MORE than one ingress concurrently (Tailscale Ingress with mixed
44209
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
43872
44210
  * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
43873
44211
  * the originating provider config (mode + sourcePort) so the
43874
44212
  * orchestrator UI can label rows distinctly. Providers that expose only
@@ -46555,7 +46893,20 @@ var NcRuleInputSchema = object({
46555
46893
  * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
46556
46894
  * path does not run Zod defaults.
46557
46895
  */
46558
- groupingSec: number().int().min(5).max(60).optional()
46896
+ groupingSec: number().int().min(5).max(60).optional(),
46897
+ /**
46898
+ * ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
46899
+ * carries "Correct" / "Wrong" buttons (and, where the channel has room, a
46900
+ * "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
46901
+ * same mark the Library writes, and records whose notification it was and
46902
+ * when. A channel with no buttons gets the same two choices as links.
46903
+ *
46904
+ * `.optional()`, never `.default()` (a Zod default does not run on the addon
46905
+ * cap path): absent and `false` both mean off, which is what every rule
46906
+ * authored before this field meant. An editor turns it off by writing
46907
+ * `false`, so it needs no `clear` key.
46908
+ */
46909
+ askVerdict: boolean().optional()
46559
46910
  });
46560
46911
  /**
46561
46912
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -57788,7 +58139,8 @@ var BATTERY_DEVICE_TYPES = [
57788
58139
  DeviceType.Switch,
57789
58140
  DeviceType.PetFeeder,
57790
58141
  DeviceType.Container,
57791
- DeviceType.Thermostat
58142
+ DeviceType.Thermostat,
58143
+ DeviceType.EventEmitter
57792
58144
  ];
57793
58145
  /** Generic Customize target for the percentage reading (D936). */
57794
58146
  var batteryCustomize = {
@@ -60648,7 +61000,17 @@ var groupCommandsCapability = {
60648
61000
  scope: "device",
60649
61001
  deviceNative: true,
60650
61002
  mode: "singleton",
60651
- deviceTypes: [DeviceType.Switch, DeviceType.Light],
61003
+ deviceTypes: [
61004
+ DeviceType.Switch,
61005
+ DeviceType.Light,
61006
+ DeviceType.Fan,
61007
+ DeviceType.Cover,
61008
+ DeviceType.Valve,
61009
+ DeviceType.Lock,
61010
+ DeviceType.MediaPlayer,
61011
+ DeviceType.Thermostat,
61012
+ DeviceType.Climate
61013
+ ],
60652
61014
  methods: {},
60653
61015
  status: {
60654
61016
  schema: GroupCommandsSchema,
@@ -60666,6 +61028,49 @@ var groupCommandsCapability = {
60666
61028
  history: "none"
60667
61029
  };
60668
61030
  /**
61031
+ * Whether a helper actuator group counts as active (D944): at least one
61032
+ * included member is, or every included member is. What "active" means is the
61033
+ * type's: a cover is open, a lock is locked, a media player is playing, a
61034
+ * climate device is not off. A group of lights, switches and fans reports
61035
+ * `switch.on` instead; this cap is for the types whose own state is not a
61036
+ * yes/no. Unknown members are left out (`group-status` counts them) and an
61037
+ * empty included set takes the device offline, never `active: false`.
61038
+ */
61039
+ var GroupActiveSchema = object({
61040
+ active: boolean().meta({ label: "Active" }),
61041
+ /** Ms epoch when the slice was last updated. */
61042
+ lastChangedAt: number()
61043
+ });
61044
+ var groupActiveCapability = {
61045
+ name: "group-active",
61046
+ scope: "device",
61047
+ deviceNative: true,
61048
+ mode: "singleton",
61049
+ deviceTypes: [
61050
+ DeviceType.Cover,
61051
+ DeviceType.Valve,
61052
+ DeviceType.Lock,
61053
+ DeviceType.MediaPlayer,
61054
+ DeviceType.Climate,
61055
+ DeviceType.Thermostat
61056
+ ],
61057
+ methods: {},
61058
+ status: {
61059
+ schema: GroupActiveSchema,
61060
+ kind: "push"
61061
+ },
61062
+ runtimeState: GroupActiveSchema,
61063
+ /**
61064
+ * Runtime-state durability: **session** — recomputed from the members on the
61065
+ * next source event; a restored value would outlive the members it named.
61066
+ *
61067
+ * See `RuntimeStateDurability`. Enforced by
61068
+ * `scripts/check-runtime-state-durability.ts`.
61069
+ */
61070
+ durability: "session",
61071
+ history: "none"
61072
+ };
61073
+ /**
60669
61074
  * How many members of a helper group answered (D944). Counts are integers,
60670
61075
  * not measurements: zero included is a real count, never a stand-in reading.
60671
61076
  * The reading itself stays on the output capability. History is none — the
@@ -60696,7 +61101,14 @@ var groupStatusCapability = {
60696
61101
  deviceTypes: [
60697
61102
  DeviceType.Sensor,
60698
61103
  DeviceType.Switch,
60699
- DeviceType.Light
61104
+ DeviceType.Light,
61105
+ DeviceType.Fan,
61106
+ DeviceType.Cover,
61107
+ DeviceType.Valve,
61108
+ DeviceType.Lock,
61109
+ DeviceType.MediaPlayer,
61110
+ DeviceType.Thermostat,
61111
+ DeviceType.Climate
60700
61112
  ],
60701
61113
  methods: {},
60702
61114
  status: {
@@ -61969,7 +62381,14 @@ var lockControlCapability = {
61969
62381
  }),
61970
62382
  unlock: method(object({
61971
62383
  deviceId: number().int().nonnegative(),
61972
- code: string().min(1).optional()
62384
+ code: string().min(1).optional(),
62385
+ /**
62386
+ * Honoured by a helper lock GROUP only (D944): unlocking several doors
62387
+ * with one call is refused unless the caller says it was confirmed. A
62388
+ * single lock ignores it. The refusal lives in the callee, so no
62389
+ * automation or API caller reaches the doors by skipping a dialog.
62390
+ */
62391
+ confirm: boolean().optional()
61973
62392
  }), _void(), {
61974
62393
  kind: "mutation",
61975
62394
  auth: "protected"
@@ -62006,6 +62425,44 @@ var lockControlCapability = {
62006
62425
  volatileStateFields: ["lastChangedAt"]
62007
62426
  };
62008
62427
  /**
62428
+ * A monotonic total between resets (D944). Energy uses `power-meter.kwhTotal`
62429
+ * so dashboards keep one series; every other instantaneous reading lands here.
62430
+ * `value` is the time integral in the source quantity's accumulated base
62431
+ * (base-unit seconds, or kWh on the power meter). `null` is a gap, never zero.
62432
+ */
62433
+ var MeterStatusSchema = object({
62434
+ value: number().nullable().meta(readingMeta("device", { label: "Total" })),
62435
+ lastFetchedAt: number()
62436
+ });
62437
+ var meterCapability = {
62438
+ name: "meter",
62439
+ scope: "device",
62440
+ deviceNative: true,
62441
+ mode: "singleton",
62442
+ deviceTypes: [DeviceType.Sensor],
62443
+ methods: {},
62444
+ status: {
62445
+ schema: MeterStatusSchema,
62446
+ kind: "push"
62447
+ },
62448
+ runtimeState: MeterStatusSchema,
62449
+ /**
62450
+ * Runtime-state durability: **restored** — the total is a cache of the
62451
+ * history integral. A restart shows it only until the history pass
62452
+ * recomputes the open period; it is not an in-memory buffer.
62453
+ *
62454
+ * See `RuntimeStateDurability`. Enforced by
62455
+ * `scripts/check-runtime-state-durability.ts`.
62456
+ */
62457
+ durability: "restored",
62458
+ history: { fields: { value: {
62459
+ kind: "counter",
62460
+ deadband: .001,
62461
+ minIntervalMs: 6e4
62462
+ } } },
62463
+ volatileStateFields: ["lastFetchedAt"]
62464
+ };
62465
+ /**
62009
62466
  * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
62010
62467
  * Apple TV, Spotify, Roku, generic OTT receivers, …) on
62011
62468
  * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
@@ -64563,9 +65020,9 @@ var PowerMeterStatusSchema = object({
64563
65020
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
64564
65021
  kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
64565
65022
  /** Voltage in volts. */
64566
- volts: number().optional().meta(readingMeta("voltage")),
65023
+ volts: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
64567
65024
  /** Current in amperes. */
64568
- amps: number().optional().meta(readingMeta("current")),
65025
+ amps: number().optional().meta(readingMeta("current", { label: "Current" })),
64569
65026
  /** Apparent power in volt-amperes. */
64570
65027
  va: number().optional().meta(readingMeta("apparent-power")),
64571
65028
  /** Power factor (0..1) if reported. */
@@ -64573,12 +65030,74 @@ var PowerMeterStatusSchema = object({
64573
65030
  /** Ms epoch when the slice was last updated. */
64574
65031
  lastFetchedAt: number()
64575
65032
  });
65033
+ /**
65034
+ * Loads that draw metered mains power (D951). The reading is a feature of the
65035
+ * device itself — a plug, a lamp, a fan, an air conditioner, a boiler, a
65036
+ * vacuum dock — not a separate sensor beside it. A `Sensor` stays: a clamp
65037
+ * meter or a standalone energy monitor IS the device. Cover, lock, valve,
65038
+ * camera and the like are left out: their draw is not what anyone meters.
65039
+ */
65040
+ var POWER_METER_DEVICE_TYPES = [
65041
+ DeviceType.Sensor,
65042
+ DeviceType.Switch,
65043
+ DeviceType.Light,
65044
+ DeviceType.Fan,
65045
+ DeviceType.Climate,
65046
+ DeviceType.Thermostat,
65047
+ DeviceType.Humidifier,
65048
+ DeviceType.WaterHeater,
65049
+ DeviceType.MediaPlayer,
65050
+ DeviceType.Washer,
65051
+ DeviceType.Dryer,
65052
+ DeviceType.Dishwasher,
65053
+ DeviceType.Vacuum,
65054
+ DeviceType.LawnMower,
65055
+ DeviceType.PetFeeder
65056
+ ];
65057
+ /** Everything but a bare `Sensor`: a sensor that takes its reading from another sensor adds nothing. */
65058
+ var POWER_METER_CUSTOMIZE_DEVICE_TYPES = POWER_METER_DEVICE_TYPES.filter((type) => type !== DeviceType.Sensor);
65059
+ /** One scalar target per reading; each is sourced from a device of its own (D951). */
65060
+ function powerTarget(id, unit) {
65061
+ return {
65062
+ id,
65063
+ cardinality: "scalar",
65064
+ vocabulary: {
65065
+ cap: "power-meter",
65066
+ stateField: id
65067
+ },
65068
+ deviceTypes: POWER_METER_CUSTOMIZE_DEVICE_TYPES,
65069
+ apply: "add",
65070
+ path: id,
65071
+ sources: [{
65072
+ kind: "numeric",
65073
+ requiredUnit: unit,
65074
+ transforms: [{ kind: "identity" }]
65075
+ }],
65076
+ companions: [{
65077
+ kind: "clock",
65078
+ path: "lastFetchedAt",
65079
+ when: "always"
65080
+ }],
65081
+ sourceReport: { status: "assignment-status" },
65082
+ advancedExpression: true
65083
+ };
65084
+ }
65085
+ /** "Power and energy" extension: watts, energy, voltage and current from other devices (D951). */
65086
+ var powerMeterCustomize = {
65087
+ version: 1,
65088
+ targets: [
65089
+ powerTarget("watts", "W"),
65090
+ powerTarget("kwhTotal", "kWh"),
65091
+ powerTarget("volts", "V"),
65092
+ powerTarget("amps", "A")
65093
+ ]
65094
+ };
64576
65095
  var powerMeterCapability = {
64577
65096
  name: "power-meter",
64578
65097
  scope: "device",
64579
65098
  deviceNative: true,
64580
65099
  mode: "singleton",
64581
- deviceTypes: [DeviceType.Sensor],
65100
+ deviceTypes: POWER_METER_DEVICE_TYPES,
64582
65101
  methods: {},
64583
65102
  status: {
64584
65103
  schema: PowerMeterStatusSchema,
@@ -64635,7 +65154,8 @@ var powerMeterCapability = {
64635
65154
  } },
64636
65155
  /** Clock fields: written, but excluded from the compare that decides
64637
65156
  * whether persisting is worth a SQLite commit. */
64638
- volatileStateFields: ["lastFetchedAt"]
65157
+ volatileStateFields: ["lastFetchedAt"],
65158
+ customize: powerMeterCustomize
64639
65159
  };
64640
65160
  /**
64641
65161
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -65428,6 +65948,8 @@ var runningCustomize = {
65428
65948
  stateField: "running"
65429
65949
  },
65430
65950
  deviceTypes: [
65951
+ DeviceType.Washer,
65952
+ DeviceType.Dryer,
65431
65953
  DeviceType.Dishwasher,
65432
65954
  DeviceType.Vacuum,
65433
65955
  DeviceType.LawnMower,
@@ -68221,7 +68743,8 @@ var switchCapability = {
68221
68743
  deviceTypes: [
68222
68744
  DeviceType.Switch,
68223
68745
  DeviceType.Siren,
68224
- DeviceType.Light
68746
+ DeviceType.Light,
68747
+ DeviceType.Fan
68225
68748
  ],
68226
68749
  methods: { setState: method(object({
68227
68750
  deviceId: number(),
@@ -70567,8 +71090,9 @@ function withClipEventLabels(provider, deps) {
70567
71090
  listClips
70568
71091
  };
70569
71092
  }
71093
+ DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
70570
71094
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
70571
- 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;
71095
+ 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;
70572
71096
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
70573
71097
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
70574
71098
  new Set(CAP_NAMES);
@@ -70897,6 +71421,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
70897
71421
  featureProbe: featureProbeCapability,
70898
71422
  flood: floodCapability,
70899
71423
  gas: gasCapability,
71424
+ groupActive: groupActiveCapability,
70900
71425
  groupCommands: groupCommandsCapability,
70901
71426
  groupStatus: groupStatusCapability,
70902
71427
  heatingValve: heatingValveCapability,
@@ -70908,6 +71433,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
70908
71433
  lawnMowerControl: lawnMowerControlCapability,
70909
71434
  lockControl: lockControlCapability,
70910
71435
  mediaPlayer: mediaPlayerCapability,
71436
+ meter: meterCapability,
70911
71437
  motion: motionCapability,
70912
71438
  motionTrigger: motionTriggerCapability,
70913
71439
  motionZones: motionZonesCapability,
@@ -72171,7 +72697,7 @@ var BaseDeviceProvider = class extends BaseAddon {
72171
72697
  function widenDeviceConstructor(ctor) {
72172
72698
  return ctor;
72173
72699
  }
72174
- 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;
72700
+ 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;
72175
72701
  new Set(Object.values(DeviceType));
72176
72702
  Object.freeze({
72177
72703
  "accessories.getStatus": {
@@ -75394,6 +75920,13 @@ Object.freeze({
75394
75920
  access: "view",
75395
75921
  role: "viewer"
75396
75922
  },
75923
+ "groupActive.getStatus": {
75924
+ capName: "group-active",
75925
+ capScope: "device",
75926
+ addonId: null,
75927
+ access: "view",
75928
+ role: "viewer"
75929
+ },
75397
75930
  "groupCommands.getStatus": {
75398
75931
  capName: "group-commands",
75399
75932
  capScope: "device",
@@ -76360,6 +76893,13 @@ Object.freeze({
76360
76893
  access: "create",
76361
76894
  role: "admin"
76362
76895
  },
76896
+ "meter.getStatus": {
76897
+ capName: "meter",
76898
+ capScope: "device",
76899
+ addonId: null,
76900
+ access: "view",
76901
+ role: "viewer"
76902
+ },
76363
76903
  "metricsProvider.collectSnapshot": {
76364
76904
  capName: "metrics-provider",
76365
76905
  capScope: "system",
@@ -84458,7 +84998,39 @@ var APPLIANCE_CAP_VOCABULARY = {
84458
84998
  phrase: t("When {device} finishes", "Quando {device} finisce"),
84459
84999
  icon: "square"
84460
85000
  }
84461
- }
85001
+ },
85002
+ overrides: { byType: {
85003
+ [DeviceType.Washer]: {
85004
+ state: { running: {
85005
+ whenTrue: t("When the washing machine starts", "Quando la lavatrice inizia"),
85006
+ whenFalse: t("When the washing machine has finished", "Quando la lavatrice ha finito")
85007
+ } },
85008
+ events: {
85009
+ onStarted: { phrase: t("When the washing machine starts", "Quando la lavatrice inizia") },
85010
+ onFinished: { phrase: t("When the washing machine has finished", "Quando la lavatrice ha finito") }
85011
+ }
85012
+ },
85013
+ [DeviceType.Dryer]: {
85014
+ state: { running: {
85015
+ whenTrue: t("When the dryer starts", "Quando l'asciugatrice inizia"),
85016
+ whenFalse: t("When the dryer has finished", "Quando l'asciugatrice ha finito")
85017
+ } },
85018
+ events: {
85019
+ onStarted: { phrase: t("When the dryer starts", "Quando l'asciugatrice inizia") },
85020
+ onFinished: { phrase: t("When the dryer has finished", "Quando l'asciugatrice ha finito") }
85021
+ }
85022
+ },
85023
+ [DeviceType.Dishwasher]: {
85024
+ state: { running: {
85025
+ whenTrue: t("When the dishwasher starts", "Quando la lavastoviglie inizia"),
85026
+ whenFalse: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito")
85027
+ } },
85028
+ events: {
85029
+ onStarted: { phrase: t("When the dishwasher starts", "Quando la lavastoviglie inizia") },
85030
+ onFinished: { phrase: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito") }
85031
+ }
85032
+ }
85033
+ } }
84462
85034
  },
84463
85035
  update: {
84464
85036
  scope: "device",
@@ -85230,6 +85802,10 @@ var SENSOR_CAP_VOCABULARY = {
85230
85802
  icon: "flame"
85231
85803
  }]
85232
85804
  },
85805
+ "group-active": {
85806
+ scope: "device",
85807
+ noun: t("Group state", "Stato del gruppo")
85808
+ },
85233
85809
  "group-commands": {
85234
85810
  scope: "device",
85235
85811
  noun: t("Group command", "Comando di gruppo")
@@ -85286,6 +85862,17 @@ var SENSOR_CAP_VOCABULARY = {
85286
85862
  }],
85287
85863
  events: { onMotionChanged: { notOperatorFacing: "the `detected` state trigger phrases both edges, from the mirror that survives a dropped event (D49)" } }
85288
85864
  },
85865
+ meter: {
85866
+ scope: "device",
85867
+ noun: t("Meter", "Contatore"),
85868
+ state: [{
85869
+ kind: "numeric",
85870
+ field: "value",
85871
+ quantity: t("the total", "il totale"),
85872
+ label: t("Total", "Totale"),
85873
+ icon: "gauge"
85874
+ }]
85875
+ },
85289
85876
  "numeric-sensor": {
85290
85877
  scope: "device",
85291
85878
  noun: t("Sensor", "Sensore"),
@@ -85758,8 +86345,47 @@ function projectAction(cap, action) {
85758
86345
  };
85759
86346
  }
85760
86347
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
85761
- 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;
86348
+ 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;
85762
86349
  DeviceFeature.BatteryOperated;
86350
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
86351
+ 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.";
86352
+ 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.";
86353
+ var ALEXA_REASON = "Alexa has no laundry category in this export, and neither power nor running has an Alexa interface.";
86354
+ var GOOGLE_REASON = "Google WASHER, DRYER and DISHWASHER require RunCycle. A power threshold is not that trait, and this export does not publish RunCycle.";
86355
+ 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.";
86356
+ function withheld(reason) {
86357
+ return {
86358
+ [DeviceType.Washer]: {
86359
+ exported: false,
86360
+ reason
86361
+ },
86362
+ [DeviceType.Dryer]: {
86363
+ exported: false,
86364
+ reason
86365
+ },
86366
+ [DeviceType.Dishwasher]: {
86367
+ exported: false,
86368
+ reason
86369
+ }
86370
+ };
86371
+ }
86372
+ function offered(received) {
86373
+ return {
86374
+ [DeviceType.Washer]: {
86375
+ exported: true,
86376
+ as: received
86377
+ },
86378
+ [DeviceType.Dryer]: {
86379
+ exported: true,
86380
+ as: received
86381
+ },
86382
+ [DeviceType.Dishwasher]: {
86383
+ exported: true,
86384
+ as: received
86385
+ }
86386
+ };
86387
+ }
86388
+ offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
85763
86389
  /** Reject after `ms`; always clears its own timer. */
85764
86390
  async function withTimeout$1(promise, ms, label) {
85765
86391
  let timer;
@@ -86932,6 +87558,11 @@ Object.freeze({
86932
87558
  form: "single",
86933
87559
  optional: false
86934
87560
  }],
87561
+ "groupActive.getStatus": [{
87562
+ name: "deviceId",
87563
+ form: "single",
87564
+ optional: false
87565
+ }],
86935
87566
  "groupCommands.getStatus": [{
86936
87567
  name: "deviceId",
86937
87568
  form: "single",
@@ -87142,6 +87773,11 @@ Object.freeze({
87142
87773
  form: "single",
87143
87774
  optional: false
87144
87775
  }],
87776
+ "meter.getStatus": [{
87777
+ name: "deviceId",
87778
+ form: "single",
87779
+ optional: false
87780
+ }],
87145
87781
  "motion.getStatus": [{
87146
87782
  name: "deviceId",
87147
87783
  form: "single",
@@ -88749,7 +89385,6 @@ Object.freeze({
88749
89385
  "network-access": "ingress",
88750
89386
  "smtp-provider": "email"
88751
89387
  });
88752
- DeviceType.Camera;
88753
89388
  /**
88754
89389
  * A saved Customize assignment (D936). The schema is the type: callers use
88755
89390
  * `z.infer`, they do not restate the shape.