@camstack/addon-provider-ecowitt 0.2.147 → 0.2.149

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
@@ -1,7 +1,7 @@
1
1
  import { createServer } from "http";
2
2
  import { EventEmitter } from "events";
3
3
  import { createSocket } from "dgram";
4
- //#region ../types/dist/event-category-DHXxB8t9.mjs
4
+ //#region ../types/dist/event-category-CT-hz5Rb.mjs
5
5
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
6
6
  EventCategory["SystemBoot"] = "system.boot";
7
7
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -327,6 +327,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
327
327
  /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
328
328
  EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
329
329
  /**
330
+ * A camera's package LLM confirm has failed closed three times in a row
331
+ * (D931, 2026-10-26). One edge per camera until
332
+ * {@link PipelinePackageLlmAnswered}.
333
+ */
334
+ EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
335
+ /** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
336
+ EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
337
+ /**
330
338
  * Per-camera pipeline config was mutated by the orchestrator
331
339
  * (a settings change via `setAgentInferenceDevices` /
332
340
  * `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
@@ -5436,7 +5444,7 @@ var ZodIssueCode$1 = {
5436
5444
  var ZodFirstPartyTypeKind$1;
5437
5445
  ZodFirstPartyTypeKind$1 || (ZodFirstPartyTypeKind$1 = {});
5438
5446
  //#endregion
5439
- //#region ../types/dist/sleep-CARd6wZF.mjs
5447
+ //#region ../types/dist/sleep--jXS9hWo.mjs
5440
5448
  /**
5441
5449
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5442
5450
  * window to float samples (D455).
@@ -8629,6 +8637,9 @@ var CAP_NAMES = Object.freeze([
8629
8637
  "filesystem-browse",
8630
8638
  "flood",
8631
8639
  "gas",
8640
+ "group-active",
8641
+ "group-commands",
8642
+ "group-status",
8632
8643
  "heating-valve",
8633
8644
  "humidifier",
8634
8645
  "humidity-sensor",
@@ -8649,6 +8660,7 @@ var CAP_NAMES = Object.freeze([
8649
8660
  "login-method",
8650
8661
  "media-player",
8651
8662
  "mesh-network",
8663
+ "meter",
8652
8664
  "metrics-provider",
8653
8665
  "model-convert",
8654
8666
  "model-distributor",
@@ -9447,6 +9459,24 @@ Object.freeze({
9447
9459
  "path": "detected",
9448
9460
  "valueType": "boolean"
9449
9461
  }],
9462
+ "group-active": [{
9463
+ "path": "active",
9464
+ "valueType": "boolean"
9465
+ }],
9466
+ "group-status": [
9467
+ {
9468
+ "path": "included",
9469
+ "valueType": "number"
9470
+ },
9471
+ {
9472
+ "path": "excluded",
9473
+ "valueType": "number"
9474
+ },
9475
+ {
9476
+ "path": "total",
9477
+ "valueType": "number"
9478
+ }
9479
+ ],
9450
9480
  "heating-valve": [{
9451
9481
  "path": "position",
9452
9482
  "valueType": "number"
@@ -9692,6 +9722,10 @@ Object.freeze({
9692
9722
  ]
9693
9723
  }
9694
9724
  ],
9725
+ "meter": [{
9726
+ "path": "value",
9727
+ "valueType": "number"
9728
+ }],
9695
9729
  "motion": [
9696
9730
  {
9697
9731
  "path": "detected",
@@ -10449,7 +10483,7 @@ function userError(code, params, englishFallback) {
10449
10483
  return new CamstackError(code, params, englishFallback);
10450
10484
  }
10451
10485
  //#endregion
10452
- //#region ../types/dist/err-msg-BXKzRBwg.mjs
10486
+ //#region ../types/dist/err-msg-DIorGYeC.mjs
10453
10487
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10454
10488
  DeviceType["Camera"] = "camera";
10455
10489
  DeviceType["Hub"] = "hub";
@@ -10543,8 +10577,14 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
10543
10577
  DeviceType["PetFeeder"] = "pet-feeder";
10544
10578
  /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10545
10579
  * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10546
- * Sources: native Bosch Home Connect, reusable by similar integrations. */
10580
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
10581
+ * A plug-built dishwasher carries running-state and power instead. */
10547
10582
  DeviceType["Dishwasher"] = "dishwasher";
10583
+ /** Washing machine. A plug-built one is running while its power stays
10584
+ * above a threshold. No program cap — that surface is the dishwasher's. */
10585
+ DeviceType["Washer"] = "washer";
10586
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
10587
+ DeviceType["Dryer"] = "dryer";
10548
10588
  return DeviceType;
10549
10589
  }({});
10550
10590
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -10802,7 +10842,7 @@ function errMsg(err) {
10802
10842
  return String(err);
10803
10843
  }
10804
10844
  //#endregion
10805
- //#region ../types/dist/composition-kEl9mevu.mjs
10845
+ //#region ../types/dist/composition-Bb8iwHEJ.mjs
10806
10846
  /**
10807
10847
  * The ffmpeg build every CamStack node downloads, per platform and arch.
10808
10848
  *
@@ -11117,6 +11157,13 @@ var table = {
11117
11157
  apply: () => {
11118
11158
  throw new ExpressionEvalError("latchedAt: stateful evaluation context is required");
11119
11159
  }
11160
+ },
11161
+ schmitt: {
11162
+ minArgs: 3,
11163
+ maxArgs: 5,
11164
+ apply: () => {
11165
+ throw new ExpressionEvalError("schmitt: stateful evaluation context is required");
11166
+ }
11120
11167
  }
11121
11168
  };
11122
11169
  Object.freeze(Object.assign(Object.create(null), table));
@@ -11557,15 +11604,232 @@ function validateExpressionSource(src) {
11557
11604
  }
11558
11605
  return null;
11559
11606
  }
11560
- /** A composed device's stableId is this prefix plus the block id. */
11561
- var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11562
- /** Longest snippet a `code` source may carry (slice 4). */
11563
- var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11607
+ /**
11608
+ * One power reading becomes a washer, dryer or dishwasher (D944 helper).
11609
+ *
11610
+ * Standby of the control board is under 5 W; a motor or a heater is far
11611
+ * above that, so 5 W is the line. A door lock or a short pump spike is not
11612
+ * a cycle, so running starts only after 30 s above the line.
11613
+ *
11614
+ * A wash, dry or dishwasher pause (soak, heat-wait, heater off between
11615
+ * bursts) lasts several minutes. Ending the cycle on the first dip would
11616
+ * report "finished" in the middle of the program, so the stop dwell is
11617
+ * 10 minutes on every type. The number is stored per type so one machine
11618
+ * can move later without moving the others.
11619
+ */
11620
+ var APPLIANCE_TEMPLATE_ID = "appliance";
11621
+ var APPLIANCE_KINDS = [
11622
+ "washer",
11623
+ "dryer",
11624
+ "dishwasher"
11625
+ ];
11626
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
11627
+ /**
11628
+ * Statistics and integrals over a history read (D944).
11629
+ *
11630
+ * Pure: the same points always give the same answer, so a restart recomputes
11631
+ * rather than trusting a buffer. A gauge mean is time-weighted (value × ms),
11632
+ * never the mean of the samples. A hole with no held value is not integrated
11633
+ * as zero. A truncated read is not published.
11634
+ */
11635
+ var STATISTIC_STATS = [
11636
+ "min",
11637
+ "max",
11638
+ "mean",
11639
+ "sum",
11640
+ "count",
11641
+ "change"
11642
+ ];
11643
+ /**
11644
+ * The range a statistics or integral helper asks history for (D944).
11645
+ *
11646
+ * Calendar edges are the hub's timezone, never UTC midnight: a day is 23, 24
11647
+ * or 25 hours across a DST change. A tier is eligible only when its retention
11648
+ * is strictly longer than the longest that range can be. Forever qualifies.
11649
+ * Equal to a tier's retention is a refusal, not a short answer.
11650
+ */
11651
+ var MINUTE_MS = 6e4;
11652
+ var HOUR_MS = 60 * MINUTE_MS;
11653
+ var DAY_MS = 24 * HOUR_MS;
11654
+ var ROLLING_SPANS = [
11655
+ "1h",
11656
+ "24h",
11657
+ "2d",
11658
+ "7d",
11659
+ "30d",
11660
+ "custom"
11661
+ ];
11662
+ var CALENDAR_PERIODS = [
11663
+ "today",
11664
+ "yesterday",
11665
+ "this-week",
11666
+ "this-month"
11667
+ ];
11668
+ var RESET_PERIODS = [
11669
+ "never",
11670
+ "daily",
11671
+ "weekly",
11672
+ "monthly"
11673
+ ];
11674
+ 5 * MINUTE_MS;
11675
+ 24 * HOUR_MS, 2 * DAY_MS, 7 * DAY_MS, 30 * DAY_MS;
11676
+ 7 * DAY_MS, 30 * DAY_MS, 365 * DAY_MS;
11677
+ var rollingWindowObject = object({
11678
+ kind: literal("rolling"),
11679
+ span: _enum(ROLLING_SPANS),
11680
+ customMinutes: number().int().min(5).optional()
11681
+ }).check((payload) => {
11682
+ if (payload.value.span === "custom" && payload.value.customMinutes === void 0) payload.issues.push({
11683
+ code: "custom",
11684
+ message: "a custom range needs a duration in minutes",
11685
+ path: ["customMinutes"],
11686
+ input: payload.value
11687
+ });
11688
+ if (payload.value.span !== "custom" && payload.value.customMinutes !== void 0) payload.issues.push({
11689
+ code: "custom",
11690
+ message: "only a custom range carries a duration",
11691
+ path: ["customMinutes"],
11692
+ input: payload.value
11693
+ });
11694
+ });
11695
+ var RollingWindowSchema = rollingWindowObject;
11696
+ var StatisticWindowSchema = discriminatedUnion("kind", [rollingWindowObject, object({
11697
+ kind: literal("calendar"),
11698
+ period: _enum(CALENDAR_PERIODS)
11699
+ })]);
11700
+ /**
11701
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
11702
+ * id. Split out so helper schemas can name it without a cycle through
11703
+ * `composition.ts` (D659).
11704
+ */
11564
11705
  var CompositionSourceRefSchema = object({
11565
11706
  /** The owning addon; `stableId` is unique only within it. */
11566
11707
  addonId: string().min(1),
11567
11708
  stableId: string().min(1)
11568
11709
  });
11710
+ var GROUP_TEMPLATE_IDS = [
11711
+ "temperature",
11712
+ "humidity",
11713
+ "pressure",
11714
+ "illuminance",
11715
+ "power",
11716
+ "energy",
11717
+ "battery",
11718
+ "numeric",
11719
+ "motion",
11720
+ "contact",
11721
+ "leak",
11722
+ "smoke",
11723
+ "occupancy",
11724
+ "binary",
11725
+ "switch",
11726
+ "light",
11727
+ "fan",
11728
+ "cover",
11729
+ "valve",
11730
+ "lock",
11731
+ "media-player",
11732
+ "climate"
11733
+ ];
11734
+ var NUMERIC_AGGREGATES = [
11735
+ "mean",
11736
+ "median",
11737
+ "min",
11738
+ "max",
11739
+ "sum"
11740
+ ];
11741
+ var BINARY_AGGREGATES = ["any", "all"];
11742
+ /** How an actuator group decides it is on. Unknown members never count as off. */
11743
+ var ACTIVE_CRITERIA = ["at-least-one", "all"];
11744
+ var AGGREGATE_REDUCERS = [
11745
+ "mean",
11746
+ "median",
11747
+ "min",
11748
+ "max",
11749
+ "sum",
11750
+ "any",
11751
+ "all"
11752
+ ];
11753
+ var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
11754
+ var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
11755
+ var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
11756
+ /** One member. The field is stored so occupancy can name motion or binary. */
11757
+ var HelperMemberSchema = object({
11758
+ source: CompositionSourceRefSchema,
11759
+ cap: string().min(1),
11760
+ fieldPath: string().min(1)
11761
+ });
11762
+ var HelperSpecSchema = discriminatedUnion("kind", [
11763
+ object({
11764
+ kind: literal("group"),
11765
+ templateId: GroupTemplateIdSchema,
11766
+ aggregate: AggregateReducerSchema,
11767
+ members: array(HelperMemberSchema).min(1).max(64),
11768
+ /** Actuator groups only. Readings leave this unset. */
11769
+ active: ActiveCriterionSchema.optional()
11770
+ }),
11771
+ object({
11772
+ kind: literal("appliance"),
11773
+ templateId: literal(APPLIANCE_TEMPLATE_ID),
11774
+ appliance: _enum(APPLIANCE_KINDS),
11775
+ thresholdWatts: number().positive(),
11776
+ startDwellMs: number().int().nonnegative(),
11777
+ stopDwellMs: number().int().nonnegative(),
11778
+ member: HelperMemberSchema
11779
+ }),
11780
+ object({
11781
+ kind: literal("statistic"),
11782
+ stat: _enum(STATISTIC_STATS),
11783
+ window: StatisticWindowSchema,
11784
+ source: HelperMemberSchema
11785
+ }),
11786
+ object({
11787
+ kind: literal("integral"),
11788
+ reset: _enum(RESET_PERIODS),
11789
+ output: _enum(["energy", "meter"]),
11790
+ source: HelperMemberSchema
11791
+ }),
11792
+ object({
11793
+ kind: literal("threshold"),
11794
+ source: HelperMemberSchema,
11795
+ high: number(),
11796
+ low: number(),
11797
+ onForMs: number().int().nonnegative(),
11798
+ offForMs: number().int().nonnegative()
11799
+ }),
11800
+ object({
11801
+ kind: literal("arithmetic"),
11802
+ op: _enum([
11803
+ "sum",
11804
+ "difference",
11805
+ "ratio"
11806
+ ]),
11807
+ inputs: array(HelperMemberSchema).min(2).max(64)
11808
+ }),
11809
+ object({
11810
+ kind: literal("expression"),
11811
+ expr: string().min(1),
11812
+ inputs: array(object({
11813
+ name: string().min(1),
11814
+ member: HelperMemberSchema
11815
+ })).min(1).max(64),
11816
+ output: _enum(["number", "boolean"])
11817
+ }),
11818
+ object({
11819
+ kind: literal("derivative"),
11820
+ window: RollingWindowSchema,
11821
+ per: _enum([
11822
+ "second",
11823
+ "minute",
11824
+ "hour"
11825
+ ]),
11826
+ source: HelperMemberSchema
11827
+ })
11828
+ ]);
11829
+ /** A composed device's stableId is this prefix plus the block id. */
11830
+ var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
11831
+ /** Longest snippet a `code` source may carry (slice 4). */
11832
+ var MAX_COMPOSITION_CODE_LENGTH = 2e4;
11569
11833
  /** One field of one capability of one source device. */
11570
11834
  var CompositionFieldReadSchema = object({
11571
11835
  source: CompositionSourceRefSchema,
@@ -11609,7 +11873,59 @@ var CompositionCodeSourceSchema = object({
11609
11873
  var CompositionFieldSourceSchema = discriminatedUnion("kind", [
11610
11874
  CompositionFromSourceSchema,
11611
11875
  CompositionExpressionSourceSchema,
11612
- CompositionCodeSourceSchema
11876
+ CompositionCodeSourceSchema,
11877
+ object({
11878
+ kind: literal("aggregate"),
11879
+ reducer: _enum([
11880
+ "mean",
11881
+ "median",
11882
+ "min",
11883
+ "max",
11884
+ "sum",
11885
+ "any",
11886
+ "all"
11887
+ ]),
11888
+ reads: array(CompositionFieldReadSchema).min(1).max(64),
11889
+ /**
11890
+ * Set when the reads are an enum state, not a yes/no: the listed states
11891
+ * count as active (a cover is `open`). A state outside the list is inactive;
11892
+ * a read that is not a state at all is left out. Only `any` / `all` use it.
11893
+ */
11894
+ activeWhen: array(string().min(1)).min(1).optional(),
11895
+ /** `value` is the reading; the others are the member counts. */
11896
+ publish: _enum([
11897
+ "value",
11898
+ "included",
11899
+ "excluded",
11900
+ "total"
11901
+ ]),
11902
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
11903
+ empty: _enum([
11904
+ "null",
11905
+ "omit",
11906
+ "offline"
11907
+ ])
11908
+ }),
11909
+ object({
11910
+ kind: literal("series"),
11911
+ read: CompositionFieldReadSchema,
11912
+ /** `value` is the statistic or the integral. The others are the gap counts. */
11913
+ publish: _enum([
11914
+ "value",
11915
+ "included",
11916
+ "excluded",
11917
+ "total"
11918
+ ]),
11919
+ /**
11920
+ * What an unanswered `value` does. Counts ignore this. `offline` leaves the
11921
+ * device unseeded until history answers, so a restart cannot publish 0.
11922
+ */
11923
+ empty: _enum([
11924
+ "null",
11925
+ "omit",
11926
+ "offline"
11927
+ ])
11928
+ })
11613
11929
  ]);
11614
11930
  var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
11615
11931
  kind: literal("forward"),
@@ -11655,7 +11971,13 @@ var CompositionSchema = object({
11655
11971
  kind: literal("existing"),
11656
11972
  device: CompositionSourceRefSchema
11657
11973
  })]),
11658
- features: array(CompositionFeatureSchema).min(1).max(16)
11974
+ features: array(CompositionFeatureSchema).min(1).max(16),
11975
+ /**
11976
+ * Present when the block is a helper template. Absent on a free
11977
+ * composition (the room example, device 4603). The editor reads this,
11978
+ * never the compiled features.
11979
+ */
11980
+ helper: HelperSpecSchema.optional()
11659
11981
  }).superRefine((composition, ctx) => {
11660
11982
  const seen = /* @__PURE__ */ new Set();
11661
11983
  composition.features.forEach((feature, index) => {
@@ -16762,7 +17084,22 @@ var BrokerProviderInfoSchema = object({
16762
17084
  */
16763
17085
  interactiveConnectPath: string().min(1).optional(),
16764
17086
  /** Relative hub path to disconnect the linked account (same popup pattern). */
16765
- interactiveDisconnectPath: string().min(1).optional()
17087
+ interactiveDisconnectPath: string().min(1).optional(),
17088
+ /**
17089
+ * The kind signs in through a server-held interactive session instead of
17090
+ * (or before) typed credentials. `basePath` is a hub-relative route set
17091
+ * served by the owning addon (`/addon/<id>/...`) speaking the sign-in
17092
+ * contract: `POST {basePath}/start` (body: the form's settings) answers
17093
+ * `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
17094
+ * `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
17095
+ * 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
17096
+ * (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
17097
+ * existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
17098
+ * (`{ sessionId }`) drops the session. The dialog that edits a broker
17099
+ * offers it as the default path; the fields the session fills are
17100
+ * marked `filledBySignIn` in the kind's settings schema.
17101
+ */
17102
+ signIn: object({ basePath: string().min(1) }).optional()
16766
17103
  }))
16767
17104
  });
16768
17105
  var ListInputSchema = object({
@@ -17864,7 +18201,11 @@ var CompositionProblemSchema = object({
17864
18201
  "target-missing",
17865
18202
  "target-unreadable",
17866
18203
  "not-replaceable",
17867
- "native-capability-replace"
18204
+ "native-capability-replace",
18205
+ "mixed-quantity",
18206
+ "helper-mismatch",
18207
+ "member-constraint",
18208
+ "retention"
17868
18209
  ]),
17869
18210
  path: string(),
17870
18211
  message: string()
@@ -17885,7 +18226,8 @@ var CompositionUnavailableModeSchema = _enum([
17885
18226
  "null",
17886
18227
  "offline",
17887
18228
  "unknown",
17888
- "release"
18229
+ "release",
18230
+ "omit"
17889
18231
  ]);
17890
18232
  /**
17891
18233
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -18097,8 +18439,392 @@ Object.freeze({
18097
18439
  latchedAt: {
18098
18440
  kind: "fixed",
18099
18441
  result: ["number", "null"]
18442
+ },
18443
+ schmitt: {
18444
+ kind: "fixed",
18445
+ result: ["boolean", "null"]
18100
18446
  }
18101
18447
  });
18448
+ /**
18449
+ * One catalog row per base reading a group helper can build (D944 §3).
18450
+ * Data, not a form default written into the block.
18451
+ */
18452
+ var NOW$2 = {
18453
+ fieldPath: "lastFetchedAt",
18454
+ expr: "now"
18455
+ };
18456
+ var CHANGED = {
18457
+ fieldPath: "lastChangedAt",
18458
+ expr: "now"
18459
+ };
18460
+ function numeric(input) {
18461
+ return {
18462
+ ...input,
18463
+ family: "numeric",
18464
+ deviceType: DeviceType.Sensor,
18465
+ memberDeviceType: DeviceType.Sensor,
18466
+ memberDeviceTypes: [DeviceType.Sensor],
18467
+ activeWhen: void 0,
18468
+ commandCaps: [],
18469
+ baseUnit: baseUnitOf(input.quantity),
18470
+ aggregates: NUMERIC_AGGREGATES,
18471
+ accept: [{
18472
+ cap: input.outputCap,
18473
+ fieldPath: input.outputField
18474
+ }]
18475
+ };
18476
+ }
18477
+ function binary(input) {
18478
+ return {
18479
+ ...input,
18480
+ family: "binary",
18481
+ deviceType: DeviceType.Sensor,
18482
+ memberDeviceType: DeviceType.Sensor,
18483
+ memberDeviceTypes: [DeviceType.Sensor],
18484
+ activeWhen: void 0,
18485
+ commandCaps: [],
18486
+ quantity: "none",
18487
+ baseUnit: void 0,
18488
+ aggregates: BINARY_AGGREGATES,
18489
+ empty: "offline"
18490
+ };
18491
+ }
18492
+ var GROUP_TEMPLATES = [
18493
+ numeric({
18494
+ id: "temperature",
18495
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18496
+ role: DeviceRole.TemperatureSensor,
18497
+ outputCap: "temperature-sensor",
18498
+ outputField: "celsius",
18499
+ quantity: "temperature",
18500
+ defaultAggregate: "mean",
18501
+ empty: "null",
18502
+ companions: [NOW$2]
18503
+ }),
18504
+ numeric({
18505
+ id: "humidity",
18506
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18507
+ role: DeviceRole.HumiditySensor,
18508
+ outputCap: "humidity-sensor",
18509
+ outputField: "percent",
18510
+ quantity: "humidity",
18511
+ defaultAggregate: "mean",
18512
+ empty: "null",
18513
+ companions: [NOW$2]
18514
+ }),
18515
+ numeric({
18516
+ id: "pressure",
18517
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18518
+ role: DeviceRole.PressureSensor,
18519
+ outputCap: "pressure-sensor",
18520
+ outputField: "hpa",
18521
+ quantity: "pressure",
18522
+ defaultAggregate: "mean",
18523
+ empty: "null",
18524
+ companions: [NOW$2]
18525
+ }),
18526
+ numeric({
18527
+ id: "illuminance",
18528
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18529
+ role: DeviceRole.AmbientLightSensor,
18530
+ outputCap: "ambient-light-sensor",
18531
+ outputField: "lux",
18532
+ quantity: "illuminance",
18533
+ defaultAggregate: "mean",
18534
+ empty: "null",
18535
+ companions: [NOW$2]
18536
+ }),
18537
+ numeric({
18538
+ id: "power",
18539
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18540
+ role: DeviceRole.PowerSensor,
18541
+ outputCap: "power-meter",
18542
+ outputField: "watts",
18543
+ quantity: "power",
18544
+ defaultAggregate: "sum",
18545
+ empty: "omit",
18546
+ companions: [NOW$2]
18547
+ }),
18548
+ numeric({
18549
+ id: "energy",
18550
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18551
+ role: DeviceRole.EnergySensor,
18552
+ outputCap: "power-meter",
18553
+ outputField: "kwhTotal",
18554
+ quantity: "energy",
18555
+ defaultAggregate: "sum",
18556
+ empty: "omit",
18557
+ companions: [NOW$2]
18558
+ }),
18559
+ numeric({
18560
+ id: "battery",
18561
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18562
+ role: DeviceRole.BatterySensor,
18563
+ outputCap: "battery",
18564
+ outputField: "percentage",
18565
+ quantity: "percentage",
18566
+ defaultAggregate: "mean",
18567
+ empty: "null",
18568
+ companions: [
18569
+ {
18570
+ fieldPath: "charging",
18571
+ expr: "\"unknown\""
18572
+ },
18573
+ {
18574
+ fieldPath: "sleeping",
18575
+ expr: "false"
18576
+ },
18577
+ {
18578
+ fieldPath: "lastUpdated",
18579
+ expr: "now"
18580
+ }
18581
+ ]
18582
+ }),
18583
+ numeric({
18584
+ id: "numeric",
18585
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18586
+ role: DeviceRole.NumericSensor,
18587
+ outputCap: "numeric-sensor",
18588
+ outputField: "value",
18589
+ quantity: "device",
18590
+ defaultAggregate: "mean",
18591
+ empty: "null",
18592
+ companions: [NOW$2]
18593
+ }),
18594
+ binary({
18595
+ id: "motion",
18596
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18597
+ role: DeviceRole.MotionSensor,
18598
+ outputCap: "motion",
18599
+ outputField: "detected",
18600
+ defaultAggregate: "any",
18601
+ accept: [{
18602
+ cap: "motion",
18603
+ fieldPath: "detected"
18604
+ }],
18605
+ companions: [{
18606
+ fieldPath: "lastDetectedAt",
18607
+ expr: "null"
18608
+ }, {
18609
+ fieldPath: "autoClearAfterMs",
18610
+ expr: "null"
18611
+ }]
18612
+ }),
18613
+ binary({
18614
+ id: "contact",
18615
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18616
+ role: DeviceRole.ContactSensor,
18617
+ outputCap: "contact",
18618
+ outputField: "entryOpen",
18619
+ defaultAggregate: "any",
18620
+ accept: [{
18621
+ cap: "contact",
18622
+ fieldPath: "entryOpen"
18623
+ }],
18624
+ companions: [CHANGED]
18625
+ }),
18626
+ binary({
18627
+ id: "leak",
18628
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18629
+ role: DeviceRole.LeakSensor,
18630
+ outputCap: "flood",
18631
+ outputField: "flooded",
18632
+ defaultAggregate: "any",
18633
+ accept: [{
18634
+ cap: "flood",
18635
+ fieldPath: "flooded"
18636
+ }],
18637
+ companions: [CHANGED]
18638
+ }),
18639
+ binary({
18640
+ id: "smoke",
18641
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18642
+ role: DeviceRole.SmokeSensor,
18643
+ outputCap: "smoke",
18644
+ outputField: "detected",
18645
+ defaultAggregate: "any",
18646
+ accept: [{
18647
+ cap: "smoke",
18648
+ fieldPath: "detected"
18649
+ }],
18650
+ companions: [CHANGED]
18651
+ }),
18652
+ binary({
18653
+ id: "occupancy",
18654
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18655
+ role: DeviceRole.BinarySensor,
18656
+ outputCap: "binary",
18657
+ outputField: "on",
18658
+ defaultAggregate: "any",
18659
+ accept: [{
18660
+ cap: "motion",
18661
+ fieldPath: "detected"
18662
+ }, {
18663
+ cap: "binary",
18664
+ fieldPath: "on"
18665
+ }],
18666
+ companions: [CHANGED]
18667
+ }),
18668
+ binary({
18669
+ id: "binary",
18670
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18671
+ role: DeviceRole.BinarySensor,
18672
+ outputCap: "binary",
18673
+ outputField: "on",
18674
+ defaultAggregate: "any",
18675
+ accept: [{
18676
+ cap: "binary",
18677
+ fieldPath: "on"
18678
+ }],
18679
+ companions: [CHANGED]
18680
+ }),
18681
+ actuator({
18682
+ id: "switch",
18683
+ 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.",
18684
+ deviceType: DeviceType.Switch,
18685
+ outputCap: "switch",
18686
+ outputField: "on",
18687
+ member: {
18688
+ cap: "switch",
18689
+ fieldPath: "on"
18690
+ },
18691
+ commandCaps: ["switch"]
18692
+ }),
18693
+ actuator({
18694
+ id: "light",
18695
+ 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.",
18696
+ deviceType: DeviceType.Light,
18697
+ outputCap: "switch",
18698
+ outputField: "on",
18699
+ member: {
18700
+ cap: "switch",
18701
+ fieldPath: "on"
18702
+ },
18703
+ commandCaps: [
18704
+ "switch",
18705
+ "brightness",
18706
+ "color"
18707
+ ]
18708
+ }),
18709
+ actuator({
18710
+ id: "fan",
18711
+ 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.",
18712
+ deviceType: DeviceType.Fan,
18713
+ outputCap: "switch",
18714
+ outputField: "on",
18715
+ member: {
18716
+ cap: "switch",
18717
+ fieldPath: "on"
18718
+ },
18719
+ commandCaps: ["switch", "fan-control"]
18720
+ }),
18721
+ actuator({
18722
+ id: "cover",
18723
+ 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.",
18724
+ deviceType: DeviceType.Cover,
18725
+ outputCap: "group-active",
18726
+ outputField: "active",
18727
+ member: {
18728
+ cap: "cover",
18729
+ fieldPath: "state"
18730
+ },
18731
+ activeWhen: [
18732
+ "open",
18733
+ "opening",
18734
+ "stopped"
18735
+ ],
18736
+ commandCaps: ["cover"]
18737
+ }),
18738
+ actuator({
18739
+ id: "valve",
18740
+ 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.",
18741
+ deviceType: DeviceType.Valve,
18742
+ outputCap: "group-active",
18743
+ outputField: "active",
18744
+ member: {
18745
+ cap: "valve",
18746
+ fieldPath: "state"
18747
+ },
18748
+ activeWhen: [
18749
+ "open",
18750
+ "opening",
18751
+ "stopped"
18752
+ ],
18753
+ commandCaps: ["valve"]
18754
+ }),
18755
+ actuator({
18756
+ id: "lock",
18757
+ 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.",
18758
+ deviceType: DeviceType.Lock,
18759
+ outputCap: "group-active",
18760
+ outputField: "active",
18761
+ member: {
18762
+ cap: "lock-control",
18763
+ fieldPath: "state"
18764
+ },
18765
+ activeWhen: ["locked"],
18766
+ commandCaps: ["lock-control"]
18767
+ }),
18768
+ actuator({
18769
+ id: "media-player",
18770
+ 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.",
18771
+ deviceType: DeviceType.MediaPlayer,
18772
+ outputCap: "group-active",
18773
+ outputField: "active",
18774
+ member: {
18775
+ cap: "media-player",
18776
+ fieldPath: "state"
18777
+ },
18778
+ activeWhen: ["playing", "buffering"],
18779
+ commandCaps: ["media-player"]
18780
+ }),
18781
+ actuator({
18782
+ id: "climate",
18783
+ 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.",
18784
+ deviceType: DeviceType.Thermostat,
18785
+ memberDeviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
18786
+ outputCap: "group-active",
18787
+ outputField: "active",
18788
+ member: {
18789
+ cap: "climate-control",
18790
+ fieldPath: "mode"
18791
+ },
18792
+ activeWhen: [
18793
+ "heat",
18794
+ "cool",
18795
+ "auto",
18796
+ "heat_cool",
18797
+ "fan_only",
18798
+ "dry"
18799
+ ],
18800
+ commandCaps: ["climate-control"]
18801
+ })
18802
+ ];
18803
+ function actuator(input) {
18804
+ return {
18805
+ id: input.id,
18806
+ summary: input.summary,
18807
+ deviceType: input.deviceType,
18808
+ outputCap: input.outputCap,
18809
+ outputField: input.outputField,
18810
+ commandCaps: input.commandCaps,
18811
+ family: "actuator",
18812
+ role: void 0,
18813
+ memberDeviceType: input.deviceType,
18814
+ memberDeviceTypes: input.memberDeviceTypes ?? [input.deviceType],
18815
+ activeWhen: input.activeWhen,
18816
+ quantity: "none",
18817
+ baseUnit: void 0,
18818
+ aggregates: BINARY_AGGREGATES,
18819
+ defaultAggregate: "any",
18820
+ empty: "offline",
18821
+ accept: [input.member],
18822
+ companions: [CHANGED]
18823
+ };
18824
+ }
18825
+ new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18826
+ DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
18827
+ DeviceType.Camera;
18102
18828
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
18103
18829
  /**
18104
18830
  * What a composition may BUILD on a device type (G2, D676): the caps a new
@@ -43492,7 +44218,7 @@ var NetworkAccessStatusSchema = object({
43492
44218
  });
43493
44219
  /**
43494
44220
  * Optional, richer endpoint shape returned by providers that expose
43495
- * MORE than one ingress concurrently (Tailscale Ingress with mixed
44221
+ * MORE than one ingress concurrently (Tailscale Funnel with mixed
43496
44222
  * serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
43497
44223
  * the originating provider config (mode + sourcePort) so the
43498
44224
  * orchestrator UI can label rows distinctly. Providers that expose only
@@ -45088,6 +45814,8 @@ var NcSystemEventKindSchema = _enum([
45088
45814
  "detection-blind",
45089
45815
  "motion-noisy",
45090
45816
  "motion-calm",
45817
+ "package-llm-silent",
45818
+ "package-llm-answered",
45091
45819
  "recording-stalled",
45092
45820
  "recording-healthy",
45093
45821
  "recording-degraded",
@@ -46177,7 +46905,20 @@ var NcRuleInputSchema = object({
46177
46905
  * save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
46178
46906
  * path does not run Zod defaults.
46179
46907
  */
46180
- groupingSec: number().int().min(5).max(60).optional()
46908
+ groupingSec: number().int().min(5).max(60).optional(),
46909
+ /**
46910
+ * ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
46911
+ * carries "Correct" / "Wrong" buttons (and, where the channel has room, a
46912
+ * "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
46913
+ * same mark the Library writes, and records whose notification it was and
46914
+ * when. A channel with no buttons gets the same two choices as links.
46915
+ *
46916
+ * `.optional()`, never `.default()` (a Zod default does not run on the addon
46917
+ * cap path): absent and `false` both mean off, which is what every rule
46918
+ * authored before this field meant. An editor turns it off by writing
46919
+ * `false`, so it needs no `clear` key.
46920
+ */
46921
+ askVerdict: boolean().optional()
46181
46922
  });
46182
46923
  /**
46183
46924
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -56179,6 +56920,28 @@ var PackageVersionInfoSchema = object({
56179
56920
  deprecated: string().optional(),
56180
56921
  distTags: array(string()).readonly()
56181
56922
  });
56923
+ /**
56924
+ * One published document. `ok` is markdown from the registry. `missing` means
56925
+ * the registry answered and the file was not there. `error` is a registry or
56926
+ * tarball failure — never an empty string standing in for either.
56927
+ */
56928
+ var PackageDocBodySchema = discriminatedUnion("status", [
56929
+ object({
56930
+ status: literal("ok"),
56931
+ markdown: string()
56932
+ }),
56933
+ object({ status: literal("missing") }),
56934
+ object({
56935
+ status: literal("error"),
56936
+ message: string()
56937
+ })
56938
+ ]);
56939
+ var PackageDocsSchema = object({
56940
+ name: string(),
56941
+ version: string(),
56942
+ readme: PackageDocBodySchema,
56943
+ changelog: PackageDocBodySchema
56944
+ });
56182
56945
  var SearchResultSchema = object({
56183
56946
  name: string(),
56184
56947
  version: string(),
@@ -56397,7 +57160,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
56397
57160
  }), object({ success: literal(true) }), {
56398
57161
  kind: "mutation",
56399
57162
  auth: "admin"
56400
- }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({ addonId: string() }), RestartAddonResultSchema, {
57163
+ }), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
57164
+ name: string().min(1),
57165
+ version: string().min(1).optional()
57166
+ }), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
56401
57167
  kind: "mutation",
56402
57168
  auth: "admin"
56403
57169
  }), method(object({ packageName: string() }), object({ success: literal(true) }), {
@@ -56872,7 +57638,8 @@ var BATTERY_DEVICE_TYPES = [
56872
57638
  DeviceType.Switch,
56873
57639
  DeviceType.PetFeeder,
56874
57640
  DeviceType.Container,
56875
- DeviceType.Thermostat
57641
+ DeviceType.Thermostat,
57642
+ DeviceType.EventEmitter
56876
57643
  ];
56877
57644
  /** Generic Customize target for the percentage reading (D936). */
56878
57645
  var batteryCustomize = {
@@ -59716,6 +60483,148 @@ var gasCapability = {
59716
60483
  * whether persisting is worth a SQLite commit. */
59717
60484
  volatileStateFields: ["lastChangedAt"]
59718
60485
  };
60486
+ var GroupCommandsSchema = object({
60487
+ /** One row per member that has been commanded. A later command replaces that member's row. */
60488
+ results: array(object({
60489
+ deviceId: number().int().nonnegative(),
60490
+ cap: string().min(1),
60491
+ method: string().min(1),
60492
+ ok: boolean(),
60493
+ error: string().optional(),
60494
+ /** Ms epoch of the attempt. */
60495
+ at: number()
60496
+ })) });
60497
+ var groupCommandsCapability = {
60498
+ name: "group-commands",
60499
+ scope: "device",
60500
+ deviceNative: true,
60501
+ mode: "singleton",
60502
+ deviceTypes: [
60503
+ DeviceType.Switch,
60504
+ DeviceType.Light,
60505
+ DeviceType.Fan,
60506
+ DeviceType.Cover,
60507
+ DeviceType.Valve,
60508
+ DeviceType.Lock,
60509
+ DeviceType.MediaPlayer,
60510
+ DeviceType.Thermostat,
60511
+ DeviceType.Climate
60512
+ ],
60513
+ methods: {},
60514
+ status: {
60515
+ schema: GroupCommandsSchema,
60516
+ kind: "push"
60517
+ },
60518
+ runtimeState: GroupCommandsSchema,
60519
+ /**
60520
+ * Runtime-state durability: **session** — the last attempt. A restored
60521
+ * failure would outlive the command that caused it.
60522
+ *
60523
+ * See `RuntimeStateDurability`. Enforced by
60524
+ * `scripts/check-runtime-state-durability.ts`.
60525
+ */
60526
+ durability: "session",
60527
+ history: "none"
60528
+ };
60529
+ /**
60530
+ * Whether a helper actuator group counts as active (D944): at least one
60531
+ * included member is, or every included member is. What "active" means is the
60532
+ * type's: a cover is open, a lock is locked, a media player is playing, a
60533
+ * climate device is not off. A group of lights, switches and fans reports
60534
+ * `switch.on` instead; this cap is for the types whose own state is not a
60535
+ * yes/no. Unknown members are left out (`group-status` counts them) and an
60536
+ * empty included set takes the device offline, never `active: false`.
60537
+ */
60538
+ var GroupActiveSchema = object({
60539
+ active: boolean().meta({ label: "Active" }),
60540
+ /** Ms epoch when the slice was last updated. */
60541
+ lastChangedAt: number()
60542
+ });
60543
+ var groupActiveCapability = {
60544
+ name: "group-active",
60545
+ scope: "device",
60546
+ deviceNative: true,
60547
+ mode: "singleton",
60548
+ deviceTypes: [
60549
+ DeviceType.Cover,
60550
+ DeviceType.Valve,
60551
+ DeviceType.Lock,
60552
+ DeviceType.MediaPlayer,
60553
+ DeviceType.Climate,
60554
+ DeviceType.Thermostat
60555
+ ],
60556
+ methods: {},
60557
+ status: {
60558
+ schema: GroupActiveSchema,
60559
+ kind: "push"
60560
+ },
60561
+ runtimeState: GroupActiveSchema,
60562
+ /**
60563
+ * Runtime-state durability: **session** — recomputed from the members on the
60564
+ * next source event; a restored value would outlive the members it named.
60565
+ *
60566
+ * See `RuntimeStateDurability`. Enforced by
60567
+ * `scripts/check-runtime-state-durability.ts`.
60568
+ */
60569
+ durability: "session",
60570
+ history: "none"
60571
+ };
60572
+ /**
60573
+ * How many members of a helper group answered (D944). Counts are integers,
60574
+ * not measurements: zero included is a real count, never a stand-in reading.
60575
+ * The reading itself stays on the output capability. History is none — the
60576
+ * series that later statistics read is the output cap's own history block.
60577
+ */
60578
+ var GroupStatusSchema = object({
60579
+ /** Members whose current value entered the aggregate. */
60580
+ included: number().int().nonnegative().meta(readingMeta("count", {
60581
+ precision: 0,
60582
+ label: "Reporting"
60583
+ })),
60584
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
60585
+ excluded: number().int().nonnegative().meta(readingMeta("count", {
60586
+ precision: 0,
60587
+ label: "Excluded"
60588
+ })),
60589
+ /** `included` + `excluded`. */
60590
+ total: number().int().nonnegative().meta(readingMeta("count", {
60591
+ precision: 0,
60592
+ label: "Members"
60593
+ }))
60594
+ });
60595
+ var groupStatusCapability = {
60596
+ name: "group-status",
60597
+ scope: "device",
60598
+ deviceNative: true,
60599
+ mode: "singleton",
60600
+ deviceTypes: [
60601
+ DeviceType.Sensor,
60602
+ DeviceType.Switch,
60603
+ DeviceType.Light,
60604
+ DeviceType.Fan,
60605
+ DeviceType.Cover,
60606
+ DeviceType.Valve,
60607
+ DeviceType.Lock,
60608
+ DeviceType.MediaPlayer,
60609
+ DeviceType.Thermostat,
60610
+ DeviceType.Climate
60611
+ ],
60612
+ methods: {},
60613
+ status: {
60614
+ schema: GroupStatusSchema,
60615
+ kind: "push"
60616
+ },
60617
+ runtimeState: GroupStatusSchema,
60618
+ /**
60619
+ * Runtime-state durability: **session** — recomputed from members on the
60620
+ * next source event; a restored count would outlive the readings it named.
60621
+ *
60622
+ * See `RuntimeStateDurability`. Enforced by
60623
+ * `scripts/check-runtime-state-durability.ts`.
60624
+ */
60625
+ durability: "session",
60626
+ history: "none"
60627
+ };
59719
60628
  var HeatingValveStatusSchema = object({
59720
60629
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
59721
60630
  position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
@@ -60971,7 +61880,14 @@ var lockControlCapability = {
60971
61880
  }),
60972
61881
  unlock: method(object({
60973
61882
  deviceId: number().int().nonnegative(),
60974
- code: string().min(1).optional()
61883
+ code: string().min(1).optional(),
61884
+ /**
61885
+ * Honoured by a helper lock GROUP only (D944): unlocking several doors
61886
+ * with one call is refused unless the caller says it was confirmed. A
61887
+ * single lock ignores it. The refusal lives in the callee, so no
61888
+ * automation or API caller reaches the doors by skipping a dialog.
61889
+ */
61890
+ confirm: boolean().optional()
60975
61891
  }), _void(), {
60976
61892
  kind: "mutation",
60977
61893
  auth: "protected"
@@ -61008,6 +61924,44 @@ var lockControlCapability = {
61008
61924
  volatileStateFields: ["lastChangedAt"]
61009
61925
  };
61010
61926
  /**
61927
+ * A monotonic total between resets (D944). Energy uses `power-meter.kwhTotal`
61928
+ * so dashboards keep one series; every other instantaneous reading lands here.
61929
+ * `value` is the time integral in the source quantity's accumulated base
61930
+ * (base-unit seconds, or kWh on the power meter). `null` is a gap, never zero.
61931
+ */
61932
+ var MeterStatusSchema = object({
61933
+ value: number().nullable().meta(readingMeta("device", { label: "Total" })),
61934
+ lastFetchedAt: number()
61935
+ });
61936
+ var meterCapability = {
61937
+ name: "meter",
61938
+ scope: "device",
61939
+ deviceNative: true,
61940
+ mode: "singleton",
61941
+ deviceTypes: [DeviceType.Sensor],
61942
+ methods: {},
61943
+ status: {
61944
+ schema: MeterStatusSchema,
61945
+ kind: "push"
61946
+ },
61947
+ runtimeState: MeterStatusSchema,
61948
+ /**
61949
+ * Runtime-state durability: **restored** — the total is a cache of the
61950
+ * history integral. A restart shows it only until the history pass
61951
+ * recomputes the open period; it is not an in-memory buffer.
61952
+ *
61953
+ * See `RuntimeStateDurability`. Enforced by
61954
+ * `scripts/check-runtime-state-durability.ts`.
61955
+ */
61956
+ durability: "restored",
61957
+ history: { fields: { value: {
61958
+ kind: "counter",
61959
+ deadband: .001,
61960
+ minIntervalMs: 6e4
61961
+ } } },
61962
+ volatileStateFields: ["lastFetchedAt"]
61963
+ };
61964
+ /**
61011
61965
  * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
61012
61966
  * Apple TV, Spotify, Roku, generic OTT receivers, …) on
61013
61967
  * `DeviceType.MediaPlayer`. Disjoint from the camera streaming
@@ -61485,12 +62439,18 @@ var motionCapability = {
61485
62439
  methods: {
61486
62440
  /**
61487
62441
  * Pull the current motion state synchronously. Convenience shortcut
61488
- * for consumers that don't need the full status object; equivalent
61489
- * to `getStatus()?.detected ?? false`. Will likely be folded into
61490
- * `getStatus` once the auto-injected status surface lands in every
61491
- * consumer.
62442
+ * for consumers that don't need the full status object. A composed
62443
+ * group answers from its slice (D944): unknown throws, it is not off.
61492
62444
  */
61493
62445
  isDetected: method(object({ deviceId: number() }), boolean()) },
62446
+ /**
62447
+ * A composed motion group serves `isDetected` from `detected` (D944).
62448
+ * Without this the cap stays uncomposable (`has-commands`).
62449
+ */
62450
+ composedMethods: { isDetected: {
62451
+ kind: "slice",
62452
+ field: "detected"
62453
+ } },
61494
62454
  events: {
61495
62455
  /**
61496
62456
  * Fires every time the runner transitions a camera between
@@ -63513,9 +64473,9 @@ var PowerMeterStatusSchema = object({
63513
64473
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
63514
64474
  kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
63515
64475
  /** Voltage in volts. */
63516
- volts: number().optional().meta(readingMeta("voltage")),
64476
+ volts: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
63517
64477
  /** Current in amperes. */
63518
- amps: number().optional().meta(readingMeta("current")),
64478
+ amps: number().optional().meta(readingMeta("current", { label: "Current" })),
63519
64479
  /** Apparent power in volt-amperes. */
63520
64480
  va: number().optional().meta(readingMeta("apparent-power")),
63521
64481
  /** Power factor (0..1) if reported. */
@@ -63523,12 +64483,74 @@ var PowerMeterStatusSchema = object({
63523
64483
  /** Ms epoch when the slice was last updated. */
63524
64484
  lastFetchedAt: number()
63525
64485
  });
64486
+ /**
64487
+ * Loads that draw metered mains power (D951). The reading is a feature of the
64488
+ * device itself — a plug, a lamp, a fan, an air conditioner, a boiler, a
64489
+ * vacuum dock — not a separate sensor beside it. A `Sensor` stays: a clamp
64490
+ * meter or a standalone energy monitor IS the device. Cover, lock, valve,
64491
+ * camera and the like are left out: their draw is not what anyone meters.
64492
+ */
64493
+ var POWER_METER_DEVICE_TYPES = [
64494
+ DeviceType.Sensor,
64495
+ DeviceType.Switch,
64496
+ DeviceType.Light,
64497
+ DeviceType.Fan,
64498
+ DeviceType.Climate,
64499
+ DeviceType.Thermostat,
64500
+ DeviceType.Humidifier,
64501
+ DeviceType.WaterHeater,
64502
+ DeviceType.MediaPlayer,
64503
+ DeviceType.Washer,
64504
+ DeviceType.Dryer,
64505
+ DeviceType.Dishwasher,
64506
+ DeviceType.Vacuum,
64507
+ DeviceType.LawnMower,
64508
+ DeviceType.PetFeeder
64509
+ ];
64510
+ /** Everything but a bare `Sensor`: a sensor that takes its reading from another sensor adds nothing. */
64511
+ var POWER_METER_CUSTOMIZE_DEVICE_TYPES = POWER_METER_DEVICE_TYPES.filter((type) => type !== DeviceType.Sensor);
64512
+ /** One scalar target per reading; each is sourced from a device of its own (D951). */
64513
+ function powerTarget(id, unit) {
64514
+ return {
64515
+ id,
64516
+ cardinality: "scalar",
64517
+ vocabulary: {
64518
+ cap: "power-meter",
64519
+ stateField: id
64520
+ },
64521
+ deviceTypes: POWER_METER_CUSTOMIZE_DEVICE_TYPES,
64522
+ apply: "add",
64523
+ path: id,
64524
+ sources: [{
64525
+ kind: "numeric",
64526
+ requiredUnit: unit,
64527
+ transforms: [{ kind: "identity" }]
64528
+ }],
64529
+ companions: [{
64530
+ kind: "clock",
64531
+ path: "lastFetchedAt",
64532
+ when: "always"
64533
+ }],
64534
+ sourceReport: { status: "assignment-status" },
64535
+ advancedExpression: true
64536
+ };
64537
+ }
64538
+ /** "Power and energy" extension: watts, energy, voltage and current from other devices (D951). */
64539
+ var powerMeterCustomize = {
64540
+ version: 1,
64541
+ targets: [
64542
+ powerTarget("watts", "W"),
64543
+ powerTarget("kwhTotal", "kWh"),
64544
+ powerTarget("volts", "V"),
64545
+ powerTarget("amps", "A")
64546
+ ]
64547
+ };
63526
64548
  var powerMeterCapability = {
63527
64549
  name: "power-meter",
63528
64550
  scope: "device",
63529
64551
  deviceNative: true,
63530
64552
  mode: "singleton",
63531
- deviceTypes: [DeviceType.Sensor],
64553
+ deviceTypes: POWER_METER_DEVICE_TYPES,
63532
64554
  methods: {},
63533
64555
  status: {
63534
64556
  schema: PowerMeterStatusSchema,
@@ -63585,7 +64607,8 @@ var powerMeterCapability = {
63585
64607
  } },
63586
64608
  /** Clock fields: written, but excluded from the compare that decides
63587
64609
  * whether persisting is worth a SQLite commit. */
63588
- volatileStateFields: ["lastFetchedAt"]
64610
+ volatileStateFields: ["lastFetchedAt"],
64611
+ customize: powerMeterCustomize
63589
64612
  };
63590
64613
  /**
63591
64614
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -64266,6 +65289,8 @@ var runningCustomize = {
64266
65289
  stateField: "running"
64267
65290
  },
64268
65291
  deviceTypes: [
65292
+ DeviceType.Washer,
65293
+ DeviceType.Dryer,
64269
65294
  DeviceType.Dishwasher,
64270
65295
  DeviceType.Vacuum,
64271
65296
  DeviceType.LawnMower,
@@ -66755,7 +67780,8 @@ var switchCapability = {
66755
67780
  deviceTypes: [
66756
67781
  DeviceType.Switch,
66757
67782
  DeviceType.Siren,
66758
- DeviceType.Light
67783
+ DeviceType.Light,
67784
+ DeviceType.Fan
66759
67785
  ],
66760
67786
  methods: { setState: method(object({
66761
67787
  deviceId: number(),
@@ -68968,6 +69994,7 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
68968
69994
  kind: "mutation",
68969
69995
  auth: "admin"
68970
69996
  });
69997
+ DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
68971
69998
  /**
68972
69999
  * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
68973
70000
  * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
@@ -69352,7 +70379,7 @@ function tryConvertUnit(value, from, to) {
69352
70379
  return fromCanonical(toCanonical(value, fromSpec), toSpec);
69353
70380
  }
69354
70381
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
69355
- 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;
70382
+ 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;
69356
70383
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
69357
70384
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
69358
70385
  new Set(CAP_NAMES);
@@ -69681,6 +70708,9 @@ var DEVICE_LOCAL_STATE_CAPS = {
69681
70708
  featureProbe: featureProbeCapability,
69682
70709
  flood: floodCapability,
69683
70710
  gas: gasCapability,
70711
+ groupActive: groupActiveCapability,
70712
+ groupCommands: groupCommandsCapability,
70713
+ groupStatus: groupStatusCapability,
69684
70714
  heatingValve: heatingValveCapability,
69685
70715
  humidifier: humidifierCapability,
69686
70716
  humiditySensor: humiditySensorCapability,
@@ -69690,6 +70720,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
69690
70720
  lawnMowerControl: lawnMowerControlCapability,
69691
70721
  lockControl: lockControlCapability,
69692
70722
  mediaPlayer: mediaPlayerCapability,
70723
+ meter: meterCapability,
69693
70724
  motion: motionCapability,
69694
70725
  motionTrigger: motionTriggerCapability,
69695
70726
  motionZones: motionZonesCapability,
@@ -70949,7 +71980,7 @@ var BaseDeviceProvider = class extends BaseAddon {
70949
71980
  return toDeviceSummary(device, this.addonId);
70950
71981
  }
70951
71982
  };
70952
- 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;
71983
+ 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;
70953
71984
  new Set(Object.values(DeviceType));
70954
71985
  Object.freeze({
70955
71986
  "accessories.getStatus": {
@@ -71057,6 +72088,13 @@ Object.freeze({
71057
72088
  access: "view",
71058
72089
  role: "admin"
71059
72090
  },
72091
+ "addons.getPackageDocs": {
72092
+ capName: "addons",
72093
+ capScope: "system",
72094
+ addonId: null,
72095
+ access: "view",
72096
+ role: "admin"
72097
+ },
71060
72098
  "addons.getVersions": {
71061
72099
  capName: "addons",
71062
72100
  capScope: "system",
@@ -74165,6 +75203,27 @@ Object.freeze({
74165
75203
  access: "view",
74166
75204
  role: "viewer"
74167
75205
  },
75206
+ "groupActive.getStatus": {
75207
+ capName: "group-active",
75208
+ capScope: "device",
75209
+ addonId: null,
75210
+ access: "view",
75211
+ role: "viewer"
75212
+ },
75213
+ "groupCommands.getStatus": {
75214
+ capName: "group-commands",
75215
+ capScope: "device",
75216
+ addonId: null,
75217
+ access: "view",
75218
+ role: "viewer"
75219
+ },
75220
+ "groupStatus.getStatus": {
75221
+ capName: "group-status",
75222
+ capScope: "device",
75223
+ addonId: null,
75224
+ access: "view",
75225
+ role: "viewer"
75226
+ },
74168
75227
  "heatingValve.getStatus": {
74169
75228
  capName: "heating-valve",
74170
75229
  capScope: "device",
@@ -75117,6 +76176,13 @@ Object.freeze({
75117
76176
  access: "create",
75118
76177
  role: "admin"
75119
76178
  },
76179
+ "meter.getStatus": {
76180
+ capName: "meter",
76181
+ capScope: "device",
76182
+ addonId: null,
76183
+ access: "view",
76184
+ role: "viewer"
76185
+ },
75120
76186
  "metricsProvider.collectSnapshot": {
75121
76187
  capName: "metrics-provider",
75122
76188
  capScope: "system",
@@ -83215,7 +84281,39 @@ var APPLIANCE_CAP_VOCABULARY = {
83215
84281
  phrase: t("When {device} finishes", "Quando {device} finisce"),
83216
84282
  icon: "square"
83217
84283
  }
83218
- }
84284
+ },
84285
+ overrides: { byType: {
84286
+ [DeviceType.Washer]: {
84287
+ state: { running: {
84288
+ whenTrue: t("When the washing machine starts", "Quando la lavatrice inizia"),
84289
+ whenFalse: t("When the washing machine has finished", "Quando la lavatrice ha finito")
84290
+ } },
84291
+ events: {
84292
+ onStarted: { phrase: t("When the washing machine starts", "Quando la lavatrice inizia") },
84293
+ onFinished: { phrase: t("When the washing machine has finished", "Quando la lavatrice ha finito") }
84294
+ }
84295
+ },
84296
+ [DeviceType.Dryer]: {
84297
+ state: { running: {
84298
+ whenTrue: t("When the dryer starts", "Quando l'asciugatrice inizia"),
84299
+ whenFalse: t("When the dryer has finished", "Quando l'asciugatrice ha finito")
84300
+ } },
84301
+ events: {
84302
+ onStarted: { phrase: t("When the dryer starts", "Quando l'asciugatrice inizia") },
84303
+ onFinished: { phrase: t("When the dryer has finished", "Quando l'asciugatrice ha finito") }
84304
+ }
84305
+ },
84306
+ [DeviceType.Dishwasher]: {
84307
+ state: { running: {
84308
+ whenTrue: t("When the dishwasher starts", "Quando la lavastoviglie inizia"),
84309
+ whenFalse: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito")
84310
+ } },
84311
+ events: {
84312
+ onStarted: { phrase: t("When the dishwasher starts", "Quando la lavastoviglie inizia") },
84313
+ onFinished: { phrase: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito") }
84314
+ }
84315
+ }
84316
+ } }
83219
84317
  },
83220
84318
  update: {
83221
84319
  scope: "device",
@@ -83987,6 +85085,41 @@ var SENSOR_CAP_VOCABULARY = {
83987
85085
  icon: "flame"
83988
85086
  }]
83989
85087
  },
85088
+ "group-active": {
85089
+ scope: "device",
85090
+ noun: t("Group state", "Stato del gruppo")
85091
+ },
85092
+ "group-commands": {
85093
+ scope: "device",
85094
+ noun: t("Group command", "Comando di gruppo")
85095
+ },
85096
+ "group-status": {
85097
+ scope: "device",
85098
+ noun: t("Group", "Gruppo"),
85099
+ state: [
85100
+ {
85101
+ kind: "numeric",
85102
+ field: "included",
85103
+ quantity: t("the members reporting", "i membri che rispondono"),
85104
+ label: t("Reporting", "Rispondono"),
85105
+ icon: "users"
85106
+ },
85107
+ {
85108
+ kind: "numeric",
85109
+ field: "excluded",
85110
+ quantity: t("the members left out", "i membri esclusi"),
85111
+ label: t("Excluded", "Esclusi"),
85112
+ icon: "users"
85113
+ },
85114
+ {
85115
+ kind: "numeric",
85116
+ field: "total",
85117
+ quantity: t("the member count", "il numero di membri"),
85118
+ label: t("Members", "Membri"),
85119
+ icon: "sigma"
85120
+ }
85121
+ ]
85122
+ },
83990
85123
  "humidity-sensor": {
83991
85124
  scope: "device",
83992
85125
  noun: t("Humidity", "Umidità"),
@@ -84012,6 +85145,17 @@ var SENSOR_CAP_VOCABULARY = {
84012
85145
  }],
84013
85146
  events: { onMotionChanged: { notOperatorFacing: "the `detected` state trigger phrases both edges, from the mirror that survives a dropped event (D49)" } }
84014
85147
  },
85148
+ meter: {
85149
+ scope: "device",
85150
+ noun: t("Meter", "Contatore"),
85151
+ state: [{
85152
+ kind: "numeric",
85153
+ field: "value",
85154
+ quantity: t("the total", "il totale"),
85155
+ label: t("Total", "Totale"),
85156
+ icon: "gauge"
85157
+ }]
85158
+ },
84015
85159
  "numeric-sensor": {
84016
85160
  scope: "device",
84017
85161
  noun: t("Sensor", "Sensore"),
@@ -84484,8 +85628,47 @@ function projectAction(cap, action) {
84484
85628
  };
84485
85629
  }
84486
85630
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
84487
- 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;
85631
+ 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;
84488
85632
  DeviceFeature.BatteryOperated;
85633
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
85634
+ 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.";
85635
+ 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.";
85636
+ var ALEXA_REASON = "Alexa has no laundry category in this export, and neither power nor running has an Alexa interface.";
85637
+ var GOOGLE_REASON = "Google WASHER, DRYER and DISHWASHER require RunCycle. A power threshold is not that trait, and this export does not publish RunCycle.";
85638
+ 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.";
85639
+ function withheld(reason) {
85640
+ return {
85641
+ [DeviceType.Washer]: {
85642
+ exported: false,
85643
+ reason
85644
+ },
85645
+ [DeviceType.Dryer]: {
85646
+ exported: false,
85647
+ reason
85648
+ },
85649
+ [DeviceType.Dishwasher]: {
85650
+ exported: false,
85651
+ reason
85652
+ }
85653
+ };
85654
+ }
85655
+ function offered(received) {
85656
+ return {
85657
+ [DeviceType.Washer]: {
85658
+ exported: true,
85659
+ as: received
85660
+ },
85661
+ [DeviceType.Dryer]: {
85662
+ exported: true,
85663
+ as: received
85664
+ },
85665
+ [DeviceType.Dishwasher]: {
85666
+ exported: true,
85667
+ as: received
85668
+ }
85669
+ };
85670
+ }
85671
+ offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
84489
85672
  Object.freeze({
84490
85673
  "accessories.getStatus": [{
84491
85674
  name: "deviceId",
@@ -85452,6 +86635,21 @@ Object.freeze({
85452
86635
  form: "single",
85453
86636
  optional: false
85454
86637
  }],
86638
+ "groupActive.getStatus": [{
86639
+ name: "deviceId",
86640
+ form: "single",
86641
+ optional: false
86642
+ }],
86643
+ "groupCommands.getStatus": [{
86644
+ name: "deviceId",
86645
+ form: "single",
86646
+ optional: false
86647
+ }],
86648
+ "groupStatus.getStatus": [{
86649
+ name: "deviceId",
86650
+ form: "single",
86651
+ optional: false
86652
+ }],
85455
86653
  "heatingValve.getStatus": [{
85456
86654
  name: "deviceId",
85457
86655
  form: "single",
@@ -85652,6 +86850,11 @@ Object.freeze({
85652
86850
  form: "single",
85653
86851
  optional: false
85654
86852
  }],
86853
+ "meter.getStatus": [{
86854
+ name: "deviceId",
86855
+ form: "single",
86856
+ optional: false
86857
+ }],
85655
86858
  "motion.getStatus": [{
85656
86859
  name: "deviceId",
85657
86860
  form: "single",
@@ -87259,7 +88462,6 @@ Object.freeze({
87259
88462
  "network-access": "ingress",
87260
88463
  "smtp-provider": "email"
87261
88464
  });
87262
- DeviceType.Camera;
87263
88465
  /**
87264
88466
  * A saved Customize assignment (D936). The schema is the type: callers use
87265
88467
  * `z.infer`, they do not restate the shape.
@@ -87436,6 +88638,8 @@ new Map(Object.entries({
87436
88638
  "detection-blind": "device-events",
87437
88639
  "motion-noisy": "device-events",
87438
88640
  "motion-calm": "device-events",
88641
+ "package-llm-silent": "device-events",
88642
+ "package-llm-answered": "device-events",
87439
88643
  "recording-stalled": "device-events",
87440
88644
  "recording-healthy": "device-events",
87441
88645
  "recording-degraded": "device-events",
@@ -87519,6 +88723,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
87519
88723
  label: "Motion without a subject",
87520
88724
  kinds: ["motion-noisy", "motion-calm"]
87521
88725
  },
88726
+ {
88727
+ id: "package-llm",
88728
+ family: "device-events",
88729
+ label: "Package confirm",
88730
+ kinds: ["package-llm-silent", "package-llm-answered"]
88731
+ },
87522
88732
  {
87523
88733
  id: "recording",
87524
88734
  family: "device-events",
@@ -87700,6 +88910,8 @@ var DEVICE_EVENTS = [
87700
88910
  "detection-blind",
87701
88911
  "motion-noisy",
87702
88912
  "motion-calm",
88913
+ "package-llm-silent",
88914
+ "package-llm-answered",
87703
88915
  "recording-stalled",
87704
88916
  "recording-healthy",
87705
88917
  "recording-degraded",
@@ -87968,6 +89180,15 @@ new Map([
87968
89180
  snapshotLabel: "Cameras with constant motion now",
87969
89181
  snapshotSection: null
87970
89182
  },
89183
+ {
89184
+ id: "package-llm-confirm",
89185
+ family: "device-events",
89186
+ label: "Package confirm (not answering / answering again)",
89187
+ bad: ["package-llm-silent"],
89188
+ recovery: ["package-llm-answered"],
89189
+ snapshotLabel: "Cameras whose package confirm is not answering",
89190
+ snapshotSection: null
89191
+ },
87971
89192
  {
87972
89193
  id: "recording-health",
87973
89194
  family: "device-events",