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