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