@camstack/addon-notifiers 1.2.622 → 1.2.623

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
@@ -5578,7 +5578,7 @@ var ZodIssueCode = {
5578
5578
  var ZodFirstPartyTypeKind;
5579
5579
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5580
5580
  //#endregion
5581
- //#region ../types/dist/sleep-bykKrTgu.mjs
5581
+ //#region ../types/dist/sleep--jXS9hWo.mjs
5582
5582
  /**
5583
5583
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5584
5584
  * window to float samples (D455).
@@ -8935,6 +8935,7 @@ var CAP_NAMES = Object.freeze([
8935
8935
  "filesystem-browse",
8936
8936
  "flood",
8937
8937
  "gas",
8938
+ "group-active",
8938
8939
  "group-commands",
8939
8940
  "group-status",
8940
8941
  "heating-valve",
@@ -8957,6 +8958,7 @@ var CAP_NAMES = Object.freeze([
8957
8958
  "login-method",
8958
8959
  "media-player",
8959
8960
  "mesh-network",
8961
+ "meter",
8960
8962
  "metrics-provider",
8961
8963
  "model-convert",
8962
8964
  "model-distributor",
@@ -9755,6 +9757,10 @@ Object.freeze({
9755
9757
  "path": "detected",
9756
9758
  "valueType": "boolean"
9757
9759
  }],
9760
+ "group-active": [{
9761
+ "path": "active",
9762
+ "valueType": "boolean"
9763
+ }],
9758
9764
  "group-status": [
9759
9765
  {
9760
9766
  "path": "included",
@@ -10014,6 +10020,10 @@ Object.freeze({
10014
10020
  ]
10015
10021
  }
10016
10022
  ],
10023
+ "meter": [{
10024
+ "path": "value",
10025
+ "valueType": "number"
10026
+ }],
10017
10027
  "motion": [
10018
10028
  {
10019
10029
  "path": "detected",
@@ -10771,7 +10781,7 @@ function userError(code, params, englishFallback) {
10771
10781
  return new CamstackError(code, params, englishFallback);
10772
10782
  }
10773
10783
  //#endregion
10774
- //#region ../types/dist/err-msg-BXKzRBwg.mjs
10784
+ //#region ../types/dist/err-msg-DIorGYeC.mjs
10775
10785
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10776
10786
  DeviceType["Camera"] = "camera";
10777
10787
  DeviceType["Hub"] = "hub";
@@ -10865,8 +10875,14 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
10865
10875
  DeviceType["PetFeeder"] = "pet-feeder";
10866
10876
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10867
10877
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10868
- * Sources: native Bosch Home Connect, reusable by similar integrations. */
10878
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
10879
+ * A plug-built dishwasher carries running-state and power instead. */
10869
10880
  DeviceType["Dishwasher"] = "dishwasher";
10881
+ /** Washing machine. A plug-built one is running while its power stays
10882
+ * above a threshold. No program cap — that surface is the dishwasher's. */
10883
+ DeviceType["Washer"] = "washer";
10884
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
10885
+ DeviceType["Dryer"] = "dryer";
10870
10886
  return DeviceType;
10871
10887
  }({});
10872
10888
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -11114,7 +11130,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11114
11130
  return DeviceRole;
11115
11131
  }({});
11116
11132
  //#endregion
11117
- //#region ../types/dist/composition-OhpuespA.mjs
11133
+ //#region ../types/dist/composition-Bb8iwHEJ.mjs
11118
11134
  /**
11119
11135
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11120
11136
  *
@@ -11429,6 +11445,13 @@ var table = {
11429
11445
  apply: () => {
11430
11446
  throw new ExpressionEvalError("latchedAt: stateful evaluation context is required");
11431
11447
  }
11448
+ },
11449
+ schmitt: {
11450
+ minArgs: 3,
11451
+ maxArgs: 5,
11452
+ apply: () => {
11453
+ throw new ExpressionEvalError("schmitt: stateful evaluation context is required");
11454
+ }
11432
11455
  }
11433
11456
  };
11434
11457
  Object.freeze(Object.assign(Object.create(null), table));
@@ -11870,6 +11893,99 @@ function validateExpressionSource(src) {
11870
11893
  return null;
11871
11894
  }
11872
11895
  /**
11896
+ * One power reading becomes a washer, dryer or dishwasher (D944 helper).
11897
+ *
11898
+ * Standby of the control board is under 5 W; a motor or a heater is far
11899
+ * above that, so 5 W is the line. A door lock or a short pump spike is not
11900
+ * a cycle, so running starts only after 30 s above the line.
11901
+ *
11902
+ * A wash, dry or dishwasher pause (soak, heat-wait, heater off between
11903
+ * bursts) lasts several minutes. Ending the cycle on the first dip would
11904
+ * report "finished" in the middle of the program, so the stop dwell is
11905
+ * 10 minutes on every type. The number is stored per type so one machine
11906
+ * can move later without moving the others.
11907
+ */
11908
+ var APPLIANCE_TEMPLATE_ID = "appliance";
11909
+ var APPLIANCE_KINDS = [
11910
+ "washer",
11911
+ "dryer",
11912
+ "dishwasher"
11913
+ ];
11914
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
11915
+ /**
11916
+ * Statistics and integrals over a history read (D944).
11917
+ *
11918
+ * Pure: the same points always give the same answer, so a restart recomputes
11919
+ * rather than trusting a buffer. A gauge mean is time-weighted (value × ms),
11920
+ * never the mean of the samples. A hole with no held value is not integrated
11921
+ * as zero. A truncated read is not published.
11922
+ */
11923
+ var STATISTIC_STATS = [
11924
+ "min",
11925
+ "max",
11926
+ "mean",
11927
+ "sum",
11928
+ "count",
11929
+ "change"
11930
+ ];
11931
+ /**
11932
+ * The range a statistics or integral helper asks history for (D944).
11933
+ *
11934
+ * Calendar edges are the hub's timezone, never UTC midnight: a day is 23, 24
11935
+ * or 25 hours across a DST change. A tier is eligible only when its retention
11936
+ * is strictly longer than the longest that range can be. Forever qualifies.
11937
+ * Equal to a tier's retention is a refusal, not a short answer.
11938
+ */
11939
+ var MINUTE_MS = 6e4;
11940
+ var HOUR_MS = 60 * MINUTE_MS;
11941
+ var DAY_MS = 24 * HOUR_MS;
11942
+ var ROLLING_SPANS = [
11943
+ "1h",
11944
+ "24h",
11945
+ "2d",
11946
+ "7d",
11947
+ "30d",
11948
+ "custom"
11949
+ ];
11950
+ var CALENDAR_PERIODS = [
11951
+ "today",
11952
+ "yesterday",
11953
+ "this-week",
11954
+ "this-month"
11955
+ ];
11956
+ var RESET_PERIODS = [
11957
+ "never",
11958
+ "daily",
11959
+ "weekly",
11960
+ "monthly"
11961
+ ];
11962
+ 5 * MINUTE_MS;
11963
+ 24 * HOUR_MS, 2 * DAY_MS, 7 * DAY_MS, 30 * DAY_MS;
11964
+ 7 * DAY_MS, 30 * DAY_MS, 365 * DAY_MS;
11965
+ var rollingWindowObject = object({
11966
+ kind: literal("rolling"),
11967
+ span: _enum(ROLLING_SPANS),
11968
+ customMinutes: number().int().min(5).optional()
11969
+ }).check((payload) => {
11970
+ if (payload.value.span === "custom" && payload.value.customMinutes === void 0) payload.issues.push({
11971
+ code: "custom",
11972
+ message: "a custom range needs a duration in minutes",
11973
+ path: ["customMinutes"],
11974
+ input: payload.value
11975
+ });
11976
+ if (payload.value.span !== "custom" && payload.value.customMinutes !== void 0) payload.issues.push({
11977
+ code: "custom",
11978
+ message: "only a custom range carries a duration",
11979
+ path: ["customMinutes"],
11980
+ input: payload.value
11981
+ });
11982
+ });
11983
+ var RollingWindowSchema = rollingWindowObject;
11984
+ var StatisticWindowSchema = discriminatedUnion("kind", [rollingWindowObject, object({
11985
+ kind: literal("calendar"),
11986
+ period: _enum(CALENDAR_PERIODS)
11987
+ })]);
11988
+ /**
11873
11989
  * A composition source identity: `(addonId, stableId)`, never a numeric device
11874
11990
  * id. Split out so helper schemas can name it without a cycle through
11875
11991
  * `composition.ts` (D659).
@@ -11895,7 +12011,13 @@ var GROUP_TEMPLATE_IDS = [
11895
12011
  "occupancy",
11896
12012
  "binary",
11897
12013
  "switch",
11898
- "light"
12014
+ "light",
12015
+ "fan",
12016
+ "cover",
12017
+ "valve",
12018
+ "lock",
12019
+ "media-player",
12020
+ "climate"
11899
12021
  ];
11900
12022
  var NUMERIC_AGGREGATES = [
11901
12023
  "mean",
@@ -11925,14 +12047,73 @@ var HelperMemberSchema = object({
11925
12047
  cap: string().min(1),
11926
12048
  fieldPath: string().min(1)
11927
12049
  });
11928
- var HelperSpecSchema = discriminatedUnion("kind", [object({
11929
- kind: literal("group"),
11930
- templateId: GroupTemplateIdSchema,
11931
- aggregate: AggregateReducerSchema,
11932
- members: array(HelperMemberSchema).min(1).max(64),
11933
- /** Actuator groups only. Readings leave this unset. */
11934
- active: ActiveCriterionSchema.optional()
11935
- })]);
12050
+ var HelperSpecSchema = discriminatedUnion("kind", [
12051
+ object({
12052
+ kind: literal("group"),
12053
+ templateId: GroupTemplateIdSchema,
12054
+ aggregate: AggregateReducerSchema,
12055
+ members: array(HelperMemberSchema).min(1).max(64),
12056
+ /** Actuator groups only. Readings leave this unset. */
12057
+ active: ActiveCriterionSchema.optional()
12058
+ }),
12059
+ object({
12060
+ kind: literal("appliance"),
12061
+ templateId: literal(APPLIANCE_TEMPLATE_ID),
12062
+ appliance: _enum(APPLIANCE_KINDS),
12063
+ thresholdWatts: number().positive(),
12064
+ startDwellMs: number().int().nonnegative(),
12065
+ stopDwellMs: number().int().nonnegative(),
12066
+ member: HelperMemberSchema
12067
+ }),
12068
+ object({
12069
+ kind: literal("statistic"),
12070
+ stat: _enum(STATISTIC_STATS),
12071
+ window: StatisticWindowSchema,
12072
+ source: HelperMemberSchema
12073
+ }),
12074
+ object({
12075
+ kind: literal("integral"),
12076
+ reset: _enum(RESET_PERIODS),
12077
+ output: _enum(["energy", "meter"]),
12078
+ source: HelperMemberSchema
12079
+ }),
12080
+ object({
12081
+ kind: literal("threshold"),
12082
+ source: HelperMemberSchema,
12083
+ high: number(),
12084
+ low: number(),
12085
+ onForMs: number().int().nonnegative(),
12086
+ offForMs: number().int().nonnegative()
12087
+ }),
12088
+ object({
12089
+ kind: literal("arithmetic"),
12090
+ op: _enum([
12091
+ "sum",
12092
+ "difference",
12093
+ "ratio"
12094
+ ]),
12095
+ inputs: array(HelperMemberSchema).min(2).max(64)
12096
+ }),
12097
+ object({
12098
+ kind: literal("expression"),
12099
+ expr: string().min(1),
12100
+ inputs: array(object({
12101
+ name: string().min(1),
12102
+ member: HelperMemberSchema
12103
+ })).min(1).max(64),
12104
+ output: _enum(["number", "boolean"])
12105
+ }),
12106
+ object({
12107
+ kind: literal("derivative"),
12108
+ window: RollingWindowSchema,
12109
+ per: _enum([
12110
+ "second",
12111
+ "minute",
12112
+ "hour"
12113
+ ]),
12114
+ source: HelperMemberSchema
12115
+ })
12116
+ ]);
11936
12117
  /** A composed device's stableId is this prefix plus the block id. */
11937
12118
  var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11938
12119
  /** Longest snippet a `code` source may carry (slice 4). */
@@ -11993,6 +12174,12 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11993
12174
  "all"
11994
12175
  ]),
11995
12176
  reads: array(CompositionFieldReadSchema).min(1).max(64),
12177
+ /**
12178
+ * Set when the reads are an enum state, not a yes/no: the listed states
12179
+ * count as active (a cover is `open`). A state outside the list is inactive;
12180
+ * a read that is not a state at all is left out. Only `any` / `all` use it.
12181
+ */
12182
+ activeWhen: array(string().min(1)).min(1).optional(),
11996
12183
  /** `value` is the reading; the others are the member counts. */
11997
12184
  publish: _enum([
11998
12185
  "value",
@@ -12006,6 +12193,26 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12006
12193
  "omit",
12007
12194
  "offline"
12008
12195
  ])
12196
+ }),
12197
+ object({
12198
+ kind: literal("series"),
12199
+ read: CompositionFieldReadSchema,
12200
+ /** `value` is the statistic or the integral. The others are the gap counts. */
12201
+ publish: _enum([
12202
+ "value",
12203
+ "included",
12204
+ "excluded",
12205
+ "total"
12206
+ ]),
12207
+ /**
12208
+ * What an unanswered `value` does. Counts ignore this. `offline` leaves the
12209
+ * device unseeded until history answers, so a restart cannot publish 0.
12210
+ */
12211
+ empty: _enum([
12212
+ "null",
12213
+ "omit",
12214
+ "offline"
12215
+ ])
12009
12216
  })
12010
12217
  ]);
12011
12218
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
@@ -17301,7 +17508,22 @@ var BrokerProviderInfoSchema = object({
17301
17508
  */
17302
17509
  interactiveConnectPath: string().min(1).optional(),
17303
17510
  /** Relative hub path to disconnect the linked account (same popup pattern). */
17304
- interactiveDisconnectPath: string().min(1).optional()
17511
+ interactiveDisconnectPath: string().min(1).optional(),
17512
+ /**
17513
+ * The kind signs in through a server-held interactive session instead of
17514
+ * (or before) typed credentials. `basePath` is a hub-relative route set
17515
+ * served by the owning addon (`/addon/<id>/...`) speaking the sign-in
17516
+ * contract: `POST {basePath}/start` (body: the form's settings) answers
17517
+ * `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
17518
+ * `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
17519
+ * 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
17520
+ * (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
17521
+ * existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
17522
+ * (`{ sessionId }`) drops the session. The dialog that edits a broker
17523
+ * offers it as the default path; the fields the session fills are
17524
+ * marked `filledBySignIn` in the kind's settings schema.
17525
+ */
17526
+ signIn: object({ basePath: string().min(1) }).optional()
17305
17527
  }))
17306
17528
  });
17307
17529
  var ListInputSchema = object({
@@ -18300,7 +18522,8 @@ var CompositionProblemSchema = object({
18300
18522
  "native-capability-replace",
18301
18523
  "mixed-quantity",
18302
18524
  "helper-mismatch",
18303
- "member-constraint"
18525
+ "member-constraint",
18526
+ "retention"
18304
18527
  ]),
18305
18528
  path: string(),
18306
18529
  message: string()
@@ -18534,13 +18757,17 @@ Object.freeze({
18534
18757
  latchedAt: {
18535
18758
  kind: "fixed",
18536
18759
  result: ["number", "null"]
18760
+ },
18761
+ schmitt: {
18762
+ kind: "fixed",
18763
+ result: ["boolean", "null"]
18537
18764
  }
18538
18765
  });
18539
18766
  /**
18540
18767
  * One catalog row per base reading a group helper can build (D944 §3).
18541
18768
  * Data, not a form default written into the block.
18542
18769
  */
18543
- var NOW = {
18770
+ var NOW$2 = {
18544
18771
  fieldPath: "lastFetchedAt",
18545
18772
  expr: "now"
18546
18773
  };
@@ -18554,6 +18781,8 @@ function numeric(input) {
18554
18781
  family: "numeric",
18555
18782
  deviceType: DeviceType.Sensor,
18556
18783
  memberDeviceType: DeviceType.Sensor,
18784
+ memberDeviceTypes: [DeviceType.Sensor],
18785
+ activeWhen: void 0,
18557
18786
  commandCaps: [],
18558
18787
  baseUnit: baseUnitOf(input.quantity),
18559
18788
  aggregates: NUMERIC_AGGREGATES,
@@ -18569,6 +18798,8 @@ function binary(input) {
18569
18798
  family: "binary",
18570
18799
  deviceType: DeviceType.Sensor,
18571
18800
  memberDeviceType: DeviceType.Sensor,
18801
+ memberDeviceTypes: [DeviceType.Sensor],
18802
+ activeWhen: void 0,
18572
18803
  commandCaps: [],
18573
18804
  quantity: "none",
18574
18805
  baseUnit: void 0,
@@ -18586,7 +18817,7 @@ var GROUP_TEMPLATES = [
18586
18817
  quantity: "temperature",
18587
18818
  defaultAggregate: "mean",
18588
18819
  empty: "null",
18589
- companions: [NOW]
18820
+ companions: [NOW$2]
18590
18821
  }),
18591
18822
  numeric({
18592
18823
  id: "humidity",
@@ -18597,7 +18828,7 @@ var GROUP_TEMPLATES = [
18597
18828
  quantity: "humidity",
18598
18829
  defaultAggregate: "mean",
18599
18830
  empty: "null",
18600
- companions: [NOW]
18831
+ companions: [NOW$2]
18601
18832
  }),
18602
18833
  numeric({
18603
18834
  id: "pressure",
@@ -18608,7 +18839,7 @@ var GROUP_TEMPLATES = [
18608
18839
  quantity: "pressure",
18609
18840
  defaultAggregate: "mean",
18610
18841
  empty: "null",
18611
- companions: [NOW]
18842
+ companions: [NOW$2]
18612
18843
  }),
18613
18844
  numeric({
18614
18845
  id: "illuminance",
@@ -18619,7 +18850,7 @@ var GROUP_TEMPLATES = [
18619
18850
  quantity: "illuminance",
18620
18851
  defaultAggregate: "mean",
18621
18852
  empty: "null",
18622
- companions: [NOW]
18853
+ companions: [NOW$2]
18623
18854
  }),
18624
18855
  numeric({
18625
18856
  id: "power",
@@ -18630,7 +18861,7 @@ var GROUP_TEMPLATES = [
18630
18861
  quantity: "power",
18631
18862
  defaultAggregate: "sum",
18632
18863
  empty: "omit",
18633
- companions: [NOW]
18864
+ companions: [NOW$2]
18634
18865
  }),
18635
18866
  numeric({
18636
18867
  id: "energy",
@@ -18641,7 +18872,7 @@ var GROUP_TEMPLATES = [
18641
18872
  quantity: "energy",
18642
18873
  defaultAggregate: "sum",
18643
18874
  empty: "omit",
18644
- companions: [NOW]
18875
+ companions: [NOW$2]
18645
18876
  }),
18646
18877
  numeric({
18647
18878
  id: "battery",
@@ -18676,7 +18907,7 @@ var GROUP_TEMPLATES = [
18676
18907
  quantity: "device",
18677
18908
  defaultAggregate: "mean",
18678
18909
  empty: "null",
18679
- companions: [NOW]
18910
+ companions: [NOW$2]
18680
18911
  }),
18681
18912
  binary({
18682
18913
  id: "motion",
@@ -18770,6 +19001,11 @@ var GROUP_TEMPLATES = [
18770
19001
  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.",
18771
19002
  deviceType: DeviceType.Switch,
18772
19003
  outputCap: "switch",
19004
+ outputField: "on",
19005
+ member: {
19006
+ cap: "switch",
19007
+ fieldPath: "on"
19008
+ },
18773
19009
  commandCaps: ["switch"]
18774
19010
  }),
18775
19011
  actuator({
@@ -18777,34 +19013,136 @@ var GROUP_TEMPLATES = [
18777
19013
  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.",
18778
19014
  deviceType: DeviceType.Light,
18779
19015
  outputCap: "switch",
19016
+ outputField: "on",
19017
+ member: {
19018
+ cap: "switch",
19019
+ fieldPath: "on"
19020
+ },
18780
19021
  commandCaps: [
18781
19022
  "switch",
18782
19023
  "brightness",
18783
19024
  "color"
18784
19025
  ]
19026
+ }),
19027
+ actuator({
19028
+ id: "fan",
19029
+ 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.",
19030
+ deviceType: DeviceType.Fan,
19031
+ outputCap: "switch",
19032
+ outputField: "on",
19033
+ member: {
19034
+ cap: "switch",
19035
+ fieldPath: "on"
19036
+ },
19037
+ commandCaps: ["switch", "fan-control"]
19038
+ }),
19039
+ actuator({
19040
+ id: "cover",
19041
+ 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.",
19042
+ deviceType: DeviceType.Cover,
19043
+ outputCap: "group-active",
19044
+ outputField: "active",
19045
+ member: {
19046
+ cap: "cover",
19047
+ fieldPath: "state"
19048
+ },
19049
+ activeWhen: [
19050
+ "open",
19051
+ "opening",
19052
+ "stopped"
19053
+ ],
19054
+ commandCaps: ["cover"]
19055
+ }),
19056
+ actuator({
19057
+ id: "valve",
19058
+ 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.",
19059
+ deviceType: DeviceType.Valve,
19060
+ outputCap: "group-active",
19061
+ outputField: "active",
19062
+ member: {
19063
+ cap: "valve",
19064
+ fieldPath: "state"
19065
+ },
19066
+ activeWhen: [
19067
+ "open",
19068
+ "opening",
19069
+ "stopped"
19070
+ ],
19071
+ commandCaps: ["valve"]
19072
+ }),
19073
+ actuator({
19074
+ id: "lock",
19075
+ 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.",
19076
+ deviceType: DeviceType.Lock,
19077
+ outputCap: "group-active",
19078
+ outputField: "active",
19079
+ member: {
19080
+ cap: "lock-control",
19081
+ fieldPath: "state"
19082
+ },
19083
+ activeWhen: ["locked"],
19084
+ commandCaps: ["lock-control"]
19085
+ }),
19086
+ actuator({
19087
+ id: "media-player",
19088
+ 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.",
19089
+ deviceType: DeviceType.MediaPlayer,
19090
+ outputCap: "group-active",
19091
+ outputField: "active",
19092
+ member: {
19093
+ cap: "media-player",
19094
+ fieldPath: "state"
19095
+ },
19096
+ activeWhen: ["playing", "buffering"],
19097
+ commandCaps: ["media-player"]
19098
+ }),
19099
+ actuator({
19100
+ id: "climate",
19101
+ 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.",
19102
+ deviceType: DeviceType.Thermostat,
19103
+ memberDeviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
19104
+ outputCap: "group-active",
19105
+ outputField: "active",
19106
+ member: {
19107
+ cap: "climate-control",
19108
+ fieldPath: "mode"
19109
+ },
19110
+ activeWhen: [
19111
+ "heat",
19112
+ "cool",
19113
+ "auto",
19114
+ "heat_cool",
19115
+ "fan_only",
19116
+ "dry"
19117
+ ],
19118
+ commandCaps: ["climate-control"]
18785
19119
  })
18786
19120
  ];
18787
19121
  function actuator(input) {
18788
19122
  return {
18789
- ...input,
19123
+ id: input.id,
19124
+ summary: input.summary,
19125
+ deviceType: input.deviceType,
19126
+ outputCap: input.outputCap,
19127
+ outputField: input.outputField,
19128
+ commandCaps: input.commandCaps,
18790
19129
  family: "actuator",
18791
19130
  role: void 0,
18792
19131
  memberDeviceType: input.deviceType,
18793
- outputField: "on",
19132
+ memberDeviceTypes: input.memberDeviceTypes ?? [input.deviceType],
19133
+ activeWhen: input.activeWhen,
18794
19134
  quantity: "none",
18795
19135
  baseUnit: void 0,
18796
19136
  aggregates: BINARY_AGGREGATES,
18797
19137
  defaultAggregate: "any",
18798
19138
  empty: "offline",
18799
- accept: [{
18800
- cap: "switch",
18801
- fieldPath: "on"
18802
- }],
19139
+ accept: [input.member],
18803
19140
  companions: [CHANGED]
18804
19141
  };
18805
19142
  }
18806
19143
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18807
19144
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
19145
+ DeviceType.Camera;
18808
19146
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18809
19147
  /**
18810
19148
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -43819,7 +44157,7 @@ var NetworkAccessStatusSchema = object({
43819
44157
  });
43820
44158
  /**
43821
44159
  * Optional, richer endpoint shape returned by providers that expose
43822
- * MORE than one ingress concurrently (Tailscale Ingress with mixed
44160
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
43823
44161
  * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
43824
44162
  * the originating provider config (mode + sourcePort) so the
43825
44163
  * orchestrator UI can label rows distinctly. Providers that expose only
@@ -46484,7 +46822,20 @@ var NcRuleInputSchema = object({
46484
46822
  * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
46485
46823
  * path does not run Zod defaults.
46486
46824
  */
46487
- groupingSec: number().int().min(5).max(60).optional()
46825
+ groupingSec: number().int().min(5).max(60).optional(),
46826
+ /**
46827
+ * ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
46828
+ * carries "Correct" / "Wrong" buttons (and, where the channel has room, a
46829
+ * "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
46830
+ * same mark the Library writes, and records whose notification it was and
46831
+ * when. A channel with no buttons gets the same two choices as links.
46832
+ *
46833
+ * `.optional()`, never `.default()` (a Zod default does not run on the addon
46834
+ * cap path): absent and `false` both mean off, which is what every rule
46835
+ * authored before this field meant. An editor turns it off by writing
46836
+ * `false`, so it needs no `clear` key.
46837
+ */
46838
+ askVerdict: boolean().optional()
46488
46839
  });
46489
46840
  /**
46490
46841
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -56941,7 +57292,7 @@ var BatteryStatusSchema = object({
56941
57292
  */
56942
57293
  binary: boolean().optional().meta({ label: "Binary indicator" })
56943
57294
  });
56944
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat;
57295
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter;
56945
57296
  method(object({
56946
57297
  deviceId: number(),
56947
57298
  /** Bound on the wait. Sensible range 3000–10000ms. */
@@ -58685,7 +59036,13 @@ results: array(object({
58685
59036
  /** Ms epoch of the attempt. */
58686
59037
  at: number()
58687
59038
  })) });
58688
- DeviceType.Switch, DeviceType.Light;
59039
+ DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Cover, DeviceType.Valve, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.Thermostat, DeviceType.Climate;
59040
+ object({
59041
+ active: boolean().meta({ label: "Active" }),
59042
+ /** Ms epoch when the slice was last updated. */
59043
+ lastChangedAt: number()
59044
+ });
59045
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.Climate, DeviceType.Thermostat;
58689
59046
  object({
58690
59047
  /** Members whose current value entered the aggregate. */
58691
59048
  included: number().int().nonnegative().meta(readingMeta("count", {
@@ -58703,7 +59060,7 @@ object({
58703
59060
  label: "Members"
58704
59061
  }))
58705
59062
  });
58706
- DeviceType.Sensor, DeviceType.Switch, DeviceType.Light;
59063
+ DeviceType.Sensor, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Cover, DeviceType.Valve, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.Thermostat, DeviceType.Climate;
58707
59064
  object({
58708
59065
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
58709
59066
  position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
@@ -59610,7 +59967,14 @@ DeviceType.Lock, method(object({
59610
59967
  auth: "protected"
59611
59968
  }), method(object({
59612
59969
  deviceId: number().int().nonnegative(),
59613
- code: string().min(1).optional()
59970
+ code: string().min(1).optional(),
59971
+ /**
59972
+ * Honoured by a helper lock GROUP only (D944): unlocking several doors
59973
+ * with one call is refused unless the caller says it was confirmed. A
59974
+ * single lock ignores it. The refusal lives in the callee, so no
59975
+ * automation or API caller reaches the doors by skipping a dialog.
59976
+ */
59977
+ confirm: boolean().optional()
59614
59978
  }), _void(), {
59615
59979
  kind: "mutation",
59616
59980
  auth: "protected"
@@ -59618,6 +59982,11 @@ DeviceType.Lock, method(object({
59618
59982
  kind: "mutation",
59619
59983
  auth: "protected"
59620
59984
  });
59985
+ object({
59986
+ value: number().nullable().meta(readingMeta("device", { label: "Total" })),
59987
+ lastFetchedAt: number()
59988
+ });
59989
+ DeviceType.Sensor;
59621
59990
  /**
59622
59991
  * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
59623
59992
  * Apple TV, Spotify, Roku, generic OTT receivers, …) on
@@ -61525,9 +61894,9 @@ object({
61525
61894
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
61526
61895
  kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
61527
61896
  /** Voltage in volts. */
61528
- volts: number().optional().meta(readingMeta("voltage")),
61897
+ volts: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
61529
61898
  /** Current in amperes. */
61530
- amps: number().optional().meta(readingMeta("current")),
61899
+ amps: number().optional().meta(readingMeta("current", { label: "Current" })),
61531
61900
  /** Apparent power in volt-amperes. */
61532
61901
  va: number().optional().meta(readingMeta("apparent-power")),
61533
61902
  /** Power factor (0..1) if reported. */
@@ -61535,7 +61904,23 @@ object({
61535
61904
  /** Ms epoch when the slice was last updated. */
61536
61905
  lastFetchedAt: number()
61537
61906
  });
61538
- DeviceType.Sensor;
61907
+ [
61908
+ DeviceType.Sensor,
61909
+ DeviceType.Switch,
61910
+ DeviceType.Light,
61911
+ DeviceType.Fan,
61912
+ DeviceType.Climate,
61913
+ DeviceType.Thermostat,
61914
+ DeviceType.Humidifier,
61915
+ DeviceType.WaterHeater,
61916
+ DeviceType.MediaPlayer,
61917
+ DeviceType.Washer,
61918
+ DeviceType.Dryer,
61919
+ DeviceType.Dishwasher,
61920
+ DeviceType.Vacuum,
61921
+ DeviceType.LawnMower,
61922
+ DeviceType.PetFeeder
61923
+ ].filter((type) => type !== DeviceType.Sensor);
61539
61924
  /**
61540
61925
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
61541
61926
  * on `DeviceType.Presence`. Read-only — no setters: presence is
@@ -61982,7 +62367,7 @@ object({
61982
62367
  phase: string().nullable()
61983
62368
  });
61984
62369
  Object.values(DeviceType);
61985
- DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
62370
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
61986
62371
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
61987
62372
  kind: "mutation",
61988
62373
  auth: "admin"
@@ -64021,7 +64406,7 @@ object({
64021
64406
  /** Ms epoch of the last state change. Useful for UI "X minutes ago". */
64022
64407
  lastChangedAt: number()
64023
64408
  });
64024
- DeviceType.Switch, DeviceType.Siren, DeviceType.Light, method(object({
64409
+ DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, method(object({
64025
64410
  deviceId: number(),
64026
64411
  on: boolean()
64027
64412
  }), _void(), {
@@ -65713,8 +66098,9 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
65713
66098
  kind: "mutation",
65714
66099
  auth: "admin"
65715
66100
  });
66101
+ DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
65716
66102
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
65717
- 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;
66103
+ 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;
65718
66104
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
65719
66105
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
65720
66106
  new Set(CAP_NAMES);
@@ -65767,7 +66153,7 @@ var AccessoryKind = {
65767
66153
  PrivacyMask: DeviceRole.PrivacyMask
65768
66154
  };
65769
66155
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
65770
- 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;
66156
+ 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;
65771
66157
  new Set(Object.values(DeviceType));
65772
66158
  Object.freeze({
65773
66159
  "accessories.getStatus": {
@@ -68990,6 +69376,13 @@ Object.freeze({
68990
69376
  access: "view",
68991
69377
  role: "viewer"
68992
69378
  },
69379
+ "groupActive.getStatus": {
69380
+ capName: "group-active",
69381
+ capScope: "device",
69382
+ addonId: null,
69383
+ access: "view",
69384
+ role: "viewer"
69385
+ },
68993
69386
  "groupCommands.getStatus": {
68994
69387
  capName: "group-commands",
68995
69388
  capScope: "device",
@@ -69956,6 +70349,13 @@ Object.freeze({
69956
70349
  access: "create",
69957
70350
  role: "admin"
69958
70351
  },
70352
+ "meter.getStatus": {
70353
+ capName: "meter",
70354
+ capScope: "device",
70355
+ addonId: null,
70356
+ access: "view",
70357
+ role: "viewer"
70358
+ },
69959
70359
  "metricsProvider.collectSnapshot": {
69960
70360
  capName: "metrics-provider",
69961
70361
  capScope: "system",
@@ -78054,7 +78454,39 @@ var APPLIANCE_CAP_VOCABULARY = {
78054
78454
  phrase: t("When {device} finishes", "Quando {device} finisce"),
78055
78455
  icon: "square"
78056
78456
  }
78057
- }
78457
+ },
78458
+ overrides: { byType: {
78459
+ [DeviceType.Washer]: {
78460
+ state: { running: {
78461
+ whenTrue: t("When the washing machine starts", "Quando la lavatrice inizia"),
78462
+ whenFalse: t("When the washing machine has finished", "Quando la lavatrice ha finito")
78463
+ } },
78464
+ events: {
78465
+ onStarted: { phrase: t("When the washing machine starts", "Quando la lavatrice inizia") },
78466
+ onFinished: { phrase: t("When the washing machine has finished", "Quando la lavatrice ha finito") }
78467
+ }
78468
+ },
78469
+ [DeviceType.Dryer]: {
78470
+ state: { running: {
78471
+ whenTrue: t("When the dryer starts", "Quando l'asciugatrice inizia"),
78472
+ whenFalse: t("When the dryer has finished", "Quando l'asciugatrice ha finito")
78473
+ } },
78474
+ events: {
78475
+ onStarted: { phrase: t("When the dryer starts", "Quando l'asciugatrice inizia") },
78476
+ onFinished: { phrase: t("When the dryer has finished", "Quando l'asciugatrice ha finito") }
78477
+ }
78478
+ },
78479
+ [DeviceType.Dishwasher]: {
78480
+ state: { running: {
78481
+ whenTrue: t("When the dishwasher starts", "Quando la lavastoviglie inizia"),
78482
+ whenFalse: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito")
78483
+ } },
78484
+ events: {
78485
+ onStarted: { phrase: t("When the dishwasher starts", "Quando la lavastoviglie inizia") },
78486
+ onFinished: { phrase: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito") }
78487
+ }
78488
+ }
78489
+ } }
78058
78490
  },
78059
78491
  update: {
78060
78492
  scope: "device",
@@ -78826,6 +79258,10 @@ var SENSOR_CAP_VOCABULARY = {
78826
79258
  icon: "flame"
78827
79259
  }]
78828
79260
  },
79261
+ "group-active": {
79262
+ scope: "device",
79263
+ noun: t("Group state", "Stato del gruppo")
79264
+ },
78829
79265
  "group-commands": {
78830
79266
  scope: "device",
78831
79267
  noun: t("Group command", "Comando di gruppo")
@@ -78882,6 +79318,17 @@ var SENSOR_CAP_VOCABULARY = {
78882
79318
  }],
78883
79319
  events: { onMotionChanged: { notOperatorFacing: "the `detected` state trigger phrases both edges, from the mirror that survives a dropped event (D49)" } }
78884
79320
  },
79321
+ meter: {
79322
+ scope: "device",
79323
+ noun: t("Meter", "Contatore"),
79324
+ state: [{
79325
+ kind: "numeric",
79326
+ field: "value",
79327
+ quantity: t("the total", "il totale"),
79328
+ label: t("Total", "Totale"),
79329
+ icon: "gauge"
79330
+ }]
79331
+ },
78885
79332
  "numeric-sensor": {
78886
79333
  scope: "device",
78887
79334
  noun: t("Sensor", "Sensore"),
@@ -79354,8 +79801,47 @@ function projectAction(cap, action) {
79354
79801
  };
79355
79802
  }
79356
79803
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
79357
- 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;
79804
+ 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;
79358
79805
  DeviceFeature.BatteryOperated;
79806
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
79807
+ 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.";
79808
+ 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.";
79809
+ var ALEXA_REASON = "Alexa has no laundry category in this export, and neither power nor running has an Alexa interface.";
79810
+ var GOOGLE_REASON = "Google WASHER, DRYER and DISHWASHER require RunCycle. A power threshold is not that trait, and this export does not publish RunCycle.";
79811
+ 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.";
79812
+ function withheld(reason) {
79813
+ return {
79814
+ [DeviceType.Washer]: {
79815
+ exported: false,
79816
+ reason
79817
+ },
79818
+ [DeviceType.Dryer]: {
79819
+ exported: false,
79820
+ reason
79821
+ },
79822
+ [DeviceType.Dishwasher]: {
79823
+ exported: false,
79824
+ reason
79825
+ }
79826
+ };
79827
+ }
79828
+ function offered(received) {
79829
+ return {
79830
+ [DeviceType.Washer]: {
79831
+ exported: true,
79832
+ as: received
79833
+ },
79834
+ [DeviceType.Dryer]: {
79835
+ exported: true,
79836
+ as: received
79837
+ },
79838
+ [DeviceType.Dishwasher]: {
79839
+ exported: true,
79840
+ as: received
79841
+ }
79842
+ };
79843
+ }
79844
+ offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
79359
79845
  Object.freeze({
79360
79846
  "accessories.getStatus": [{
79361
79847
  name: "deviceId",
@@ -80322,6 +80808,11 @@ Object.freeze({
80322
80808
  form: "single",
80323
80809
  optional: false
80324
80810
  }],
80811
+ "groupActive.getStatus": [{
80812
+ name: "deviceId",
80813
+ form: "single",
80814
+ optional: false
80815
+ }],
80325
80816
  "groupCommands.getStatus": [{
80326
80817
  name: "deviceId",
80327
80818
  form: "single",
@@ -80532,6 +81023,11 @@ Object.freeze({
80532
81023
  form: "single",
80533
81024
  optional: false
80534
81025
  }],
81026
+ "meter.getStatus": [{
81027
+ name: "deviceId",
81028
+ form: "single",
81029
+ optional: false
81030
+ }],
80535
81031
  "motion.getStatus": [{
80536
81032
  name: "deviceId",
80537
81033
  form: "single",
@@ -82139,7 +82635,6 @@ Object.freeze({
82139
82635
  "network-access": "ingress",
82140
82636
  "smtp-provider": "email"
82141
82637
  });
82142
- DeviceType.Camera;
82143
82638
  /**
82144
82639
  * A saved Customize assignment (D936). The schema is the type: callers use
82145
82640
  * `z.infer`, they do not restate the shape.
@@ -83888,7 +84383,7 @@ function buildEmailDescriptor(providers = []) {
83888
84383
  id: "email",
83889
84384
  title: "Email",
83890
84385
  titleKey: "notifiers:emailDescriptor.s_email.title",
83891
- description: "Send via a Connectivity → Email provider (Google Services or SMTP). Install and Verify a provider first.",
84386
+ description: "Send via a Connectivity → Email provider (Google or Email (SMTP)). Install and Verify a provider first.",
83892
84387
  descriptionKey: "notifiers:emailDescriptor.s_email.description",
83893
84388
  fields: [
83894
84389
  {
@@ -83905,7 +84400,7 @@ function buildEmailDescriptor(providers = []) {
83905
84400
  key: "smtpAddonId",
83906
84401
  label: "Email provider",
83907
84402
  labelKey: "notifiers:emailDescriptor.s_email.smtpAddonId.label",
83908
- description: options.length === 0 ? "No Email provider installed. Open Connectivity → Email, install Google Services or SMTP, then Verify." : "Which Connectivity → Email entry delivers this target. Verify it under Connectivity → Email if the label says not verified.",
84403
+ description: options.length === 0 ? "No Email provider installed. Open Connectivity → Email, install Google or Email (SMTP), then Verify." : "Which Connectivity → Email entry delivers this target. Verify it under Connectivity → Email if the label says not verified.",
83909
84404
  required: true,
83910
84405
  options
83911
84406
  },
@@ -86205,7 +86700,7 @@ var HTTP_NOTIFIER_ADAPTERS = [
86205
86700
  */
86206
86701
  function buildNotifierAdapters(smtp) {
86207
86702
  const emailSmtp = smtp ?? { async sendEmail() {
86208
- throw new Error("email: no smtp-provider client — install Google Services or SMTP Email under Connectivity → Email and Verify it");
86703
+ throw new Error("email: no smtp-provider client — install Google or Email (SMTP) under Connectivity → Email and Verify it");
86209
86704
  } };
86210
86705
  return [...HTTP_NOTIFIER_ADAPTERS, createEmailAdapter(emailSmtp)];
86211
86706
  }