@camstack/addon-provider-rademacher 0.2.147 → 0.2.148

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
@@ -6426,7 +6426,7 @@ var ZodIssueCode = {
6426
6426
  var ZodFirstPartyTypeKind;
6427
6427
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6428
6428
  //#endregion
6429
- //#region ../types/dist/sleep-bykKrTgu.mjs
6429
+ //#region ../types/dist/sleep--jXS9hWo.mjs
6430
6430
  /**
6431
6431
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6432
6432
  * window to float samples (D455).
@@ -9619,6 +9619,7 @@ var CAP_NAMES = Object.freeze([
9619
9619
  "filesystem-browse",
9620
9620
  "flood",
9621
9621
  "gas",
9622
+ "group-active",
9622
9623
  "group-commands",
9623
9624
  "group-status",
9624
9625
  "heating-valve",
@@ -9641,6 +9642,7 @@ var CAP_NAMES = Object.freeze([
9641
9642
  "login-method",
9642
9643
  "media-player",
9643
9644
  "mesh-network",
9645
+ "meter",
9644
9646
  "metrics-provider",
9645
9647
  "model-convert",
9646
9648
  "model-distributor",
@@ -10439,6 +10441,10 @@ Object.freeze({
10439
10441
  "path": "detected",
10440
10442
  "valueType": "boolean"
10441
10443
  }],
10444
+ "group-active": [{
10445
+ "path": "active",
10446
+ "valueType": "boolean"
10447
+ }],
10442
10448
  "group-status": [
10443
10449
  {
10444
10450
  "path": "included",
@@ -10698,6 +10704,10 @@ Object.freeze({
10698
10704
  ]
10699
10705
  }
10700
10706
  ],
10707
+ "meter": [{
10708
+ "path": "value",
10709
+ "valueType": "number"
10710
+ }],
10701
10711
  "motion": [
10702
10712
  {
10703
10713
  "path": "detected",
@@ -11441,7 +11451,7 @@ object({
11441
11451
  params: record(string(), union([string(), number()])).optional()
11442
11452
  });
11443
11453
  //#endregion
11444
- //#region ../types/dist/err-msg-BXKzRBwg.mjs
11454
+ //#region ../types/dist/err-msg-DIorGYeC.mjs
11445
11455
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
11446
11456
  DeviceType["Camera"] = "camera";
11447
11457
  DeviceType["Hub"] = "hub";
@@ -11535,8 +11545,14 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
11535
11545
  DeviceType["PetFeeder"] = "pet-feeder";
11536
11546
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
11537
11547
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
11538
- * Sources: native Bosch Home Connect, reusable by similar integrations. */
11548
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
11549
+ * A plug-built dishwasher carries running-state and power instead. */
11539
11550
  DeviceType["Dishwasher"] = "dishwasher";
11551
+ /** Washing machine. A plug-built one is running while its power stays
11552
+ * above a threshold. No program cap — that surface is the dishwasher's. */
11553
+ DeviceType["Washer"] = "washer";
11554
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
11555
+ DeviceType["Dryer"] = "dryer";
11540
11556
  return DeviceType;
11541
11557
  }({});
11542
11558
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -11794,7 +11810,7 @@ function errMsg(err) {
11794
11810
  return String(err);
11795
11811
  }
11796
11812
  //#endregion
11797
- //#region ../types/dist/composition-OhpuespA.mjs
11813
+ //#region ../types/dist/composition-Bb8iwHEJ.mjs
11798
11814
  /**
11799
11815
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11800
11816
  *
@@ -12109,6 +12125,13 @@ var table = {
12109
12125
  apply: () => {
12110
12126
  throw new ExpressionEvalError("latchedAt: stateful evaluation context is required");
12111
12127
  }
12128
+ },
12129
+ schmitt: {
12130
+ minArgs: 3,
12131
+ maxArgs: 5,
12132
+ apply: () => {
12133
+ throw new ExpressionEvalError("schmitt: stateful evaluation context is required");
12134
+ }
12112
12135
  }
12113
12136
  };
12114
12137
  Object.freeze(Object.assign(Object.create(null), table));
@@ -12550,6 +12573,99 @@ function validateExpressionSource(src) {
12550
12573
  return null;
12551
12574
  }
12552
12575
  /**
12576
+ * One power reading becomes a washer, dryer or dishwasher (D944 helper).
12577
+ *
12578
+ * Standby of the control board is under 5 W; a motor or a heater is far
12579
+ * above that, so 5 W is the line. A door lock or a short pump spike is not
12580
+ * a cycle, so running starts only after 30 s above the line.
12581
+ *
12582
+ * A wash, dry or dishwasher pause (soak, heat-wait, heater off between
12583
+ * bursts) lasts several minutes. Ending the cycle on the first dip would
12584
+ * report "finished" in the middle of the program, so the stop dwell is
12585
+ * 10 minutes on every type. The number is stored per type so one machine
12586
+ * can move later without moving the others.
12587
+ */
12588
+ var APPLIANCE_TEMPLATE_ID = "appliance";
12589
+ var APPLIANCE_KINDS = [
12590
+ "washer",
12591
+ "dryer",
12592
+ "dishwasher"
12593
+ ];
12594
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
12595
+ /**
12596
+ * Statistics and integrals over a history read (D944).
12597
+ *
12598
+ * Pure: the same points always give the same answer, so a restart recomputes
12599
+ * rather than trusting a buffer. A gauge mean is time-weighted (value × ms),
12600
+ * never the mean of the samples. A hole with no held value is not integrated
12601
+ * as zero. A truncated read is not published.
12602
+ */
12603
+ var STATISTIC_STATS = [
12604
+ "min",
12605
+ "max",
12606
+ "mean",
12607
+ "sum",
12608
+ "count",
12609
+ "change"
12610
+ ];
12611
+ /**
12612
+ * The range a statistics or integral helper asks history for (D944).
12613
+ *
12614
+ * Calendar edges are the hub's timezone, never UTC midnight: a day is 23, 24
12615
+ * or 25 hours across a DST change. A tier is eligible only when its retention
12616
+ * is strictly longer than the longest that range can be. Forever qualifies.
12617
+ * Equal to a tier's retention is a refusal, not a short answer.
12618
+ */
12619
+ var MINUTE_MS = 6e4;
12620
+ var HOUR_MS = 60 * MINUTE_MS;
12621
+ var DAY_MS = 24 * HOUR_MS;
12622
+ var ROLLING_SPANS = [
12623
+ "1h",
12624
+ "24h",
12625
+ "2d",
12626
+ "7d",
12627
+ "30d",
12628
+ "custom"
12629
+ ];
12630
+ var CALENDAR_PERIODS = [
12631
+ "today",
12632
+ "yesterday",
12633
+ "this-week",
12634
+ "this-month"
12635
+ ];
12636
+ var RESET_PERIODS = [
12637
+ "never",
12638
+ "daily",
12639
+ "weekly",
12640
+ "monthly"
12641
+ ];
12642
+ 5 * MINUTE_MS;
12643
+ 24 * HOUR_MS, 2 * DAY_MS, 7 * DAY_MS, 30 * DAY_MS;
12644
+ 7 * DAY_MS, 30 * DAY_MS, 365 * DAY_MS;
12645
+ var rollingWindowObject = object({
12646
+ kind: literal("rolling"),
12647
+ span: _enum(ROLLING_SPANS),
12648
+ customMinutes: number().int().min(5).optional()
12649
+ }).check((payload) => {
12650
+ if (payload.value.span === "custom" && payload.value.customMinutes === void 0) payload.issues.push({
12651
+ code: "custom",
12652
+ message: "a custom range needs a duration in minutes",
12653
+ path: ["customMinutes"],
12654
+ input: payload.value
12655
+ });
12656
+ if (payload.value.span !== "custom" && payload.value.customMinutes !== void 0) payload.issues.push({
12657
+ code: "custom",
12658
+ message: "only a custom range carries a duration",
12659
+ path: ["customMinutes"],
12660
+ input: payload.value
12661
+ });
12662
+ });
12663
+ var RollingWindowSchema = rollingWindowObject;
12664
+ var StatisticWindowSchema = discriminatedUnion("kind", [rollingWindowObject, object({
12665
+ kind: literal("calendar"),
12666
+ period: _enum(CALENDAR_PERIODS)
12667
+ })]);
12668
+ /**
12553
12669
  * A composition source identity: `(addonId, stableId)`, never a numeric device
12554
12670
  * id. Split out so helper schemas can name it without a cycle through
12555
12671
  * `composition.ts` (D659).
@@ -12575,7 +12691,13 @@ var GROUP_TEMPLATE_IDS = [
12575
12691
  "occupancy",
12576
12692
  "binary",
12577
12693
  "switch",
12578
- "light"
12694
+ "light",
12695
+ "fan",
12696
+ "cover",
12697
+ "valve",
12698
+ "lock",
12699
+ "media-player",
12700
+ "climate"
12579
12701
  ];
12580
12702
  var NUMERIC_AGGREGATES = [
12581
12703
  "mean",
@@ -12605,14 +12727,73 @@ var HelperMemberSchema = object({
12605
12727
  cap: string().min(1),
12606
12728
  fieldPath: string().min(1)
12607
12729
  });
12608
- var HelperSpecSchema = discriminatedUnion("kind", [object({
12609
- kind: literal("group"),
12610
- templateId: GroupTemplateIdSchema,
12611
- aggregate: AggregateReducerSchema,
12612
- members: array(HelperMemberSchema).min(1).max(64),
12613
- /** Actuator groups only. Readings leave this unset. */
12614
- active: ActiveCriterionSchema.optional()
12615
- })]);
12730
+ var HelperSpecSchema = discriminatedUnion("kind", [
12731
+ object({
12732
+ kind: literal("group"),
12733
+ templateId: GroupTemplateIdSchema,
12734
+ aggregate: AggregateReducerSchema,
12735
+ members: array(HelperMemberSchema).min(1).max(64),
12736
+ /** Actuator groups only. Readings leave this unset. */
12737
+ active: ActiveCriterionSchema.optional()
12738
+ }),
12739
+ object({
12740
+ kind: literal("appliance"),
12741
+ templateId: literal(APPLIANCE_TEMPLATE_ID),
12742
+ appliance: _enum(APPLIANCE_KINDS),
12743
+ thresholdWatts: number().positive(),
12744
+ startDwellMs: number().int().nonnegative(),
12745
+ stopDwellMs: number().int().nonnegative(),
12746
+ member: HelperMemberSchema
12747
+ }),
12748
+ object({
12749
+ kind: literal("statistic"),
12750
+ stat: _enum(STATISTIC_STATS),
12751
+ window: StatisticWindowSchema,
12752
+ source: HelperMemberSchema
12753
+ }),
12754
+ object({
12755
+ kind: literal("integral"),
12756
+ reset: _enum(RESET_PERIODS),
12757
+ output: _enum(["energy", "meter"]),
12758
+ source: HelperMemberSchema
12759
+ }),
12760
+ object({
12761
+ kind: literal("threshold"),
12762
+ source: HelperMemberSchema,
12763
+ high: number(),
12764
+ low: number(),
12765
+ onForMs: number().int().nonnegative(),
12766
+ offForMs: number().int().nonnegative()
12767
+ }),
12768
+ object({
12769
+ kind: literal("arithmetic"),
12770
+ op: _enum([
12771
+ "sum",
12772
+ "difference",
12773
+ "ratio"
12774
+ ]),
12775
+ inputs: array(HelperMemberSchema).min(2).max(64)
12776
+ }),
12777
+ object({
12778
+ kind: literal("expression"),
12779
+ expr: string().min(1),
12780
+ inputs: array(object({
12781
+ name: string().min(1),
12782
+ member: HelperMemberSchema
12783
+ })).min(1).max(64),
12784
+ output: _enum(["number", "boolean"])
12785
+ }),
12786
+ object({
12787
+ kind: literal("derivative"),
12788
+ window: RollingWindowSchema,
12789
+ per: _enum([
12790
+ "second",
12791
+ "minute",
12792
+ "hour"
12793
+ ]),
12794
+ source: HelperMemberSchema
12795
+ })
12796
+ ]);
12616
12797
  /** A composed device's stableId is this prefix plus the block id. */
12617
12798
  var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
12618
12799
  /** Longest snippet a `code` source may carry (slice 4). */
@@ -12673,6 +12854,12 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12673
12854
  "all"
12674
12855
  ]),
12675
12856
  reads: array(CompositionFieldReadSchema).min(1).max(64),
12857
+ /**
12858
+ * Set when the reads are an enum state, not a yes/no: the listed states
12859
+ * count as active (a cover is `open`). A state outside the list is inactive;
12860
+ * a read that is not a state at all is left out. Only `any` / `all` use it.
12861
+ */
12862
+ activeWhen: array(string().min(1)).min(1).optional(),
12676
12863
  /** `value` is the reading; the others are the member counts. */
12677
12864
  publish: _enum([
12678
12865
  "value",
@@ -12686,6 +12873,26 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12686
12873
  "omit",
12687
12874
  "offline"
12688
12875
  ])
12876
+ }),
12877
+ object({
12878
+ kind: literal("series"),
12879
+ read: CompositionFieldReadSchema,
12880
+ /** `value` is the statistic or the integral. The others are the gap counts. */
12881
+ publish: _enum([
12882
+ "value",
12883
+ "included",
12884
+ "excluded",
12885
+ "total"
12886
+ ]),
12887
+ /**
12888
+ * What an unanswered `value` does. Counts ignore this. `offline` leaves the
12889
+ * device unseeded until history answers, so a restart cannot publish 0.
12890
+ */
12891
+ empty: _enum([
12892
+ "null",
12893
+ "omit",
12894
+ "offline"
12895
+ ])
12689
12896
  })
12690
12897
  ]);
12691
12898
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
@@ -17845,7 +18052,22 @@ var BrokerProviderInfoSchema = object({
17845
18052
  */
17846
18053
  interactiveConnectPath: string().min(1).optional(),
17847
18054
  /** Relative hub path to disconnect the linked account (same popup pattern). */
17848
- interactiveDisconnectPath: string().min(1).optional()
18055
+ interactiveDisconnectPath: string().min(1).optional(),
18056
+ /**
18057
+ * The kind signs in through a server-held interactive session instead of
18058
+ * (or before) typed credentials. `basePath` is a hub-relative route set
18059
+ * served by the owning addon (`/addon/<id>/...`) speaking the sign-in
18060
+ * contract: `POST {basePath}/start` (body: the form's settings) answers
18061
+ * `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
18062
+ * `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
18063
+ * 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
18064
+ * (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
18065
+ * existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
18066
+ * (`{ sessionId }`) drops the session. The dialog that edits a broker
18067
+ * offers it as the default path; the fields the session fills are
18068
+ * marked `filledBySignIn` in the kind's settings schema.
18069
+ */
18070
+ signIn: object({ basePath: string().min(1) }).optional()
17849
18071
  }))
17850
18072
  });
17851
18073
  var ListInputSchema = object({
@@ -18950,7 +19172,8 @@ var CompositionProblemSchema = object({
18950
19172
  "native-capability-replace",
18951
19173
  "mixed-quantity",
18952
19174
  "helper-mismatch",
18953
- "member-constraint"
19175
+ "member-constraint",
19176
+ "retention"
18954
19177
  ]),
18955
19178
  path: string(),
18956
19179
  message: string()
@@ -19184,13 +19407,17 @@ Object.freeze({
19184
19407
  latchedAt: {
19185
19408
  kind: "fixed",
19186
19409
  result: ["number", "null"]
19410
+ },
19411
+ schmitt: {
19412
+ kind: "fixed",
19413
+ result: ["boolean", "null"]
19187
19414
  }
19188
19415
  });
19189
19416
  /**
19190
19417
  * One catalog row per base reading a group helper can build (D944 §3).
19191
19418
  * Data, not a form default written into the block.
19192
19419
  */
19193
- var NOW = {
19420
+ var NOW$2 = {
19194
19421
  fieldPath: "lastFetchedAt",
19195
19422
  expr: "now"
19196
19423
  };
@@ -19204,6 +19431,8 @@ function numeric(input) {
19204
19431
  family: "numeric",
19205
19432
  deviceType: DeviceType.Sensor,
19206
19433
  memberDeviceType: DeviceType.Sensor,
19434
+ memberDeviceTypes: [DeviceType.Sensor],
19435
+ activeWhen: void 0,
19207
19436
  commandCaps: [],
19208
19437
  baseUnit: baseUnitOf(input.quantity),
19209
19438
  aggregates: NUMERIC_AGGREGATES,
@@ -19219,6 +19448,8 @@ function binary(input) {
19219
19448
  family: "binary",
19220
19449
  deviceType: DeviceType.Sensor,
19221
19450
  memberDeviceType: DeviceType.Sensor,
19451
+ memberDeviceTypes: [DeviceType.Sensor],
19452
+ activeWhen: void 0,
19222
19453
  commandCaps: [],
19223
19454
  quantity: "none",
19224
19455
  baseUnit: void 0,
@@ -19236,7 +19467,7 @@ var GROUP_TEMPLATES = [
19236
19467
  quantity: "temperature",
19237
19468
  defaultAggregate: "mean",
19238
19469
  empty: "null",
19239
- companions: [NOW]
19470
+ companions: [NOW$2]
19240
19471
  }),
19241
19472
  numeric({
19242
19473
  id: "humidity",
@@ -19247,7 +19478,7 @@ var GROUP_TEMPLATES = [
19247
19478
  quantity: "humidity",
19248
19479
  defaultAggregate: "mean",
19249
19480
  empty: "null",
19250
- companions: [NOW]
19481
+ companions: [NOW$2]
19251
19482
  }),
19252
19483
  numeric({
19253
19484
  id: "pressure",
@@ -19258,7 +19489,7 @@ var GROUP_TEMPLATES = [
19258
19489
  quantity: "pressure",
19259
19490
  defaultAggregate: "mean",
19260
19491
  empty: "null",
19261
- companions: [NOW]
19492
+ companions: [NOW$2]
19262
19493
  }),
19263
19494
  numeric({
19264
19495
  id: "illuminance",
@@ -19269,7 +19500,7 @@ var GROUP_TEMPLATES = [
19269
19500
  quantity: "illuminance",
19270
19501
  defaultAggregate: "mean",
19271
19502
  empty: "null",
19272
- companions: [NOW]
19503
+ companions: [NOW$2]
19273
19504
  }),
19274
19505
  numeric({
19275
19506
  id: "power",
@@ -19280,7 +19511,7 @@ var GROUP_TEMPLATES = [
19280
19511
  quantity: "power",
19281
19512
  defaultAggregate: "sum",
19282
19513
  empty: "omit",
19283
- companions: [NOW]
19514
+ companions: [NOW$2]
19284
19515
  }),
19285
19516
  numeric({
19286
19517
  id: "energy",
@@ -19291,7 +19522,7 @@ var GROUP_TEMPLATES = [
19291
19522
  quantity: "energy",
19292
19523
  defaultAggregate: "sum",
19293
19524
  empty: "omit",
19294
- companions: [NOW]
19525
+ companions: [NOW$2]
19295
19526
  }),
19296
19527
  numeric({
19297
19528
  id: "battery",
@@ -19326,7 +19557,7 @@ var GROUP_TEMPLATES = [
19326
19557
  quantity: "device",
19327
19558
  defaultAggregate: "mean",
19328
19559
  empty: "null",
19329
- companions: [NOW]
19560
+ companions: [NOW$2]
19330
19561
  }),
19331
19562
  binary({
19332
19563
  id: "motion",
@@ -19420,6 +19651,11 @@ var GROUP_TEMPLATES = [
19420
19651
  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.",
19421
19652
  deviceType: DeviceType.Switch,
19422
19653
  outputCap: "switch",
19654
+ outputField: "on",
19655
+ member: {
19656
+ cap: "switch",
19657
+ fieldPath: "on"
19658
+ },
19423
19659
  commandCaps: ["switch"]
19424
19660
  }),
19425
19661
  actuator({
@@ -19427,34 +19663,136 @@ var GROUP_TEMPLATES = [
19427
19663
  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.",
19428
19664
  deviceType: DeviceType.Light,
19429
19665
  outputCap: "switch",
19666
+ outputField: "on",
19667
+ member: {
19668
+ cap: "switch",
19669
+ fieldPath: "on"
19670
+ },
19430
19671
  commandCaps: [
19431
19672
  "switch",
19432
19673
  "brightness",
19433
19674
  "color"
19434
19675
  ]
19676
+ }),
19677
+ actuator({
19678
+ id: "fan",
19679
+ 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.",
19680
+ deviceType: DeviceType.Fan,
19681
+ outputCap: "switch",
19682
+ outputField: "on",
19683
+ member: {
19684
+ cap: "switch",
19685
+ fieldPath: "on"
19686
+ },
19687
+ commandCaps: ["switch", "fan-control"]
19688
+ }),
19689
+ actuator({
19690
+ id: "cover",
19691
+ 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.",
19692
+ deviceType: DeviceType.Cover,
19693
+ outputCap: "group-active",
19694
+ outputField: "active",
19695
+ member: {
19696
+ cap: "cover",
19697
+ fieldPath: "state"
19698
+ },
19699
+ activeWhen: [
19700
+ "open",
19701
+ "opening",
19702
+ "stopped"
19703
+ ],
19704
+ commandCaps: ["cover"]
19705
+ }),
19706
+ actuator({
19707
+ id: "valve",
19708
+ 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.",
19709
+ deviceType: DeviceType.Valve,
19710
+ outputCap: "group-active",
19711
+ outputField: "active",
19712
+ member: {
19713
+ cap: "valve",
19714
+ fieldPath: "state"
19715
+ },
19716
+ activeWhen: [
19717
+ "open",
19718
+ "opening",
19719
+ "stopped"
19720
+ ],
19721
+ commandCaps: ["valve"]
19722
+ }),
19723
+ actuator({
19724
+ id: "lock",
19725
+ 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.",
19726
+ deviceType: DeviceType.Lock,
19727
+ outputCap: "group-active",
19728
+ outputField: "active",
19729
+ member: {
19730
+ cap: "lock-control",
19731
+ fieldPath: "state"
19732
+ },
19733
+ activeWhen: ["locked"],
19734
+ commandCaps: ["lock-control"]
19735
+ }),
19736
+ actuator({
19737
+ id: "media-player",
19738
+ 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.",
19739
+ deviceType: DeviceType.MediaPlayer,
19740
+ outputCap: "group-active",
19741
+ outputField: "active",
19742
+ member: {
19743
+ cap: "media-player",
19744
+ fieldPath: "state"
19745
+ },
19746
+ activeWhen: ["playing", "buffering"],
19747
+ commandCaps: ["media-player"]
19748
+ }),
19749
+ actuator({
19750
+ id: "climate",
19751
+ 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.",
19752
+ deviceType: DeviceType.Thermostat,
19753
+ memberDeviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
19754
+ outputCap: "group-active",
19755
+ outputField: "active",
19756
+ member: {
19757
+ cap: "climate-control",
19758
+ fieldPath: "mode"
19759
+ },
19760
+ activeWhen: [
19761
+ "heat",
19762
+ "cool",
19763
+ "auto",
19764
+ "heat_cool",
19765
+ "fan_only",
19766
+ "dry"
19767
+ ],
19768
+ commandCaps: ["climate-control"]
19435
19769
  })
19436
19770
  ];
19437
19771
  function actuator(input) {
19438
19772
  return {
19439
- ...input,
19773
+ id: input.id,
19774
+ summary: input.summary,
19775
+ deviceType: input.deviceType,
19776
+ outputCap: input.outputCap,
19777
+ outputField: input.outputField,
19778
+ commandCaps: input.commandCaps,
19440
19779
  family: "actuator",
19441
19780
  role: void 0,
19442
19781
  memberDeviceType: input.deviceType,
19443
- outputField: "on",
19782
+ memberDeviceTypes: input.memberDeviceTypes ?? [input.deviceType],
19783
+ activeWhen: input.activeWhen,
19444
19784
  quantity: "none",
19445
19785
  baseUnit: void 0,
19446
19786
  aggregates: BINARY_AGGREGATES,
19447
19787
  defaultAggregate: "any",
19448
19788
  empty: "offline",
19449
- accept: [{
19450
- cap: "switch",
19451
- fieldPath: "on"
19452
- }],
19789
+ accept: [input.member],
19453
19790
  companions: [CHANGED]
19454
19791
  };
19455
19792
  }
19456
19793
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
19457
19794
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
19795
+ DeviceType.Camera;
19458
19796
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
19459
19797
  /**
19460
19798
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -44774,7 +45112,7 @@ var NetworkAccessStatusSchema = object({
44774
45112
  });
44775
45113
  /**
44776
45114
  * Optional, richer endpoint shape returned by providers that expose
44777
- * MORE than one ingress concurrently (Tailscale Ingress with mixed
45115
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
44778
45116
  * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
44779
45117
  * the originating provider config (mode + sourcePort) so the
44780
45118
  * orchestrator UI can label rows distinctly. Providers that expose only
@@ -47461,7 +47799,20 @@ var NcRuleInputSchema = object({
47461
47799
  * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
47462
47800
  * path does not run Zod defaults.
47463
47801
  */
47464
- groupingSec: number().int().min(5).max(60).optional()
47802
+ groupingSec: number().int().min(5).max(60).optional(),
47803
+ /**
47804
+ * ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
47805
+ * carries "Correct" / "Wrong" buttons (and, where the channel has room, a
47806
+ * "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
47807
+ * same mark the Library writes, and records whose notification it was and
47808
+ * when. A channel with no buttons gets the same two choices as links.
47809
+ *
47810
+ * `.optional()`, never `.default()` (a Zod default does not run on the addon
47811
+ * cap path): absent and `false` both mean off, which is what every rule
47812
+ * authored before this field meant. An editor turns it off by writing
47813
+ * `false`, so it needs no `clear` key.
47814
+ */
47815
+ askVerdict: boolean().optional()
47465
47816
  });
47466
47817
  /**
47467
47818
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -58181,7 +58532,8 @@ var BATTERY_DEVICE_TYPES = [
58181
58532
  DeviceType.Switch,
58182
58533
  DeviceType.PetFeeder,
58183
58534
  DeviceType.Container,
58184
- DeviceType.Thermostat
58535
+ DeviceType.Thermostat,
58536
+ DeviceType.EventEmitter
58185
58537
  ];
58186
58538
  /** Generic Customize target for the percentage reading (D936). */
58187
58539
  var batteryCustomize = {
@@ -61041,7 +61393,17 @@ var groupCommandsCapability = {
61041
61393
  scope: "device",
61042
61394
  deviceNative: true,
61043
61395
  mode: "singleton",
61044
- deviceTypes: [DeviceType.Switch, DeviceType.Light],
61396
+ deviceTypes: [
61397
+ DeviceType.Switch,
61398
+ DeviceType.Light,
61399
+ DeviceType.Fan,
61400
+ DeviceType.Cover,
61401
+ DeviceType.Valve,
61402
+ DeviceType.Lock,
61403
+ DeviceType.MediaPlayer,
61404
+ DeviceType.Thermostat,
61405
+ DeviceType.Climate
61406
+ ],
61045
61407
  methods: {},
61046
61408
  status: {
61047
61409
  schema: GroupCommandsSchema,
@@ -61059,6 +61421,49 @@ var groupCommandsCapability = {
61059
61421
  history: "none"
61060
61422
  };
61061
61423
  /**
61424
+ * Whether a helper actuator group counts as active (D944): at least one
61425
+ * included member is, or every included member is. What "active" means is the
61426
+ * type's: a cover is open, a lock is locked, a media player is playing, a
61427
+ * climate device is not off. A group of lights, switches and fans reports
61428
+ * `switch.on` instead; this cap is for the types whose own state is not a
61429
+ * yes/no. Unknown members are left out (`group-status` counts them) and an
61430
+ * empty included set takes the device offline, never `active: false`.
61431
+ */
61432
+ var GroupActiveSchema = object({
61433
+ active: boolean().meta({ label: "Active" }),
61434
+ /** Ms epoch when the slice was last updated. */
61435
+ lastChangedAt: number()
61436
+ });
61437
+ var groupActiveCapability = {
61438
+ name: "group-active",
61439
+ scope: "device",
61440
+ deviceNative: true,
61441
+ mode: "singleton",
61442
+ deviceTypes: [
61443
+ DeviceType.Cover,
61444
+ DeviceType.Valve,
61445
+ DeviceType.Lock,
61446
+ DeviceType.MediaPlayer,
61447
+ DeviceType.Climate,
61448
+ DeviceType.Thermostat
61449
+ ],
61450
+ methods: {},
61451
+ status: {
61452
+ schema: GroupActiveSchema,
61453
+ kind: "push"
61454
+ },
61455
+ runtimeState: GroupActiveSchema,
61456
+ /**
61457
+ * Runtime-state durability: **session** — recomputed from the members on the
61458
+ * next source event; a restored value would outlive the members it named.
61459
+ *
61460
+ * See `RuntimeStateDurability`. Enforced by
61461
+ * `scripts/check-runtime-state-durability.ts`.
61462
+ */
61463
+ durability: "session",
61464
+ history: "none"
61465
+ };
61466
+ /**
61062
61467
  * How many members of a helper group answered (D944). Counts are integers,
61063
61468
  * not measurements: zero included is a real count, never a stand-in reading.
61064
61469
  * The reading itself stays on the output capability. History is none — the
@@ -61089,7 +61494,14 @@ var groupStatusCapability = {
61089
61494
  deviceTypes: [
61090
61495
  DeviceType.Sensor,
61091
61496
  DeviceType.Switch,
61092
- DeviceType.Light
61497
+ DeviceType.Light,
61498
+ DeviceType.Fan,
61499
+ DeviceType.Cover,
61500
+ DeviceType.Valve,
61501
+ DeviceType.Lock,
61502
+ DeviceType.MediaPlayer,
61503
+ DeviceType.Thermostat,
61504
+ DeviceType.Climate
61093
61505
  ],
61094
61506
  methods: {},
61095
61507
  status: {
@@ -62362,7 +62774,14 @@ var lockControlCapability = {
62362
62774
  }),
62363
62775
  unlock: method(object({
62364
62776
  deviceId: number().int().nonnegative(),
62365
- code: string().min(1).optional()
62777
+ code: string().min(1).optional(),
62778
+ /**
62779
+ * Honoured by a helper lock GROUP only (D944): unlocking several doors
62780
+ * with one call is refused unless the caller says it was confirmed. A
62781
+ * single lock ignores it. The refusal lives in the callee, so no
62782
+ * automation or API caller reaches the doors by skipping a dialog.
62783
+ */
62784
+ confirm: boolean().optional()
62366
62785
  }), _void(), {
62367
62786
  kind: "mutation",
62368
62787
  auth: "protected"
@@ -62399,6 +62818,44 @@ var lockControlCapability = {
62399
62818
  volatileStateFields: ["lastChangedAt"]
62400
62819
  };
62401
62820
  /**
62821
+ * A monotonic total between resets (D944). Energy uses `power-meter.kwhTotal`
62822
+ * so dashboards keep one series; every other instantaneous reading lands here.
62823
+ * `value` is the time integral in the source quantity's accumulated base
62824
+ * (base-unit seconds, or kWh on the power meter). `null` is a gap, never zero.
62825
+ */
62826
+ var MeterStatusSchema = object({
62827
+ value: number().nullable().meta(readingMeta("device", { label: "Total" })),
62828
+ lastFetchedAt: number()
62829
+ });
62830
+ var meterCapability = {
62831
+ name: "meter",
62832
+ scope: "device",
62833
+ deviceNative: true,
62834
+ mode: "singleton",
62835
+ deviceTypes: [DeviceType.Sensor],
62836
+ methods: {},
62837
+ status: {
62838
+ schema: MeterStatusSchema,
62839
+ kind: "push"
62840
+ },
62841
+ runtimeState: MeterStatusSchema,
62842
+ /**
62843
+ * Runtime-state durability: **restored** — the total is a cache of the
62844
+ * history integral. A restart shows it only until the history pass
62845
+ * recomputes the open period; it is not an in-memory buffer.
62846
+ *
62847
+ * See `RuntimeStateDurability`. Enforced by
62848
+ * `scripts/check-runtime-state-durability.ts`.
62849
+ */
62850
+ durability: "restored",
62851
+ history: { fields: { value: {
62852
+ kind: "counter",
62853
+ deadband: .001,
62854
+ minIntervalMs: 6e4
62855
+ } } },
62856
+ volatileStateFields: ["lastFetchedAt"]
62857
+ };
62858
+ /**
62402
62859
  * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
62403
62860
  * Apple TV, Spotify, Roku, generic OTT receivers, …) on
62404
62861
  * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
@@ -64910,9 +65367,9 @@ var PowerMeterStatusSchema = object({
64910
65367
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
64911
65368
  kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
64912
65369
  /** Voltage in volts. */
64913
- volts: number().optional().meta(readingMeta("voltage")),
65370
+ volts: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
64914
65371
  /** Current in amperes. */
64915
- amps: number().optional().meta(readingMeta("current")),
65372
+ amps: number().optional().meta(readingMeta("current", { label: "Current" })),
64916
65373
  /** Apparent power in volt-amperes. */
64917
65374
  va: number().optional().meta(readingMeta("apparent-power")),
64918
65375
  /** Power factor (0..1) if reported. */
@@ -64920,12 +65377,74 @@ var PowerMeterStatusSchema = object({
64920
65377
  /** Ms epoch when the slice was last updated. */
64921
65378
  lastFetchedAt: number()
64922
65379
  });
65380
+ /**
65381
+ * Loads that draw metered mains power (D951). The reading is a feature of the
65382
+ * device itself — a plug, a lamp, a fan, an air conditioner, a boiler, a
65383
+ * vacuum dock — not a separate sensor beside it. A `Sensor` stays: a clamp
65384
+ * meter or a standalone energy monitor IS the device. Cover, lock, valve,
65385
+ * camera and the like are left out: their draw is not what anyone meters.
65386
+ */
65387
+ var POWER_METER_DEVICE_TYPES = [
65388
+ DeviceType.Sensor,
65389
+ DeviceType.Switch,
65390
+ DeviceType.Light,
65391
+ DeviceType.Fan,
65392
+ DeviceType.Climate,
65393
+ DeviceType.Thermostat,
65394
+ DeviceType.Humidifier,
65395
+ DeviceType.WaterHeater,
65396
+ DeviceType.MediaPlayer,
65397
+ DeviceType.Washer,
65398
+ DeviceType.Dryer,
65399
+ DeviceType.Dishwasher,
65400
+ DeviceType.Vacuum,
65401
+ DeviceType.LawnMower,
65402
+ DeviceType.PetFeeder
65403
+ ];
65404
+ /** Everything but a bare `Sensor`: a sensor that takes its reading from another sensor adds nothing. */
65405
+ var POWER_METER_CUSTOMIZE_DEVICE_TYPES = POWER_METER_DEVICE_TYPES.filter((type) => type !== DeviceType.Sensor);
65406
+ /** One scalar target per reading; each is sourced from a device of its own (D951). */
65407
+ function powerTarget(id, unit) {
65408
+ return {
65409
+ id,
65410
+ cardinality: "scalar",
65411
+ vocabulary: {
65412
+ cap: "power-meter",
65413
+ stateField: id
65414
+ },
65415
+ deviceTypes: POWER_METER_CUSTOMIZE_DEVICE_TYPES,
65416
+ apply: "add",
65417
+ path: id,
65418
+ sources: [{
65419
+ kind: "numeric",
65420
+ requiredUnit: unit,
65421
+ transforms: [{ kind: "identity" }]
65422
+ }],
65423
+ companions: [{
65424
+ kind: "clock",
65425
+ path: "lastFetchedAt",
65426
+ when: "always"
65427
+ }],
65428
+ sourceReport: { status: "assignment-status" },
65429
+ advancedExpression: true
65430
+ };
65431
+ }
65432
+ /** "Power and energy" extension: watts, energy, voltage and current from other devices (D951). */
65433
+ var powerMeterCustomize = {
65434
+ version: 1,
65435
+ targets: [
65436
+ powerTarget("watts", "W"),
65437
+ powerTarget("kwhTotal", "kWh"),
65438
+ powerTarget("volts", "V"),
65439
+ powerTarget("amps", "A")
65440
+ ]
65441
+ };
64923
65442
  var powerMeterCapability = {
64924
65443
  name: "power-meter",
64925
65444
  scope: "device",
64926
65445
  deviceNative: true,
64927
65446
  mode: "singleton",
64928
- deviceTypes: [DeviceType.Sensor],
65447
+ deviceTypes: POWER_METER_DEVICE_TYPES,
64929
65448
  methods: {},
64930
65449
  status: {
64931
65450
  schema: PowerMeterStatusSchema,
@@ -64982,7 +65501,8 @@ var powerMeterCapability = {
64982
65501
  } },
64983
65502
  /** Clock fields: written, but excluded from the compare that decides
64984
65503
  * whether persisting is worth a SQLite commit. */
64985
- volatileStateFields: ["lastFetchedAt"]
65504
+ volatileStateFields: ["lastFetchedAt"],
65505
+ customize: powerMeterCustomize
64986
65506
  };
64987
65507
  /**
64988
65508
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -65663,6 +66183,8 @@ var runningCustomize = {
65663
66183
  stateField: "running"
65664
66184
  },
65665
66185
  deviceTypes: [
66186
+ DeviceType.Washer,
66187
+ DeviceType.Dryer,
65666
66188
  DeviceType.Dishwasher,
65667
66189
  DeviceType.Vacuum,
65668
66190
  DeviceType.LawnMower,
@@ -68152,7 +68674,8 @@ var switchCapability = {
68152
68674
  deviceTypes: [
68153
68675
  DeviceType.Switch,
68154
68676
  DeviceType.Siren,
68155
- DeviceType.Light
68677
+ DeviceType.Light,
68678
+ DeviceType.Fan
68156
68679
  ],
68157
68680
  methods: { setState: method(object({
68158
68681
  deviceId: number(),
@@ -70365,8 +70888,9 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
70365
70888
  kind: "mutation",
70366
70889
  auth: "admin"
70367
70890
  });
70891
+ DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
70368
70892
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
70369
- 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;
70893
+ 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;
70370
70894
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
70371
70895
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
70372
70896
  new Set(CAP_NAMES);
@@ -70695,6 +71219,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
70695
71219
  featureProbe: featureProbeCapability,
70696
71220
  flood: floodCapability,
70697
71221
  gas: gasCapability,
71222
+ groupActive: groupActiveCapability,
70698
71223
  groupCommands: groupCommandsCapability,
70699
71224
  groupStatus: groupStatusCapability,
70700
71225
  heatingValve: heatingValveCapability,
@@ -70706,6 +71231,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
70706
71231
  lawnMowerControl: lawnMowerControlCapability,
70707
71232
  lockControl: lockControlCapability,
70708
71233
  mediaPlayer: mediaPlayerCapability,
71234
+ meter: meterCapability,
70709
71235
  motion: motionCapability,
70710
71236
  motionTrigger: motionTriggerCapability,
70711
71237
  motionZones: motionZonesCapability,
@@ -71965,7 +72491,7 @@ var BaseDeviceProvider = class extends BaseAddon {
71965
72491
  return toDeviceSummary(device, this.addonId);
71966
72492
  }
71967
72493
  };
71968
- 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;
72494
+ 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;
71969
72495
  new Set(Object.values(DeviceType));
71970
72496
  Object.freeze({
71971
72497
  "accessories.getStatus": {
@@ -75188,6 +75714,13 @@ Object.freeze({
75188
75714
  access: "view",
75189
75715
  role: "viewer"
75190
75716
  },
75717
+ "groupActive.getStatus": {
75718
+ capName: "group-active",
75719
+ capScope: "device",
75720
+ addonId: null,
75721
+ access: "view",
75722
+ role: "viewer"
75723
+ },
75191
75724
  "groupCommands.getStatus": {
75192
75725
  capName: "group-commands",
75193
75726
  capScope: "device",
@@ -76154,6 +76687,13 @@ Object.freeze({
76154
76687
  access: "create",
76155
76688
  role: "admin"
76156
76689
  },
76690
+ "meter.getStatus": {
76691
+ capName: "meter",
76692
+ capScope: "device",
76693
+ addonId: null,
76694
+ access: "view",
76695
+ role: "viewer"
76696
+ },
76157
76697
  "metricsProvider.collectSnapshot": {
76158
76698
  capName: "metrics-provider",
76159
76699
  capScope: "system",
@@ -84252,7 +84792,39 @@ var APPLIANCE_CAP_VOCABULARY = {
84252
84792
  phrase: t("When {device} finishes", "Quando {device} finisce"),
84253
84793
  icon: "square"
84254
84794
  }
84255
- }
84795
+ },
84796
+ overrides: { byType: {
84797
+ [DeviceType.Washer]: {
84798
+ state: { running: {
84799
+ whenTrue: t("When the washing machine starts", "Quando la lavatrice inizia"),
84800
+ whenFalse: t("When the washing machine has finished", "Quando la lavatrice ha finito")
84801
+ } },
84802
+ events: {
84803
+ onStarted: { phrase: t("When the washing machine starts", "Quando la lavatrice inizia") },
84804
+ onFinished: { phrase: t("When the washing machine has finished", "Quando la lavatrice ha finito") }
84805
+ }
84806
+ },
84807
+ [DeviceType.Dryer]: {
84808
+ state: { running: {
84809
+ whenTrue: t("When the dryer starts", "Quando l'asciugatrice inizia"),
84810
+ whenFalse: t("When the dryer has finished", "Quando l'asciugatrice ha finito")
84811
+ } },
84812
+ events: {
84813
+ onStarted: { phrase: t("When the dryer starts", "Quando l'asciugatrice inizia") },
84814
+ onFinished: { phrase: t("When the dryer has finished", "Quando l'asciugatrice ha finito") }
84815
+ }
84816
+ },
84817
+ [DeviceType.Dishwasher]: {
84818
+ state: { running: {
84819
+ whenTrue: t("When the dishwasher starts", "Quando la lavastoviglie inizia"),
84820
+ whenFalse: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito")
84821
+ } },
84822
+ events: {
84823
+ onStarted: { phrase: t("When the dishwasher starts", "Quando la lavastoviglie inizia") },
84824
+ onFinished: { phrase: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito") }
84825
+ }
84826
+ }
84827
+ } }
84256
84828
  },
84257
84829
  update: {
84258
84830
  scope: "device",
@@ -85024,6 +85596,10 @@ var SENSOR_CAP_VOCABULARY = {
85024
85596
  icon: "flame"
85025
85597
  }]
85026
85598
  },
85599
+ "group-active": {
85600
+ scope: "device",
85601
+ noun: t("Group state", "Stato del gruppo")
85602
+ },
85027
85603
  "group-commands": {
85028
85604
  scope: "device",
85029
85605
  noun: t("Group command", "Comando di gruppo")
@@ -85080,6 +85656,17 @@ var SENSOR_CAP_VOCABULARY = {
85080
85656
  }],
85081
85657
  events: { onMotionChanged: { notOperatorFacing: "the `detected` state trigger phrases both edges, from the mirror that survives a dropped event (D49)" } }
85082
85658
  },
85659
+ meter: {
85660
+ scope: "device",
85661
+ noun: t("Meter", "Contatore"),
85662
+ state: [{
85663
+ kind: "numeric",
85664
+ field: "value",
85665
+ quantity: t("the total", "il totale"),
85666
+ label: t("Total", "Totale"),
85667
+ icon: "gauge"
85668
+ }]
85669
+ },
85083
85670
  "numeric-sensor": {
85084
85671
  scope: "device",
85085
85672
  noun: t("Sensor", "Sensore"),
@@ -85552,8 +86139,47 @@ function projectAction(cap, action) {
85552
86139
  };
85553
86140
  }
85554
86141
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
85555
- 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;
86142
+ 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;
85556
86143
  DeviceFeature.BatteryOperated;
86144
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
86145
+ 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.";
86146
+ 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.";
86147
+ var ALEXA_REASON = "Alexa has no laundry category in this export, and neither power nor running has an Alexa interface.";
86148
+ var GOOGLE_REASON = "Google WASHER, DRYER and DISHWASHER require RunCycle. A power threshold is not that trait, and this export does not publish RunCycle.";
86149
+ 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.";
86150
+ function withheld(reason) {
86151
+ return {
86152
+ [DeviceType.Washer]: {
86153
+ exported: false,
86154
+ reason
86155
+ },
86156
+ [DeviceType.Dryer]: {
86157
+ exported: false,
86158
+ reason
86159
+ },
86160
+ [DeviceType.Dishwasher]: {
86161
+ exported: false,
86162
+ reason
86163
+ }
86164
+ };
86165
+ }
86166
+ function offered(received) {
86167
+ return {
86168
+ [DeviceType.Washer]: {
86169
+ exported: true,
86170
+ as: received
86171
+ },
86172
+ [DeviceType.Dryer]: {
86173
+ exported: true,
86174
+ as: received
86175
+ },
86176
+ [DeviceType.Dishwasher]: {
86177
+ exported: true,
86178
+ as: received
86179
+ }
86180
+ };
86181
+ }
86182
+ offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
85557
86183
  Object.freeze({
85558
86184
  "accessories.getStatus": [{
85559
86185
  name: "deviceId",
@@ -86520,6 +87146,11 @@ Object.freeze({
86520
87146
  form: "single",
86521
87147
  optional: false
86522
87148
  }],
87149
+ "groupActive.getStatus": [{
87150
+ name: "deviceId",
87151
+ form: "single",
87152
+ optional: false
87153
+ }],
86523
87154
  "groupCommands.getStatus": [{
86524
87155
  name: "deviceId",
86525
87156
  form: "single",
@@ -86730,6 +87361,11 @@ Object.freeze({
86730
87361
  form: "single",
86731
87362
  optional: false
86732
87363
  }],
87364
+ "meter.getStatus": [{
87365
+ name: "deviceId",
87366
+ form: "single",
87367
+ optional: false
87368
+ }],
86733
87369
  "motion.getStatus": [{
86734
87370
  name: "deviceId",
86735
87371
  form: "single",
@@ -88337,7 +88973,6 @@ Object.freeze({
88337
88973
  "network-access": "ingress",
88338
88974
  "smtp-provider": "email"
88339
88975
  });
88340
- DeviceType.Camera;
88341
88976
  /**
88342
88977
  * A saved Customize assignment (D936). The schema is the type: callers use
88343
88978
  * `z.infer`, they do not restate the shape.