@camstack/addon-provider-tuya 0.2.146 → 0.2.147

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
@@ -6261,7 +6261,7 @@ var ZodIssueCode = {
6261
6261
  var ZodFirstPartyTypeKind;
6262
6262
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6263
6263
  //#endregion
6264
- //#region ../types/dist/sleep-bykKrTgu.mjs
6264
+ //#region ../types/dist/sleep--jXS9hWo.mjs
6265
6265
  /**
6266
6266
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6267
6267
  * window to float samples (D455).
@@ -9454,6 +9454,7 @@ var CAP_NAMES = Object.freeze([
9454
9454
  "filesystem-browse",
9455
9455
  "flood",
9456
9456
  "gas",
9457
+ "group-active",
9457
9458
  "group-commands",
9458
9459
  "group-status",
9459
9460
  "heating-valve",
@@ -9476,6 +9477,7 @@ var CAP_NAMES = Object.freeze([
9476
9477
  "login-method",
9477
9478
  "media-player",
9478
9479
  "mesh-network",
9480
+ "meter",
9479
9481
  "metrics-provider",
9480
9482
  "model-convert",
9481
9483
  "model-distributor",
@@ -10274,6 +10276,10 @@ Object.freeze({
10274
10276
  "path": "detected",
10275
10277
  "valueType": "boolean"
10276
10278
  }],
10279
+ "group-active": [{
10280
+ "path": "active",
10281
+ "valueType": "boolean"
10282
+ }],
10277
10283
  "group-status": [
10278
10284
  {
10279
10285
  "path": "included",
@@ -10533,6 +10539,10 @@ Object.freeze({
10533
10539
  ]
10534
10540
  }
10535
10541
  ],
10542
+ "meter": [{
10543
+ "path": "value",
10544
+ "valueType": "number"
10545
+ }],
10536
10546
  "motion": [
10537
10547
  {
10538
10548
  "path": "detected",
@@ -11276,7 +11286,7 @@ object({
11276
11286
  params: record(string(), union([string(), number()])).optional()
11277
11287
  });
11278
11288
  //#endregion
11279
- //#region ../types/dist/err-msg-BXKzRBwg.mjs
11289
+ //#region ../types/dist/err-msg-DIorGYeC.mjs
11280
11290
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
11281
11291
  DeviceType["Camera"] = "camera";
11282
11292
  DeviceType["Hub"] = "hub";
@@ -11370,8 +11380,14 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
11370
11380
  DeviceType["PetFeeder"] = "pet-feeder";
11371
11381
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
11372
11382
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
11373
- * Sources: native Bosch Home Connect, reusable by similar integrations. */
11383
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
11384
+ * A plug-built dishwasher carries running-state and power instead. */
11374
11385
  DeviceType["Dishwasher"] = "dishwasher";
11386
+ /** Washing machine. A plug-built one is running while its power stays
11387
+ * above a threshold. No program cap — that surface is the dishwasher's. */
11388
+ DeviceType["Washer"] = "washer";
11389
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
11390
+ DeviceType["Dryer"] = "dryer";
11375
11391
  return DeviceType;
11376
11392
  }({});
11377
11393
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -11629,7 +11645,7 @@ function errMsg(err) {
11629
11645
  return String(err);
11630
11646
  }
11631
11647
  //#endregion
11632
- //#region ../types/dist/composition-OhpuespA.mjs
11648
+ //#region ../types/dist/composition-Bb8iwHEJ.mjs
11633
11649
  /**
11634
11650
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11635
11651
  *
@@ -11944,6 +11960,13 @@ var table = {
11944
11960
  apply: () => {
11945
11961
  throw new ExpressionEvalError("latchedAt: stateful evaluation context is required");
11946
11962
  }
11963
+ },
11964
+ schmitt: {
11965
+ minArgs: 3,
11966
+ maxArgs: 5,
11967
+ apply: () => {
11968
+ throw new ExpressionEvalError("schmitt: stateful evaluation context is required");
11969
+ }
11947
11970
  }
11948
11971
  };
11949
11972
  Object.freeze(Object.assign(Object.create(null), table));
@@ -12385,6 +12408,99 @@ function validateExpressionSource(src) {
12385
12408
  return null;
12386
12409
  }
12387
12410
  /**
12411
+ * One power reading becomes a washer, dryer or dishwasher (D944 helper).
12412
+ *
12413
+ * Standby of the control board is under 5 W; a motor or a heater is far
12414
+ * above that, so 5 W is the line. A door lock or a short pump spike is not
12415
+ * a cycle, so running starts only after 30 s above the line.
12416
+ *
12417
+ * A wash, dry or dishwasher pause (soak, heat-wait, heater off between
12418
+ * bursts) lasts several minutes. Ending the cycle on the first dip would
12419
+ * report "finished" in the middle of the program, so the stop dwell is
12420
+ * 10 minutes on every type. The number is stored per type so one machine
12421
+ * can move later without moving the others.
12422
+ */
12423
+ var APPLIANCE_TEMPLATE_ID = "appliance";
12424
+ var APPLIANCE_KINDS = [
12425
+ "washer",
12426
+ "dryer",
12427
+ "dishwasher"
12428
+ ];
12429
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
12430
+ /**
12431
+ * Statistics and integrals over a history read (D944).
12432
+ *
12433
+ * Pure: the same points always give the same answer, so a restart recomputes
12434
+ * rather than trusting a buffer. A gauge mean is time-weighted (value × ms),
12435
+ * never the mean of the samples. A hole with no held value is not integrated
12436
+ * as zero. A truncated read is not published.
12437
+ */
12438
+ var STATISTIC_STATS = [
12439
+ "min",
12440
+ "max",
12441
+ "mean",
12442
+ "sum",
12443
+ "count",
12444
+ "change"
12445
+ ];
12446
+ /**
12447
+ * The range a statistics or integral helper asks history for (D944).
12448
+ *
12449
+ * Calendar edges are the hub's timezone, never UTC midnight: a day is 23, 24
12450
+ * or 25 hours across a DST change. A tier is eligible only when its retention
12451
+ * is strictly longer than the longest that range can be. Forever qualifies.
12452
+ * Equal to a tier's retention is a refusal, not a short answer.
12453
+ */
12454
+ var MINUTE_MS = 6e4;
12455
+ var HOUR_MS = 60 * MINUTE_MS;
12456
+ var DAY_MS = 24 * HOUR_MS;
12457
+ var ROLLING_SPANS = [
12458
+ "1h",
12459
+ "24h",
12460
+ "2d",
12461
+ "7d",
12462
+ "30d",
12463
+ "custom"
12464
+ ];
12465
+ var CALENDAR_PERIODS = [
12466
+ "today",
12467
+ "yesterday",
12468
+ "this-week",
12469
+ "this-month"
12470
+ ];
12471
+ var RESET_PERIODS = [
12472
+ "never",
12473
+ "daily",
12474
+ "weekly",
12475
+ "monthly"
12476
+ ];
12477
+ 5 * MINUTE_MS;
12478
+ 24 * HOUR_MS, 2 * DAY_MS, 7 * DAY_MS, 30 * DAY_MS;
12479
+ 7 * DAY_MS, 30 * DAY_MS, 365 * DAY_MS;
12480
+ var rollingWindowObject = object({
12481
+ kind: literal("rolling"),
12482
+ span: _enum(ROLLING_SPANS),
12483
+ customMinutes: number().int().min(5).optional()
12484
+ }).check((payload) => {
12485
+ if (payload.value.span === "custom" && payload.value.customMinutes === void 0) payload.issues.push({
12486
+ code: "custom",
12487
+ message: "a custom range needs a duration in minutes",
12488
+ path: ["customMinutes"],
12489
+ input: payload.value
12490
+ });
12491
+ if (payload.value.span !== "custom" && payload.value.customMinutes !== void 0) payload.issues.push({
12492
+ code: "custom",
12493
+ message: "only a custom range carries a duration",
12494
+ path: ["customMinutes"],
12495
+ input: payload.value
12496
+ });
12497
+ });
12498
+ var RollingWindowSchema = rollingWindowObject;
12499
+ var StatisticWindowSchema = discriminatedUnion("kind", [rollingWindowObject, object({
12500
+ kind: literal("calendar"),
12501
+ period: _enum(CALENDAR_PERIODS)
12502
+ })]);
12503
+ /**
12388
12504
  * A composition source identity: `(addonId, stableId)`, never a numeric device
12389
12505
  * id. Split out so helper schemas can name it without a cycle through
12390
12506
  * `composition.ts` (D659).
@@ -12410,7 +12526,13 @@ var GROUP_TEMPLATE_IDS = [
12410
12526
  "occupancy",
12411
12527
  "binary",
12412
12528
  "switch",
12413
- "light"
12529
+ "light",
12530
+ "fan",
12531
+ "cover",
12532
+ "valve",
12533
+ "lock",
12534
+ "media-player",
12535
+ "climate"
12414
12536
  ];
12415
12537
  var NUMERIC_AGGREGATES = [
12416
12538
  "mean",
@@ -12440,14 +12562,73 @@ var HelperMemberSchema = object({
12440
12562
  cap: string().min(1),
12441
12563
  fieldPath: string().min(1)
12442
12564
  });
12443
- var HelperSpecSchema = discriminatedUnion("kind", [object({
12444
- kind: literal("group"),
12445
- templateId: GroupTemplateIdSchema,
12446
- aggregate: AggregateReducerSchema,
12447
- members: array(HelperMemberSchema).min(1).max(64),
12448
- /** Actuator groups only. Readings leave this unset. */
12449
- active: ActiveCriterionSchema.optional()
12450
- })]);
12565
+ var HelperSpecSchema = discriminatedUnion("kind", [
12566
+ object({
12567
+ kind: literal("group"),
12568
+ templateId: GroupTemplateIdSchema,
12569
+ aggregate: AggregateReducerSchema,
12570
+ members: array(HelperMemberSchema).min(1).max(64),
12571
+ /** Actuator groups only. Readings leave this unset. */
12572
+ active: ActiveCriterionSchema.optional()
12573
+ }),
12574
+ object({
12575
+ kind: literal("appliance"),
12576
+ templateId: literal(APPLIANCE_TEMPLATE_ID),
12577
+ appliance: _enum(APPLIANCE_KINDS),
12578
+ thresholdWatts: number().positive(),
12579
+ startDwellMs: number().int().nonnegative(),
12580
+ stopDwellMs: number().int().nonnegative(),
12581
+ member: HelperMemberSchema
12582
+ }),
12583
+ object({
12584
+ kind: literal("statistic"),
12585
+ stat: _enum(STATISTIC_STATS),
12586
+ window: StatisticWindowSchema,
12587
+ source: HelperMemberSchema
12588
+ }),
12589
+ object({
12590
+ kind: literal("integral"),
12591
+ reset: _enum(RESET_PERIODS),
12592
+ output: _enum(["energy", "meter"]),
12593
+ source: HelperMemberSchema
12594
+ }),
12595
+ object({
12596
+ kind: literal("threshold"),
12597
+ source: HelperMemberSchema,
12598
+ high: number(),
12599
+ low: number(),
12600
+ onForMs: number().int().nonnegative(),
12601
+ offForMs: number().int().nonnegative()
12602
+ }),
12603
+ object({
12604
+ kind: literal("arithmetic"),
12605
+ op: _enum([
12606
+ "sum",
12607
+ "difference",
12608
+ "ratio"
12609
+ ]),
12610
+ inputs: array(HelperMemberSchema).min(2).max(64)
12611
+ }),
12612
+ object({
12613
+ kind: literal("expression"),
12614
+ expr: string().min(1),
12615
+ inputs: array(object({
12616
+ name: string().min(1),
12617
+ member: HelperMemberSchema
12618
+ })).min(1).max(64),
12619
+ output: _enum(["number", "boolean"])
12620
+ }),
12621
+ object({
12622
+ kind: literal("derivative"),
12623
+ window: RollingWindowSchema,
12624
+ per: _enum([
12625
+ "second",
12626
+ "minute",
12627
+ "hour"
12628
+ ]),
12629
+ source: HelperMemberSchema
12630
+ })
12631
+ ]);
12451
12632
  /** A composed device's stableId is this prefix plus the block id. */
12452
12633
  var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
12453
12634
  /** Longest snippet a `code` source may carry (slice 4). */
@@ -12508,6 +12689,12 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12508
12689
  "all"
12509
12690
  ]),
12510
12691
  reads: array(CompositionFieldReadSchema).min(1).max(64),
12692
+ /**
12693
+ * Set when the reads are an enum state, not a yes/no: the listed states
12694
+ * count as active (a cover is `open`). A state outside the list is inactive;
12695
+ * a read that is not a state at all is left out. Only `any` / `all` use it.
12696
+ */
12697
+ activeWhen: array(string().min(1)).min(1).optional(),
12511
12698
  /** `value` is the reading; the others are the member counts. */
12512
12699
  publish: _enum([
12513
12700
  "value",
@@ -12521,6 +12708,26 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
12521
12708
  "omit",
12522
12709
  "offline"
12523
12710
  ])
12711
+ }),
12712
+ object({
12713
+ kind: literal("series"),
12714
+ read: CompositionFieldReadSchema,
12715
+ /** `value` is the statistic or the integral. The others are the gap counts. */
12716
+ publish: _enum([
12717
+ "value",
12718
+ "included",
12719
+ "excluded",
12720
+ "total"
12721
+ ]),
12722
+ /**
12723
+ * What an unanswered `value` does. Counts ignore this. `offline` leaves the
12724
+ * device unseeded until history answers, so a restart cannot publish 0.
12725
+ */
12726
+ empty: _enum([
12727
+ "null",
12728
+ "omit",
12729
+ "offline"
12730
+ ])
12524
12731
  })
12525
12732
  ]);
12526
12733
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
@@ -17680,7 +17887,22 @@ var BrokerProviderInfoSchema = object({
17680
17887
  */
17681
17888
  interactiveConnectPath: string().min(1).optional(),
17682
17889
  /** Relative hub path to disconnect the linked account (same popup pattern). */
17683
- interactiveDisconnectPath: string().min(1).optional()
17890
+ interactiveDisconnectPath: string().min(1).optional(),
17891
+ /**
17892
+ * The kind signs in through a server-held interactive session instead of
17893
+ * (or before) typed credentials. `basePath` is a hub-relative route set
17894
+ * served by the owning addon (`/addon/<id>/...`) speaking the sign-in
17895
+ * contract: `POST {basePath}/start` (body: the form's settings) answers
17896
+ * `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
17897
+ * `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
17898
+ * 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
17899
+ * (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
17900
+ * existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
17901
+ * (`{ sessionId }`) drops the session. The dialog that edits a broker
17902
+ * offers it as the default path; the fields the session fills are
17903
+ * marked `filledBySignIn` in the kind's settings schema.
17904
+ */
17905
+ signIn: object({ basePath: string().min(1) }).optional()
17684
17906
  }))
17685
17907
  });
17686
17908
  var ListInputSchema = object({
@@ -18785,7 +19007,8 @@ var CompositionProblemSchema = object({
18785
19007
  "native-capability-replace",
18786
19008
  "mixed-quantity",
18787
19009
  "helper-mismatch",
18788
- "member-constraint"
19010
+ "member-constraint",
19011
+ "retention"
18789
19012
  ]),
18790
19013
  path: string(),
18791
19014
  message: string()
@@ -19019,13 +19242,17 @@ Object.freeze({
19019
19242
  latchedAt: {
19020
19243
  kind: "fixed",
19021
19244
  result: ["number", "null"]
19245
+ },
19246
+ schmitt: {
19247
+ kind: "fixed",
19248
+ result: ["boolean", "null"]
19022
19249
  }
19023
19250
  });
19024
19251
  /**
19025
19252
  * One catalog row per base reading a group helper can build (D944 §3).
19026
19253
  * Data, not a form default written into the block.
19027
19254
  */
19028
- var NOW = {
19255
+ var NOW$2 = {
19029
19256
  fieldPath: "lastFetchedAt",
19030
19257
  expr: "now"
19031
19258
  };
@@ -19039,6 +19266,8 @@ function numeric(input) {
19039
19266
  family: "numeric",
19040
19267
  deviceType: DeviceType.Sensor,
19041
19268
  memberDeviceType: DeviceType.Sensor,
19269
+ memberDeviceTypes: [DeviceType.Sensor],
19270
+ activeWhen: void 0,
19042
19271
  commandCaps: [],
19043
19272
  baseUnit: baseUnitOf(input.quantity),
19044
19273
  aggregates: NUMERIC_AGGREGATES,
@@ -19054,6 +19283,8 @@ function binary(input) {
19054
19283
  family: "binary",
19055
19284
  deviceType: DeviceType.Sensor,
19056
19285
  memberDeviceType: DeviceType.Sensor,
19286
+ memberDeviceTypes: [DeviceType.Sensor],
19287
+ activeWhen: void 0,
19057
19288
  commandCaps: [],
19058
19289
  quantity: "none",
19059
19290
  baseUnit: void 0,
@@ -19071,7 +19302,7 @@ var GROUP_TEMPLATES = [
19071
19302
  quantity: "temperature",
19072
19303
  defaultAggregate: "mean",
19073
19304
  empty: "null",
19074
- companions: [NOW]
19305
+ companions: [NOW$2]
19075
19306
  }),
19076
19307
  numeric({
19077
19308
  id: "humidity",
@@ -19082,7 +19313,7 @@ var GROUP_TEMPLATES = [
19082
19313
  quantity: "humidity",
19083
19314
  defaultAggregate: "mean",
19084
19315
  empty: "null",
19085
- companions: [NOW]
19316
+ companions: [NOW$2]
19086
19317
  }),
19087
19318
  numeric({
19088
19319
  id: "pressure",
@@ -19093,7 +19324,7 @@ var GROUP_TEMPLATES = [
19093
19324
  quantity: "pressure",
19094
19325
  defaultAggregate: "mean",
19095
19326
  empty: "null",
19096
- companions: [NOW]
19327
+ companions: [NOW$2]
19097
19328
  }),
19098
19329
  numeric({
19099
19330
  id: "illuminance",
@@ -19104,7 +19335,7 @@ var GROUP_TEMPLATES = [
19104
19335
  quantity: "illuminance",
19105
19336
  defaultAggregate: "mean",
19106
19337
  empty: "null",
19107
- companions: [NOW]
19338
+ companions: [NOW$2]
19108
19339
  }),
19109
19340
  numeric({
19110
19341
  id: "power",
@@ -19115,7 +19346,7 @@ var GROUP_TEMPLATES = [
19115
19346
  quantity: "power",
19116
19347
  defaultAggregate: "sum",
19117
19348
  empty: "omit",
19118
- companions: [NOW]
19349
+ companions: [NOW$2]
19119
19350
  }),
19120
19351
  numeric({
19121
19352
  id: "energy",
@@ -19126,7 +19357,7 @@ var GROUP_TEMPLATES = [
19126
19357
  quantity: "energy",
19127
19358
  defaultAggregate: "sum",
19128
19359
  empty: "omit",
19129
- companions: [NOW]
19360
+ companions: [NOW$2]
19130
19361
  }),
19131
19362
  numeric({
19132
19363
  id: "battery",
@@ -19161,7 +19392,7 @@ var GROUP_TEMPLATES = [
19161
19392
  quantity: "device",
19162
19393
  defaultAggregate: "mean",
19163
19394
  empty: "null",
19164
- companions: [NOW]
19395
+ companions: [NOW$2]
19165
19396
  }),
19166
19397
  binary({
19167
19398
  id: "motion",
@@ -19255,6 +19486,11 @@ var GROUP_TEMPLATES = [
19255
19486
  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.",
19256
19487
  deviceType: DeviceType.Switch,
19257
19488
  outputCap: "switch",
19489
+ outputField: "on",
19490
+ member: {
19491
+ cap: "switch",
19492
+ fieldPath: "on"
19493
+ },
19258
19494
  commandCaps: ["switch"]
19259
19495
  }),
19260
19496
  actuator({
@@ -19262,34 +19498,136 @@ var GROUP_TEMPLATES = [
19262
19498
  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.",
19263
19499
  deviceType: DeviceType.Light,
19264
19500
  outputCap: "switch",
19501
+ outputField: "on",
19502
+ member: {
19503
+ cap: "switch",
19504
+ fieldPath: "on"
19505
+ },
19265
19506
  commandCaps: [
19266
19507
  "switch",
19267
19508
  "brightness",
19268
19509
  "color"
19269
19510
  ]
19511
+ }),
19512
+ actuator({
19513
+ id: "fan",
19514
+ 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.",
19515
+ deviceType: DeviceType.Fan,
19516
+ outputCap: "switch",
19517
+ outputField: "on",
19518
+ member: {
19519
+ cap: "switch",
19520
+ fieldPath: "on"
19521
+ },
19522
+ commandCaps: ["switch", "fan-control"]
19523
+ }),
19524
+ actuator({
19525
+ id: "cover",
19526
+ 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.",
19527
+ deviceType: DeviceType.Cover,
19528
+ outputCap: "group-active",
19529
+ outputField: "active",
19530
+ member: {
19531
+ cap: "cover",
19532
+ fieldPath: "state"
19533
+ },
19534
+ activeWhen: [
19535
+ "open",
19536
+ "opening",
19537
+ "stopped"
19538
+ ],
19539
+ commandCaps: ["cover"]
19540
+ }),
19541
+ actuator({
19542
+ id: "valve",
19543
+ 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.",
19544
+ deviceType: DeviceType.Valve,
19545
+ outputCap: "group-active",
19546
+ outputField: "active",
19547
+ member: {
19548
+ cap: "valve",
19549
+ fieldPath: "state"
19550
+ },
19551
+ activeWhen: [
19552
+ "open",
19553
+ "opening",
19554
+ "stopped"
19555
+ ],
19556
+ commandCaps: ["valve"]
19557
+ }),
19558
+ actuator({
19559
+ id: "lock",
19560
+ 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.",
19561
+ deviceType: DeviceType.Lock,
19562
+ outputCap: "group-active",
19563
+ outputField: "active",
19564
+ member: {
19565
+ cap: "lock-control",
19566
+ fieldPath: "state"
19567
+ },
19568
+ activeWhen: ["locked"],
19569
+ commandCaps: ["lock-control"]
19570
+ }),
19571
+ actuator({
19572
+ id: "media-player",
19573
+ 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.",
19574
+ deviceType: DeviceType.MediaPlayer,
19575
+ outputCap: "group-active",
19576
+ outputField: "active",
19577
+ member: {
19578
+ cap: "media-player",
19579
+ fieldPath: "state"
19580
+ },
19581
+ activeWhen: ["playing", "buffering"],
19582
+ commandCaps: ["media-player"]
19583
+ }),
19584
+ actuator({
19585
+ id: "climate",
19586
+ 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.",
19587
+ deviceType: DeviceType.Thermostat,
19588
+ memberDeviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
19589
+ outputCap: "group-active",
19590
+ outputField: "active",
19591
+ member: {
19592
+ cap: "climate-control",
19593
+ fieldPath: "mode"
19594
+ },
19595
+ activeWhen: [
19596
+ "heat",
19597
+ "cool",
19598
+ "auto",
19599
+ "heat_cool",
19600
+ "fan_only",
19601
+ "dry"
19602
+ ],
19603
+ commandCaps: ["climate-control"]
19270
19604
  })
19271
19605
  ];
19272
19606
  function actuator(input) {
19273
19607
  return {
19274
- ...input,
19608
+ id: input.id,
19609
+ summary: input.summary,
19610
+ deviceType: input.deviceType,
19611
+ outputCap: input.outputCap,
19612
+ outputField: input.outputField,
19613
+ commandCaps: input.commandCaps,
19275
19614
  family: "actuator",
19276
19615
  role: void 0,
19277
19616
  memberDeviceType: input.deviceType,
19278
- outputField: "on",
19617
+ memberDeviceTypes: input.memberDeviceTypes ?? [input.deviceType],
19618
+ activeWhen: input.activeWhen,
19279
19619
  quantity: "none",
19280
19620
  baseUnit: void 0,
19281
19621
  aggregates: BINARY_AGGREGATES,
19282
19622
  defaultAggregate: "any",
19283
19623
  empty: "offline",
19284
- accept: [{
19285
- cap: "switch",
19286
- fieldPath: "on"
19287
- }],
19624
+ accept: [input.member],
19288
19625
  companions: [CHANGED]
19289
19626
  };
19290
19627
  }
19291
19628
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
19292
19629
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
19630
+ DeviceType.Camera;
19293
19631
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
19294
19632
  /**
19295
19633
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -44513,7 +44851,7 @@ var NetworkAccessStatusSchema = object({
44513
44851
  });
44514
44852
  /**
44515
44853
  * Optional, richer endpoint shape returned by providers that expose
44516
- * MORE than one ingress concurrently (Tailscale Ingress with mixed
44854
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
44517
44855
  * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
44518
44856
  * the originating provider config (mode + sourcePort) so the
44519
44857
  * orchestrator UI can label rows distinctly. Providers that expose only
@@ -47200,7 +47538,20 @@ var NcRuleInputSchema = object({
47200
47538
  * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
47201
47539
  * path does not run Zod defaults.
47202
47540
  */
47203
- groupingSec: number().int().min(5).max(60).optional()
47541
+ groupingSec: number().int().min(5).max(60).optional(),
47542
+ /**
47543
+ * ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
47544
+ * carries "Correct" / "Wrong" buttons (and, where the channel has room, a
47545
+ * "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
47546
+ * same mark the Library writes, and records whose notification it was and
47547
+ * when. A channel with no buttons gets the same two choices as links.
47548
+ *
47549
+ * `.optional()`, never `.default()` (a Zod default does not run on the addon
47550
+ * cap path): absent and `false` both mean off, which is what every rule
47551
+ * authored before this field meant. An editor turns it off by writing
47552
+ * `false`, so it needs no `clear` key.
47553
+ */
47554
+ askVerdict: boolean().optional()
47204
47555
  });
47205
47556
  /**
47206
47557
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -57920,7 +58271,8 @@ var BATTERY_DEVICE_TYPES = [
57920
58271
  DeviceType.Switch,
57921
58272
  DeviceType.PetFeeder,
57922
58273
  DeviceType.Container,
57923
- DeviceType.Thermostat
58274
+ DeviceType.Thermostat,
58275
+ DeviceType.EventEmitter
57924
58276
  ];
57925
58277
  /** Generic Customize target for the percentage reading (D936). */
57926
58278
  var batteryCustomize = {
@@ -60788,7 +61140,17 @@ var groupCommandsCapability = {
60788
61140
  scope: "device",
60789
61141
  deviceNative: true,
60790
61142
  mode: "singleton",
60791
- deviceTypes: [DeviceType.Switch, DeviceType.Light],
61143
+ deviceTypes: [
61144
+ DeviceType.Switch,
61145
+ DeviceType.Light,
61146
+ DeviceType.Fan,
61147
+ DeviceType.Cover,
61148
+ DeviceType.Valve,
61149
+ DeviceType.Lock,
61150
+ DeviceType.MediaPlayer,
61151
+ DeviceType.Thermostat,
61152
+ DeviceType.Climate
61153
+ ],
60792
61154
  methods: {},
60793
61155
  status: {
60794
61156
  schema: GroupCommandsSchema,
@@ -60806,6 +61168,49 @@ var groupCommandsCapability = {
60806
61168
  history: "none"
60807
61169
  };
60808
61170
  /**
61171
+ * Whether a helper actuator group counts as active (D944): at least one
61172
+ * included member is, or every included member is. What "active" means is the
61173
+ * type's: a cover is open, a lock is locked, a media player is playing, a
61174
+ * climate device is not off. A group of lights, switches and fans reports
61175
+ * `switch.on` instead; this cap is for the types whose own state is not a
61176
+ * yes/no. Unknown members are left out (`group-status` counts them) and an
61177
+ * empty included set takes the device offline, never `active: false`.
61178
+ */
61179
+ var GroupActiveSchema = object({
61180
+ active: boolean().meta({ label: "Active" }),
61181
+ /** Ms epoch when the slice was last updated. */
61182
+ lastChangedAt: number()
61183
+ });
61184
+ var groupActiveCapability = {
61185
+ name: "group-active",
61186
+ scope: "device",
61187
+ deviceNative: true,
61188
+ mode: "singleton",
61189
+ deviceTypes: [
61190
+ DeviceType.Cover,
61191
+ DeviceType.Valve,
61192
+ DeviceType.Lock,
61193
+ DeviceType.MediaPlayer,
61194
+ DeviceType.Climate,
61195
+ DeviceType.Thermostat
61196
+ ],
61197
+ methods: {},
61198
+ status: {
61199
+ schema: GroupActiveSchema,
61200
+ kind: "push"
61201
+ },
61202
+ runtimeState: GroupActiveSchema,
61203
+ /**
61204
+ * Runtime-state durability: **session** — recomputed from the members on the
61205
+ * next source event; a restored value would outlive the members it named.
61206
+ *
61207
+ * See `RuntimeStateDurability`. Enforced by
61208
+ * `scripts/check-runtime-state-durability.ts`.
61209
+ */
61210
+ durability: "session",
61211
+ history: "none"
61212
+ };
61213
+ /**
60809
61214
  * How many members of a helper group answered (D944). Counts are integers,
60810
61215
  * not measurements: zero included is a real count, never a stand-in reading.
60811
61216
  * The reading itself stays on the output capability. History is none — the
@@ -60836,7 +61241,14 @@ var groupStatusCapability = {
60836
61241
  deviceTypes: [
60837
61242
  DeviceType.Sensor,
60838
61243
  DeviceType.Switch,
60839
- DeviceType.Light
61244
+ DeviceType.Light,
61245
+ DeviceType.Fan,
61246
+ DeviceType.Cover,
61247
+ DeviceType.Valve,
61248
+ DeviceType.Lock,
61249
+ DeviceType.MediaPlayer,
61250
+ DeviceType.Thermostat,
61251
+ DeviceType.Climate
60840
61252
  ],
60841
61253
  methods: {},
60842
61254
  status: {
@@ -62109,7 +62521,14 @@ var lockControlCapability = {
62109
62521
  }),
62110
62522
  unlock: method(object({
62111
62523
  deviceId: number().int().nonnegative(),
62112
- code: string().min(1).optional()
62524
+ code: string().min(1).optional(),
62525
+ /**
62526
+ * Honoured by a helper lock GROUP only (D944): unlocking several doors
62527
+ * with one call is refused unless the caller says it was confirmed. A
62528
+ * single lock ignores it. The refusal lives in the callee, so no
62529
+ * automation or API caller reaches the doors by skipping a dialog.
62530
+ */
62531
+ confirm: boolean().optional()
62113
62532
  }), _void(), {
62114
62533
  kind: "mutation",
62115
62534
  auth: "protected"
@@ -62146,6 +62565,44 @@ var lockControlCapability = {
62146
62565
  volatileStateFields: ["lastChangedAt"]
62147
62566
  };
62148
62567
  /**
62568
+ * A monotonic total between resets (D944). Energy uses `power-meter.kwhTotal`
62569
+ * so dashboards keep one series; every other instantaneous reading lands here.
62570
+ * `value` is the time integral in the source quantity's accumulated base
62571
+ * (base-unit seconds, or kWh on the power meter). `null` is a gap, never zero.
62572
+ */
62573
+ var MeterStatusSchema = object({
62574
+ value: number().nullable().meta(readingMeta("device", { label: "Total" })),
62575
+ lastFetchedAt: number()
62576
+ });
62577
+ var meterCapability = {
62578
+ name: "meter",
62579
+ scope: "device",
62580
+ deviceNative: true,
62581
+ mode: "singleton",
62582
+ deviceTypes: [DeviceType.Sensor],
62583
+ methods: {},
62584
+ status: {
62585
+ schema: MeterStatusSchema,
62586
+ kind: "push"
62587
+ },
62588
+ runtimeState: MeterStatusSchema,
62589
+ /**
62590
+ * Runtime-state durability: **restored** — the total is a cache of the
62591
+ * history integral. A restart shows it only until the history pass
62592
+ * recomputes the open period; it is not an in-memory buffer.
62593
+ *
62594
+ * See `RuntimeStateDurability`. Enforced by
62595
+ * `scripts/check-runtime-state-durability.ts`.
62596
+ */
62597
+ durability: "restored",
62598
+ history: { fields: { value: {
62599
+ kind: "counter",
62600
+ deadband: .001,
62601
+ minIntervalMs: 6e4
62602
+ } } },
62603
+ volatileStateFields: ["lastFetchedAt"]
62604
+ };
62605
+ /**
62149
62606
  * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
62150
62607
  * Apple TV, Spotify, Roku, generic OTT receivers, …) on
62151
62608
  * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
@@ -64657,9 +65114,9 @@ var PowerMeterStatusSchema = object({
64657
65114
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
64658
65115
  kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
64659
65116
  /** Voltage in volts. */
64660
- volts: number().optional().meta(readingMeta("voltage")),
65117
+ volts: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
64661
65118
  /** Current in amperes. */
64662
- amps: number().optional().meta(readingMeta("current")),
65119
+ amps: number().optional().meta(readingMeta("current", { label: "Current" })),
64663
65120
  /** Apparent power in volt-amperes. */
64664
65121
  va: number().optional().meta(readingMeta("apparent-power")),
64665
65122
  /** Power factor (0..1) if reported. */
@@ -64667,12 +65124,74 @@ var PowerMeterStatusSchema = object({
64667
65124
  /** Ms epoch when the slice was last updated. */
64668
65125
  lastFetchedAt: number()
64669
65126
  });
65127
+ /**
65128
+ * Loads that draw metered mains power (D951). The reading is a feature of the
65129
+ * device itself — a plug, a lamp, a fan, an air conditioner, a boiler, a
65130
+ * vacuum dock — not a separate sensor beside it. A `Sensor` stays: a clamp
65131
+ * meter or a standalone energy monitor IS the device. Cover, lock, valve,
65132
+ * camera and the like are left out: their draw is not what anyone meters.
65133
+ */
65134
+ var POWER_METER_DEVICE_TYPES = [
65135
+ DeviceType.Sensor,
65136
+ DeviceType.Switch,
65137
+ DeviceType.Light,
65138
+ DeviceType.Fan,
65139
+ DeviceType.Climate,
65140
+ DeviceType.Thermostat,
65141
+ DeviceType.Humidifier,
65142
+ DeviceType.WaterHeater,
65143
+ DeviceType.MediaPlayer,
65144
+ DeviceType.Washer,
65145
+ DeviceType.Dryer,
65146
+ DeviceType.Dishwasher,
65147
+ DeviceType.Vacuum,
65148
+ DeviceType.LawnMower,
65149
+ DeviceType.PetFeeder
65150
+ ];
65151
+ /** Everything but a bare `Sensor`: a sensor that takes its reading from another sensor adds nothing. */
65152
+ var POWER_METER_CUSTOMIZE_DEVICE_TYPES = POWER_METER_DEVICE_TYPES.filter((type) => type !== DeviceType.Sensor);
65153
+ /** One scalar target per reading; each is sourced from a device of its own (D951). */
65154
+ function powerTarget(id, unit) {
65155
+ return {
65156
+ id,
65157
+ cardinality: "scalar",
65158
+ vocabulary: {
65159
+ cap: "power-meter",
65160
+ stateField: id
65161
+ },
65162
+ deviceTypes: POWER_METER_CUSTOMIZE_DEVICE_TYPES,
65163
+ apply: "add",
65164
+ path: id,
65165
+ sources: [{
65166
+ kind: "numeric",
65167
+ requiredUnit: unit,
65168
+ transforms: [{ kind: "identity" }]
65169
+ }],
65170
+ companions: [{
65171
+ kind: "clock",
65172
+ path: "lastFetchedAt",
65173
+ when: "always"
65174
+ }],
65175
+ sourceReport: { status: "assignment-status" },
65176
+ advancedExpression: true
65177
+ };
65178
+ }
65179
+ /** "Power and energy" extension: watts, energy, voltage and current from other devices (D951). */
65180
+ var powerMeterCustomize = {
65181
+ version: 1,
65182
+ targets: [
65183
+ powerTarget("watts", "W"),
65184
+ powerTarget("kwhTotal", "kWh"),
65185
+ powerTarget("volts", "V"),
65186
+ powerTarget("amps", "A")
65187
+ ]
65188
+ };
64670
65189
  var powerMeterCapability = {
64671
65190
  name: "power-meter",
64672
65191
  scope: "device",
64673
65192
  deviceNative: true,
64674
65193
  mode: "singleton",
64675
- deviceTypes: [DeviceType.Sensor],
65194
+ deviceTypes: POWER_METER_DEVICE_TYPES,
64676
65195
  methods: {},
64677
65196
  status: {
64678
65197
  schema: PowerMeterStatusSchema,
@@ -64729,7 +65248,8 @@ var powerMeterCapability = {
64729
65248
  } },
64730
65249
  /** Clock fields: written, but excluded from the compare that decides
64731
65250
  * whether persisting is worth a SQLite commit. */
64732
- volatileStateFields: ["lastFetchedAt"]
65251
+ volatileStateFields: ["lastFetchedAt"],
65252
+ customize: powerMeterCustomize
64733
65253
  };
64734
65254
  /**
64735
65255
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -65410,6 +65930,8 @@ var runningCustomize = {
65410
65930
  stateField: "running"
65411
65931
  },
65412
65932
  deviceTypes: [
65933
+ DeviceType.Washer,
65934
+ DeviceType.Dryer,
65413
65935
  DeviceType.Dishwasher,
65414
65936
  DeviceType.Vacuum,
65415
65937
  DeviceType.LawnMower,
@@ -67899,7 +68421,8 @@ var switchCapability = {
67899
68421
  deviceTypes: [
67900
68422
  DeviceType.Switch,
67901
68423
  DeviceType.Siren,
67902
- DeviceType.Light
68424
+ DeviceType.Light,
68425
+ DeviceType.Fan
67903
68426
  ],
67904
68427
  methods: { setState: method(object({
67905
68428
  deviceId: number(),
@@ -70112,8 +70635,9 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
70112
70635
  kind: "mutation",
70113
70636
  auth: "admin"
70114
70637
  });
70638
+ DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
70115
70639
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
70116
- 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;
70640
+ 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;
70117
70641
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
70118
70642
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
70119
70643
  new Set(CAP_NAMES);
@@ -70442,6 +70966,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
70442
70966
  featureProbe: featureProbeCapability,
70443
70967
  flood: floodCapability,
70444
70968
  gas: gasCapability,
70969
+ groupActive: groupActiveCapability,
70445
70970
  groupCommands: groupCommandsCapability,
70446
70971
  groupStatus: groupStatusCapability,
70447
70972
  heatingValve: heatingValveCapability,
@@ -70453,6 +70978,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
70453
70978
  lawnMowerControl: lawnMowerControlCapability,
70454
70979
  lockControl: lockControlCapability,
70455
70980
  mediaPlayer: mediaPlayerCapability,
70981
+ meter: meterCapability,
70456
70982
  motion: motionCapability,
70457
70983
  motionTrigger: motionTriggerCapability,
70458
70984
  motionZones: motionZonesCapability,
@@ -71712,7 +72238,7 @@ var BaseDeviceProvider = class extends BaseAddon {
71712
72238
  return toDeviceSummary(device, this.addonId);
71713
72239
  }
71714
72240
  };
71715
- 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;
72241
+ 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;
71716
72242
  new Set(Object.values(DeviceType));
71717
72243
  Object.freeze({
71718
72244
  "accessories.getStatus": {
@@ -74935,6 +75461,13 @@ Object.freeze({
74935
75461
  access: "view",
74936
75462
  role: "viewer"
74937
75463
  },
75464
+ "groupActive.getStatus": {
75465
+ capName: "group-active",
75466
+ capScope: "device",
75467
+ addonId: null,
75468
+ access: "view",
75469
+ role: "viewer"
75470
+ },
74938
75471
  "groupCommands.getStatus": {
74939
75472
  capName: "group-commands",
74940
75473
  capScope: "device",
@@ -75901,6 +76434,13 @@ Object.freeze({
75901
76434
  access: "create",
75902
76435
  role: "admin"
75903
76436
  },
76437
+ "meter.getStatus": {
76438
+ capName: "meter",
76439
+ capScope: "device",
76440
+ addonId: null,
76441
+ access: "view",
76442
+ role: "viewer"
76443
+ },
75904
76444
  "metricsProvider.collectSnapshot": {
75905
76445
  capName: "metrics-provider",
75906
76446
  capScope: "system",
@@ -83999,7 +84539,39 @@ var APPLIANCE_CAP_VOCABULARY = {
83999
84539
  phrase: t("When {device} finishes", "Quando {device} finisce"),
84000
84540
  icon: "square"
84001
84541
  }
84002
- }
84542
+ },
84543
+ overrides: { byType: {
84544
+ [DeviceType.Washer]: {
84545
+ state: { running: {
84546
+ whenTrue: t("When the washing machine starts", "Quando la lavatrice inizia"),
84547
+ whenFalse: t("When the washing machine has finished", "Quando la lavatrice ha finito")
84548
+ } },
84549
+ events: {
84550
+ onStarted: { phrase: t("When the washing machine starts", "Quando la lavatrice inizia") },
84551
+ onFinished: { phrase: t("When the washing machine has finished", "Quando la lavatrice ha finito") }
84552
+ }
84553
+ },
84554
+ [DeviceType.Dryer]: {
84555
+ state: { running: {
84556
+ whenTrue: t("When the dryer starts", "Quando l'asciugatrice inizia"),
84557
+ whenFalse: t("When the dryer has finished", "Quando l'asciugatrice ha finito")
84558
+ } },
84559
+ events: {
84560
+ onStarted: { phrase: t("When the dryer starts", "Quando l'asciugatrice inizia") },
84561
+ onFinished: { phrase: t("When the dryer has finished", "Quando l'asciugatrice ha finito") }
84562
+ }
84563
+ },
84564
+ [DeviceType.Dishwasher]: {
84565
+ state: { running: {
84566
+ whenTrue: t("When the dishwasher starts", "Quando la lavastoviglie inizia"),
84567
+ whenFalse: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito")
84568
+ } },
84569
+ events: {
84570
+ onStarted: { phrase: t("When the dishwasher starts", "Quando la lavastoviglie inizia") },
84571
+ onFinished: { phrase: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito") }
84572
+ }
84573
+ }
84574
+ } }
84003
84575
  },
84004
84576
  update: {
84005
84577
  scope: "device",
@@ -84771,6 +85343,10 @@ var SENSOR_CAP_VOCABULARY = {
84771
85343
  icon: "flame"
84772
85344
  }]
84773
85345
  },
85346
+ "group-active": {
85347
+ scope: "device",
85348
+ noun: t("Group state", "Stato del gruppo")
85349
+ },
84774
85350
  "group-commands": {
84775
85351
  scope: "device",
84776
85352
  noun: t("Group command", "Comando di gruppo")
@@ -84827,6 +85403,17 @@ var SENSOR_CAP_VOCABULARY = {
84827
85403
  }],
84828
85404
  events: { onMotionChanged: { notOperatorFacing: "the `detected` state trigger phrases both edges, from the mirror that survives a dropped event (D49)" } }
84829
85405
  },
85406
+ meter: {
85407
+ scope: "device",
85408
+ noun: t("Meter", "Contatore"),
85409
+ state: [{
85410
+ kind: "numeric",
85411
+ field: "value",
85412
+ quantity: t("the total", "il totale"),
85413
+ label: t("Total", "Totale"),
85414
+ icon: "gauge"
85415
+ }]
85416
+ },
84830
85417
  "numeric-sensor": {
84831
85418
  scope: "device",
84832
85419
  noun: t("Sensor", "Sensore"),
@@ -85299,8 +85886,47 @@ function projectAction(cap, action) {
85299
85886
  };
85300
85887
  }
85301
85888
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
85302
- 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;
85889
+ 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;
85303
85890
  DeviceFeature.BatteryOperated;
85891
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
85892
+ 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.";
85893
+ 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.";
85894
+ var ALEXA_REASON = "Alexa has no laundry category in this export, and neither power nor running has an Alexa interface.";
85895
+ var GOOGLE_REASON = "Google WASHER, DRYER and DISHWASHER require RunCycle. A power threshold is not that trait, and this export does not publish RunCycle.";
85896
+ 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.";
85897
+ function withheld(reason) {
85898
+ return {
85899
+ [DeviceType.Washer]: {
85900
+ exported: false,
85901
+ reason
85902
+ },
85903
+ [DeviceType.Dryer]: {
85904
+ exported: false,
85905
+ reason
85906
+ },
85907
+ [DeviceType.Dishwasher]: {
85908
+ exported: false,
85909
+ reason
85910
+ }
85911
+ };
85912
+ }
85913
+ function offered(received) {
85914
+ return {
85915
+ [DeviceType.Washer]: {
85916
+ exported: true,
85917
+ as: received
85918
+ },
85919
+ [DeviceType.Dryer]: {
85920
+ exported: true,
85921
+ as: received
85922
+ },
85923
+ [DeviceType.Dishwasher]: {
85924
+ exported: true,
85925
+ as: received
85926
+ }
85927
+ };
85928
+ }
85929
+ offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
85304
85930
  Object.freeze({
85305
85931
  "accessories.getStatus": [{
85306
85932
  name: "deviceId",
@@ -86267,6 +86893,11 @@ Object.freeze({
86267
86893
  form: "single",
86268
86894
  optional: false
86269
86895
  }],
86896
+ "groupActive.getStatus": [{
86897
+ name: "deviceId",
86898
+ form: "single",
86899
+ optional: false
86900
+ }],
86270
86901
  "groupCommands.getStatus": [{
86271
86902
  name: "deviceId",
86272
86903
  form: "single",
@@ -86477,6 +87108,11 @@ Object.freeze({
86477
87108
  form: "single",
86478
87109
  optional: false
86479
87110
  }],
87111
+ "meter.getStatus": [{
87112
+ name: "deviceId",
87113
+ form: "single",
87114
+ optional: false
87115
+ }],
86480
87116
  "motion.getStatus": [{
86481
87117
  name: "deviceId",
86482
87118
  form: "single",
@@ -88084,7 +88720,6 @@ Object.freeze({
88084
88720
  "network-access": "ingress",
88085
88721
  "smtp-provider": "email"
88086
88722
  });
88087
- DeviceType.Camera;
88088
88723
  /**
88089
88724
  * A saved Customize assignment (D936). The schema is the type: callers use
88090
88725
  * `z.infer`, they do not restate the shape.