@camstack/addon-provider-enpal 0.1.22 → 0.1.23

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
@@ -3,7 +3,7 @@ let node_child_process = require("node:child_process");
3
3
  let node_fs = require("node:fs");
4
4
  let node_path = require("node:path");
5
5
  let node_url = require("node:url");
6
- //#region ../types/dist/event-category-Dv-Gcv1f.mjs
6
+ //#region ../types/dist/event-category-Dhcd0TRH.mjs
7
7
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
8
8
  EventCategory["SystemBoot"] = "system.boot";
9
9
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -277,6 +277,18 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
277
277
  EventCategory["StorageLocationCritical"] = "storage.location.critical";
278
278
  /** Recovery from low/critical on a storage location. */
279
279
  EventCategory["StorageLocationOk"] = "storage.location.ok";
280
+ /**
281
+ * A `drain` location's regular progress ping (#538) — location id + name,
282
+ * bytes remaining, the observed cede rate and the estimated empty date, or
283
+ * `null` fields when the occupancy read that sweep failed (D393: never
284
+ * folded to 0). Cadence is the named constant
285
+ * `DRAIN_PROGRESS_EVENT_INTERVAL_MS` (15 min) plus once on drain start — no
286
+ * operator knob. Telemetry (D8): a lost tick is a stale progress bar the
287
+ * next sweep corrects.
288
+ */
289
+ EventCategory["StorageDrainProgress"] = "storage.drain.progress";
290
+ /** Fires once when a draining location reaches zero evictable bytes. */
291
+ EventCategory["StorageDrainComplete"] = "storage.drain.complete";
280
292
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
281
293
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
282
294
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
@@ -815,7 +827,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
815
827
  return EventCategory;
816
828
  }({});
817
829
  //#endregion
818
- //#region ../types/dist/composed-stable-id-COP6zOXx.mjs
830
+ //#region ../types/dist/composed-stable-id-RvQIEpqg.mjs
819
831
  /**
820
832
  import { errMsg } from '@camstack/types'
821
833
  * Extract a human-readable message from an unknown error value.
@@ -897,6 +909,9 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
897
909
  /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
898
910
  * `weather` cap. */
899
911
  DeviceType["Weather"] = "weather";
912
+ /** A physical weather station (Ecowitt outdoor array). `weather-station`
913
+ * cap. Distinct from {@link Weather}, which is a forecast provider. */
914
+ DeviceType["WeatherStation"] = "weather-station";
900
915
  /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
901
916
  DeviceType["Vacuum"] = "vacuum";
902
917
  /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
@@ -6064,7 +6079,7 @@ var ZodIssueCode = {
6064
6079
  var ZodFirstPartyTypeKind;
6065
6080
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6066
6081
  //#endregion
6067
- //#region ../types/dist/sleep-B5XPl2YP.mjs
6082
+ //#region ../types/dist/sleep-CLDVTB5R.mjs
6068
6083
  /**
6069
6084
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6070
6085
  * window to float samples (D455).
@@ -13446,6 +13461,76 @@ var CAP_VOCABULARY_BY_NAME = {
13446
13461
  en: "Control",
13447
13462
  it: "Controllo"
13448
13463
  },
13464
+ actions: [
13465
+ {
13466
+ id: "select.next",
13467
+ method: "next",
13468
+ label: {
13469
+ en: "Next option",
13470
+ it: "Opzione successiva"
13471
+ },
13472
+ phrase: {
13473
+ en: "Select the next option on {device}",
13474
+ it: "Seleziona l’opzione successiva su {device}"
13475
+ },
13476
+ icon: "skip-forward",
13477
+ openArgs: [{
13478
+ key: "cycle",
13479
+ label: {
13480
+ en: "Wrap to the first option",
13481
+ it: "Ricomincia dalla prima opzione"
13482
+ },
13483
+ offer: { kind: "boolean" }
13484
+ }]
13485
+ },
13486
+ {
13487
+ id: "select.previous",
13488
+ method: "previous",
13489
+ label: {
13490
+ en: "Previous option",
13491
+ it: "Opzione precedente"
13492
+ },
13493
+ phrase: {
13494
+ en: "Select the previous option on {device}",
13495
+ it: "Seleziona l’opzione precedente su {device}"
13496
+ },
13497
+ icon: "skip-back",
13498
+ openArgs: [{
13499
+ key: "cycle",
13500
+ label: {
13501
+ en: "Wrap to the last option",
13502
+ it: "Ricomincia dall’ultima opzione"
13503
+ },
13504
+ offer: { kind: "boolean" }
13505
+ }]
13506
+ },
13507
+ {
13508
+ id: "select.first",
13509
+ method: "first",
13510
+ label: {
13511
+ en: "First option",
13512
+ it: "Prima opzione"
13513
+ },
13514
+ phrase: {
13515
+ en: "Select the first option on {device}",
13516
+ it: "Seleziona la prima opzione su {device}"
13517
+ },
13518
+ icon: "chevron-first"
13519
+ },
13520
+ {
13521
+ id: "select.last",
13522
+ method: "last",
13523
+ label: {
13524
+ en: "Last option",
13525
+ it: "Ultima opzione"
13526
+ },
13527
+ phrase: {
13528
+ en: "Select the last option on {device}",
13529
+ it: "Seleziona l’ultima opzione su {device}"
13530
+ },
13531
+ icon: "chevron-last"
13532
+ }
13533
+ ],
13449
13534
  notOperatorFacing: { setValue: "the value shape depends on the control kind (numeric / select / text / datetime) read from status; a typed picker for it is a follow-up of the automations rework" },
13450
13535
  state: [{
13451
13536
  kind: "value",
@@ -18592,6 +18677,295 @@ var CAP_VOCABULARY_BY_NAME = {
18592
18677
  }
18593
18678
  ]
18594
18679
  },
18680
+ "weather-station": {
18681
+ scope: "device",
18682
+ noun: {
18683
+ en: "Weather station",
18684
+ it: "Stazione meteo"
18685
+ },
18686
+ state: [
18687
+ {
18688
+ kind: "numeric",
18689
+ field: "temperature",
18690
+ quantity: {
18691
+ en: "the outdoor temperature",
18692
+ it: "la temperatura esterna"
18693
+ },
18694
+ label: {
18695
+ en: "Temperature",
18696
+ it: "Temperatura"
18697
+ },
18698
+ unit: "°C",
18699
+ icon: "thermometer"
18700
+ },
18701
+ {
18702
+ kind: "numeric",
18703
+ field: "humidity",
18704
+ quantity: {
18705
+ en: "the outdoor humidity",
18706
+ it: "l’umidità esterna"
18707
+ },
18708
+ label: {
18709
+ en: "Humidity",
18710
+ it: "Umidità"
18711
+ },
18712
+ unit: "%",
18713
+ icon: "droplets"
18714
+ },
18715
+ {
18716
+ kind: "numeric",
18717
+ field: "pressure",
18718
+ quantity: {
18719
+ en: "the air pressure",
18720
+ it: "la pressione atmosferica"
18721
+ },
18722
+ label: {
18723
+ en: "Pressure",
18724
+ it: "Pressione"
18725
+ },
18726
+ unit: "hPa",
18727
+ icon: "gauge"
18728
+ },
18729
+ {
18730
+ kind: "numeric",
18731
+ field: "windSpeed",
18732
+ quantity: {
18733
+ en: "the wind speed",
18734
+ it: "la velocità del vento"
18735
+ },
18736
+ label: {
18737
+ en: "Wind",
18738
+ it: "Vento"
18739
+ },
18740
+ unit: "m/s",
18741
+ icon: "wind"
18742
+ },
18743
+ {
18744
+ kind: "numeric",
18745
+ field: "windGust",
18746
+ quantity: {
18747
+ en: "the wind gust",
18748
+ it: "la raffica di vento"
18749
+ },
18750
+ label: {
18751
+ en: "Gust",
18752
+ it: "Raffica"
18753
+ },
18754
+ unit: "m/s",
18755
+ icon: "wind"
18756
+ },
18757
+ {
18758
+ kind: "numeric",
18759
+ field: "windBearing",
18760
+ quantity: {
18761
+ en: "the wind bearing",
18762
+ it: "la direzione del vento"
18763
+ },
18764
+ label: {
18765
+ en: "Wind bearing",
18766
+ it: "Direzione del vento"
18767
+ },
18768
+ unit: "°",
18769
+ icon: "wind"
18770
+ },
18771
+ {
18772
+ kind: "numeric",
18773
+ field: "rainRate",
18774
+ quantity: {
18775
+ en: "the rain rate",
18776
+ it: "l’intensità della pioggia"
18777
+ },
18778
+ label: {
18779
+ en: "Rain rate",
18780
+ it: "Intensità pioggia"
18781
+ },
18782
+ unit: "mm/h",
18783
+ icon: "droplets"
18784
+ },
18785
+ {
18786
+ kind: "numeric",
18787
+ field: "rainDaily",
18788
+ quantity: {
18789
+ en: "the rain today",
18790
+ it: "la pioggia di oggi"
18791
+ },
18792
+ label: {
18793
+ en: "Rain today",
18794
+ it: "Pioggia oggi"
18795
+ },
18796
+ unit: "mm",
18797
+ icon: "droplets"
18798
+ },
18799
+ {
18800
+ kind: "numeric",
18801
+ field: "rainEvent",
18802
+ quantity: {
18803
+ en: "the rain this event",
18804
+ it: "la pioggia di questo evento"
18805
+ },
18806
+ label: {
18807
+ en: "Rain event",
18808
+ it: "Evento di pioggia"
18809
+ },
18810
+ unit: "mm",
18811
+ icon: "droplets"
18812
+ },
18813
+ {
18814
+ kind: "numeric",
18815
+ field: "uv",
18816
+ quantity: {
18817
+ en: "the UV index",
18818
+ it: "l’indice UV"
18819
+ },
18820
+ label: {
18821
+ en: "UV",
18822
+ it: "UV"
18823
+ },
18824
+ unit: "",
18825
+ icon: "cloud-sun"
18826
+ },
18827
+ {
18828
+ kind: "numeric",
18829
+ field: "solarRadiation",
18830
+ quantity: {
18831
+ en: "the solar radiation",
18832
+ it: "la radiazione solare"
18833
+ },
18834
+ label: {
18835
+ en: "Solar",
18836
+ it: "Sole"
18837
+ },
18838
+ unit: "W/m²",
18839
+ icon: "cloud-sun"
18840
+ },
18841
+ {
18842
+ kind: "numeric",
18843
+ field: "dewPoint",
18844
+ quantity: {
18845
+ en: "the dew point",
18846
+ it: "il punto di rugiada"
18847
+ },
18848
+ label: {
18849
+ en: "Dew point",
18850
+ it: "Punto di rugiada"
18851
+ },
18852
+ unit: "°C",
18853
+ icon: "thermometer"
18854
+ },
18855
+ {
18856
+ kind: "numeric",
18857
+ field: "feelsLike",
18858
+ quantity: {
18859
+ en: "the feels-like temperature",
18860
+ it: "la temperatura percepita"
18861
+ },
18862
+ label: {
18863
+ en: "Feels like",
18864
+ it: "Percepita"
18865
+ },
18866
+ unit: "°C",
18867
+ icon: "thermometer"
18868
+ },
18869
+ {
18870
+ kind: "numeric",
18871
+ field: "vpd",
18872
+ quantity: {
18873
+ en: "the vapour-pressure deficit",
18874
+ it: "il deficit di pressione di vapore"
18875
+ },
18876
+ label: {
18877
+ en: "VPD",
18878
+ it: "VPD"
18879
+ },
18880
+ unit: "kPa",
18881
+ icon: "gauge"
18882
+ },
18883
+ {
18884
+ kind: "numeric",
18885
+ field: "windGustMax",
18886
+ quantity: {
18887
+ en: "the highest gust today",
18888
+ it: "la raffica massima di oggi"
18889
+ },
18890
+ label: {
18891
+ en: "Max gust",
18892
+ it: "Raffica max"
18893
+ },
18894
+ unit: "m/s",
18895
+ icon: "wind"
18896
+ },
18897
+ {
18898
+ kind: "numeric",
18899
+ field: "windBearingAvg",
18900
+ quantity: {
18901
+ en: "the average wind bearing",
18902
+ it: "la direzione media del vento"
18903
+ },
18904
+ label: {
18905
+ en: "Avg bearing",
18906
+ it: "Direzione media"
18907
+ },
18908
+ unit: "°",
18909
+ icon: "wind"
18910
+ },
18911
+ {
18912
+ kind: "numeric",
18913
+ field: "rain24h",
18914
+ quantity: {
18915
+ en: "the rain in the last 24 hours",
18916
+ it: "la pioggia delle ultime 24 ore"
18917
+ },
18918
+ label: {
18919
+ en: "Rain 24 h",
18920
+ it: "Pioggia 24 h"
18921
+ },
18922
+ unit: "mm",
18923
+ icon: "droplets"
18924
+ },
18925
+ {
18926
+ kind: "numeric",
18927
+ field: "rainWeekly",
18928
+ quantity: {
18929
+ en: "the rain this week",
18930
+ it: "la pioggia di questa settimana"
18931
+ },
18932
+ label: {
18933
+ en: "Rain week",
18934
+ it: "Pioggia settimana"
18935
+ },
18936
+ unit: "mm",
18937
+ icon: "droplets"
18938
+ },
18939
+ {
18940
+ kind: "numeric",
18941
+ field: "rainMonthly",
18942
+ quantity: {
18943
+ en: "the rain this month",
18944
+ it: "la pioggia di questo mese"
18945
+ },
18946
+ label: {
18947
+ en: "Rain month",
18948
+ it: "Pioggia mese"
18949
+ },
18950
+ unit: "mm",
18951
+ icon: "droplets"
18952
+ },
18953
+ {
18954
+ kind: "numeric",
18955
+ field: "rainYearly",
18956
+ quantity: {
18957
+ en: "the rain this year",
18958
+ it: "la pioggia di quest’anno"
18959
+ },
18960
+ label: {
18961
+ en: "Rain year",
18962
+ it: "Pioggia anno"
18963
+ },
18964
+ unit: "mm",
18965
+ icon: "droplets"
18966
+ }
18967
+ ]
18968
+ },
18595
18969
  "battery": {
18596
18970
  scope: "device",
18597
18971
  noun: {
@@ -18816,6 +19190,30 @@ var CAP_VOCABULARY_BY_NAME = {
18816
19190
  icon: "wifi-off"
18817
19191
  }]
18818
19192
  },
19193
+ "error": {
19194
+ scope: "device",
19195
+ noun: {
19196
+ en: "Fault",
19197
+ it: "Guasto"
19198
+ },
19199
+ state: [{
19200
+ kind: "boolean",
19201
+ field: "active",
19202
+ label: {
19203
+ en: "Fault",
19204
+ it: "Guasto"
19205
+ },
19206
+ whenTrue: {
19207
+ en: "When {device} reports a fault",
19208
+ it: "Quando {device} segnala un guasto"
19209
+ },
19210
+ whenFalse: {
19211
+ en: "When the fault of {device} clears",
19212
+ it: "Quando il guasto di {device} si risolve"
19213
+ },
19214
+ icon: "triangle-alert"
19215
+ }]
19216
+ },
18819
19217
  "doorbell": {
18820
19218
  scope: "device",
18821
19219
  noun: {
@@ -19506,6 +19904,7 @@ var CAP_NAMES = Object.freeze([
19506
19904
  "embedding-encoder",
19507
19905
  "energy-system",
19508
19906
  "enum-sensor",
19907
+ "error",
19509
19908
  "event-emitter",
19510
19909
  "face-gallery",
19511
19910
  "failure-contribution",
@@ -19636,6 +20035,7 @@ var CAP_NAMES = Object.freeze([
19636
20035
  "viewer-ui",
19637
20036
  "water-heater",
19638
20037
  "weather",
20038
+ "weather-station",
19639
20039
  "webrtc-session",
19640
20040
  "zone-analytics",
19641
20041
  "zone-rules",
@@ -19915,6 +20315,18 @@ Object.freeze({
19915
20315
  "dry"
19916
20316
  ]
19917
20317
  },
20318
+ {
20319
+ "path": "action",
20320
+ "valueType": "enum",
20321
+ "enumValues": [
20322
+ "heating",
20323
+ "cooling",
20324
+ "drying",
20325
+ "fan",
20326
+ "idle",
20327
+ "off"
20328
+ ]
20329
+ },
19918
20330
  {
19919
20331
  "path": "fanMode",
19920
20332
  "valueType": "string"
@@ -20383,6 +20795,29 @@ Object.freeze({
20383
20795
  "path": "timestamp",
20384
20796
  "valueType": "boolean"
20385
20797
  }],
20798
+ "error": [
20799
+ {
20800
+ "path": "active",
20801
+ "valueType": "boolean"
20802
+ },
20803
+ {
20804
+ "path": "code",
20805
+ "valueType": "string"
20806
+ },
20807
+ {
20808
+ "path": "label",
20809
+ "valueType": "string"
20810
+ },
20811
+ {
20812
+ "path": "severity",
20813
+ "valueType": "enum",
20814
+ "enumValues": ["warning", "error"]
20815
+ },
20816
+ {
20817
+ "path": "since",
20818
+ "valueType": "number"
20819
+ }
20820
+ ],
20386
20821
  "event-emitter": [
20387
20822
  {
20388
20823
  "path": "lastEvent.deviceId",
@@ -21602,6 +22037,88 @@ Object.freeze({
21602
22037
  "valueType": "number"
21603
22038
  }
21604
22039
  ],
22040
+ "weather-station": [
22041
+ {
22042
+ "path": "temperature",
22043
+ "valueType": "number"
22044
+ },
22045
+ {
22046
+ "path": "humidity",
22047
+ "valueType": "number"
22048
+ },
22049
+ {
22050
+ "path": "pressure",
22051
+ "valueType": "number"
22052
+ },
22053
+ {
22054
+ "path": "windSpeed",
22055
+ "valueType": "number"
22056
+ },
22057
+ {
22058
+ "path": "windGust",
22059
+ "valueType": "number"
22060
+ },
22061
+ {
22062
+ "path": "windBearing",
22063
+ "valueType": "number"
22064
+ },
22065
+ {
22066
+ "path": "rainRate",
22067
+ "valueType": "number"
22068
+ },
22069
+ {
22070
+ "path": "rainDaily",
22071
+ "valueType": "number"
22072
+ },
22073
+ {
22074
+ "path": "rainEvent",
22075
+ "valueType": "number"
22076
+ },
22077
+ {
22078
+ "path": "uv",
22079
+ "valueType": "number"
22080
+ },
22081
+ {
22082
+ "path": "solarRadiation",
22083
+ "valueType": "number"
22084
+ },
22085
+ {
22086
+ "path": "dewPoint",
22087
+ "valueType": "number"
22088
+ },
22089
+ {
22090
+ "path": "feelsLike",
22091
+ "valueType": "number"
22092
+ },
22093
+ {
22094
+ "path": "vpd",
22095
+ "valueType": "number"
22096
+ },
22097
+ {
22098
+ "path": "windGustMax",
22099
+ "valueType": "number"
22100
+ },
22101
+ {
22102
+ "path": "windBearingAvg",
22103
+ "valueType": "number"
22104
+ },
22105
+ {
22106
+ "path": "rain24h",
22107
+ "valueType": "number"
22108
+ },
22109
+ {
22110
+ "path": "rainWeekly",
22111
+ "valueType": "number"
22112
+ },
22113
+ {
22114
+ "path": "rainMonthly",
22115
+ "valueType": "number"
22116
+ },
22117
+ {
22118
+ "path": "rainYearly",
22119
+ "valueType": "number"
22120
+ }
22121
+ ],
21605
22122
  "zone-analytics": [
21606
22123
  {
21607
22124
  "path": "ts",
@@ -21694,7 +22211,7 @@ Object.freeze({
21694
22211
  }
21695
22212
  });
21696
22213
  //#endregion
21697
- //#region ../types/dist/implemented-methods-0xxMOekw.mjs
22214
+ //#region ../types/dist/implemented-methods-C5FMXLGP.mjs
21698
22215
  /**
21699
22216
  * The ffmpeg build every CamStack node downloads, per platform and arch.
21700
22217
  *
@@ -48023,69 +48540,6 @@ var EventVocabularyEntrySchema = object({
48023
48540
  kind: EventVocabularyKindSchema.optional(),
48024
48541
  payload: array(EventVocabularyFieldSchema).optional()
48025
48542
  });
48026
- /**
48027
- * The link an automation made FROM a template carries (#527): which template,
48028
- * the values the operator filled in, and — when the template has since changed
48029
- * so that those values no longer fit — the problems that say why.
48030
- *
48031
- * It lives inside the recipe (`AutomationRecipe.template`), so the one row in
48032
- * `core_blocks` stays the one artefact: there is no second store of instances
48033
- * to drift from the blocks, and deleting the automation deletes the link.
48034
- * "Convert to a free automation" is dropping this field.
48035
- *
48036
- * This file depends on nothing in `recipe.ts`, so the recipe can embed it and
48037
- * the template engine (`template/automation-template.ts`) can import both.
48038
- */
48039
- /** An `event` input's value: the same three narrowing lists a `device-fired` row takes. */
48040
- var TemplateEventValueSchema = object({
48041
- eventIds: array(string().min(1)).min(1).max(64).optional(),
48042
- sourceIds: array(string().min(1)).min(1).max(64).optional(),
48043
- kinds: array(EventVocabularyKindSchema).min(1).optional()
48044
- }).refine((value) => value.eventIds !== void 0 || value.sourceIds !== void 0 || value.kinds !== void 0, { message: "an event names at least one event, part or kind" });
48045
- /** An `action` input's value: a vocabulary action and its open args. */
48046
- var TemplateActionValueSchema = object({
48047
- actionId: string().min(1),
48048
- args: record(string(), unknown()).optional()
48049
- });
48050
- /** The value of one non-group input. Which member is right is the input kind's to judge. */
48051
- var TemplateScalarValueSchema = union([
48052
- number$1(),
48053
- string(),
48054
- boolean(),
48055
- array(number$1().int().nonnegative()),
48056
- TemplateEventValueSchema,
48057
- TemplateActionValueSchema
48058
- ]);
48059
- var TemplateInputValueSchema = union([TemplateScalarValueSchema, array(record(string(), TemplateScalarValueSchema))]);
48060
- /** Input key → value. */
48061
- var TemplateInputValuesSchema = record(string(), TemplateInputValueSchema);
48062
- var TemplateInstanceProblemSchema = object({
48063
- code: _enum([
48064
- "missing-input",
48065
- "invalid-input",
48066
- "invalid-recipe",
48067
- "missing-template",
48068
- "edited-by-hand"
48069
- ]),
48070
- /** Dotted input address (`buttons.2.targets`); absent for a whole-recipe problem. */
48071
- input: string().optional(),
48072
- detail: string()
48073
- });
48074
- var AutomationTemplateLinkSchema = object({
48075
- templateId: string().min(1),
48076
- values: TemplateInputValuesSchema,
48077
- /**
48078
- * Fingerprint of the generated code with this literal blanked
48079
- * (`instanceCodeHash`), stamped by `generateAutomationBlock`. Code that no
48080
- * longer matches it was edited by hand, and is never regenerated over.
48081
- */
48082
- codeHash: string().optional(),
48083
- /**
48084
- * Present when the last re-expansion refused these values. The automation
48085
- * keeps running the body of its last good expansion; nothing is dropped.
48086
- */
48087
- problems: array(TemplateInstanceProblemSchema).min(1).optional()
48088
- });
48089
48543
  var ExpressionBindingSourceSchema = union([
48090
48544
  object({
48091
48545
  kind: literal("field").optional(),
@@ -48121,6 +48575,58 @@ object({
48121
48575
  path: ["expr"]
48122
48576
  });
48123
48577
  });
48578
+ Math.PI / 180;
48579
+ /**
48580
+ * WHEN something happens, as data (D983) — the one time vocabulary of the
48581
+ * scheduler and of an automation's schedule leaf. Three shapes, no cron:
48582
+ *
48583
+ * - `clock` — a wall-clock time every day (`07:30`), read in the site's zone;
48584
+ * - `sun` — a sun event (`sunrise`, `sunset`, `dawn`, `dusk`) plus or minus
48585
+ * up to twelve hours, from the site's coordinates;
48586
+ * - `date` — one local date and time (`2026-12-24T18:00`), once.
48587
+ *
48588
+ * A site is a place and its IANA zone ({@link TimeSite}). Every function here
48589
+ * is pure: the zone arithmetic uses `Intl`, no date library.
48590
+ *
48591
+ * Wall-clock honesty across a DST change: a local time that does not exist
48592
+ * (02:30 on the spring-forward day) happens at the first minute that does
48593
+ * (03:00); one that happens twice (02:30 on the fall-back day) happens at its
48594
+ * FIRST occurrence.
48595
+ */
48596
+ var SunEventSchema = _enum([
48597
+ "sunrise",
48598
+ "sunset",
48599
+ "dawn",
48600
+ "dusk"
48601
+ ]);
48602
+ var TimeSpecSchema = discriminatedUnion("kind", [
48603
+ object({
48604
+ kind: literal("clock"),
48605
+ hh: number$1().int().min(0).max(23),
48606
+ mm: number$1().int().min(0).max(59)
48607
+ }),
48608
+ object({
48609
+ kind: literal("sun"),
48610
+ event: SunEventSchema,
48611
+ /** Minutes after the event (negative: before). */
48612
+ offsetMin: number$1().int().min(-720).max(720)
48613
+ }),
48614
+ object({
48615
+ kind: literal("date"),
48616
+ /** `YYYY-MM-DDTHH:mm`, site-local. */
48617
+ local: string().regex(/^(\d{4}-\d{2}-\d{2})T([01]\d|2[0-3]):([0-5]\d)$/, "expected YYYY-MM-DDTHH:mm")
48618
+ })
48619
+ ]);
48620
+ /** A day of the week as a schedule names it. */
48621
+ var WeekdayKeySchema = _enum([
48622
+ "mon",
48623
+ "tue",
48624
+ "wed",
48625
+ "thu",
48626
+ "fri",
48627
+ "sat",
48628
+ "sun"
48629
+ ]);
48124
48630
  /**
48125
48631
  * Identity envelope for a device's upstream-system metadata.
48126
48632
  *
@@ -48513,58 +49019,6 @@ function macroOnly(macro) {
48513
49019
  };
48514
49020
  }
48515
49021
  }
48516
- Math.PI / 180;
48517
- /**
48518
- * WHEN something happens, as data (D983) — the one time vocabulary of the
48519
- * scheduler and of an automation's schedule leaf. Three shapes, no cron:
48520
- *
48521
- * - `clock` — a wall-clock time every day (`07:30`), read in the site's zone;
48522
- * - `sun` — a sun event (`sunrise`, `sunset`, `dawn`, `dusk`) plus or minus
48523
- * up to twelve hours, from the site's coordinates;
48524
- * - `date` — one local date and time (`2026-12-24T18:00`), once.
48525
- *
48526
- * A site is a place and its IANA zone ({@link TimeSite}). Every function here
48527
- * is pure: the zone arithmetic uses `Intl`, no date library.
48528
- *
48529
- * Wall-clock honesty across a DST change: a local time that does not exist
48530
- * (02:30 on the spring-forward day) happens at the first minute that does
48531
- * (03:00); one that happens twice (02:30 on the fall-back day) happens at its
48532
- * FIRST occurrence.
48533
- */
48534
- var SunEventSchema = _enum([
48535
- "sunrise",
48536
- "sunset",
48537
- "dawn",
48538
- "dusk"
48539
- ]);
48540
- var TimeSpecSchema = discriminatedUnion("kind", [
48541
- object({
48542
- kind: literal("clock"),
48543
- hh: number$1().int().min(0).max(23),
48544
- mm: number$1().int().min(0).max(59)
48545
- }),
48546
- object({
48547
- kind: literal("sun"),
48548
- event: SunEventSchema,
48549
- /** Minutes after the event (negative: before). */
48550
- offsetMin: number$1().int().min(-720).max(720)
48551
- }),
48552
- object({
48553
- kind: literal("date"),
48554
- /** `YYYY-MM-DDTHH:mm`, site-local. */
48555
- local: string().regex(/^(\d{4}-\d{2}-\d{2})T([01]\d|2[0-3]):([0-5]\d)$/, "expected YYYY-MM-DDTHH:mm")
48556
- })
48557
- ]);
48558
- /** A day of the week as a schedule names it. */
48559
- var WeekdayKeySchema = _enum([
48560
- "mon",
48561
- "tue",
48562
- "wed",
48563
- "thu",
48564
- "fri",
48565
- "sat",
48566
- "sun"
48567
- ]);
48568
49022
  /**
48569
49023
  * Hold semantics — "for N seconds" on a leaf of the `when` tree (#498).
48570
49024
  *
@@ -48821,24 +49275,33 @@ var StationaryPresentLeafSchema = object({
48821
49275
  * (`kind: 'date'`) is refused: only a clock time or a sun event makes a window
48822
49276
  * that repeats every day.
48823
49277
  */
48824
- var TimeIsLeafSchema = object({
48825
- kind: literal("time-is"),
49278
+ var timeIsWindowShape = {
48826
49279
  between: object({
48827
49280
  from: TimeSpecSchema,
48828
49281
  to: TimeSpecSchema
48829
49282
  }).optional(),
48830
49283
  days: array(WeekdayKeySchema).min(1).max(7).optional()
48831
- }).superRefine((leaf, ctx) => {
48832
- if (leaf.between === void 0 && leaf.days === void 0) ctx.addIssue({
49284
+ };
49285
+ function timeIsWindowProblems(window, ctx) {
49286
+ if (window.between === void 0 && window.days === void 0) ctx.addIssue({
48833
49287
  code: "custom",
48834
49288
  message: "a time-is leaf needs a between window, days, or both"
48835
49289
  });
48836
- for (const end of ["from", "to"]) if (leaf.between?.[end].kind === "date") ctx.addIssue({
49290
+ for (const end of ["from", "to"]) if (window.between?.[end].kind === "date") ctx.addIssue({
48837
49291
  code: "custom",
48838
49292
  message: "a one-date time cannot bound a daily window: use a clock time or a sun event",
48839
49293
  path: ["between", end]
48840
49294
  });
48841
- });
49295
+ }
49296
+ /**
49297
+ * What a `time-is` leaf holds beside its kind, judged by the leaf's own rules.
49298
+ * A template's time-window input stores exactly this.
49299
+ */
49300
+ var TimeIsWindowSchema = object(timeIsWindowShape).superRefine(timeIsWindowProblems);
49301
+ var TimeIsLeafSchema = object({
49302
+ kind: literal("time-is"),
49303
+ ...timeIsWindowShape
49304
+ }).superRefine(timeIsWindowProblems);
48842
49305
  /**
48843
49306
  * True for the run that one of the named event leaves started. It reads the
48844
49307
  * run, not a device: in the tree it is judged against the leaf that fired, in
@@ -49046,6 +49509,79 @@ function validateWhenTree(when) {
49046
49509
  return null;
49047
49510
  }
49048
49511
  /**
49512
+ * The link an automation made FROM a template carries (#527): which template,
49513
+ * the values the operator filled in, and — when the template has since changed
49514
+ * so that those values no longer fit — the problems that say why.
49515
+ *
49516
+ * It lives inside the recipe (`AutomationRecipe.template`), so the one row in
49517
+ * `core_blocks` stays the one artefact: there is no second store of instances
49518
+ * to drift from the blocks, and deleting the automation deletes the link.
49519
+ * "Convert to a free automation" is dropping this field.
49520
+ *
49521
+ * This file depends on nothing in `recipe.ts`, so the recipe can embed it and
49522
+ * the template engine (`template/automation-template.ts`) can import both.
49523
+ */
49524
+ /** An `event` input's value: the same three narrowing lists a `device-fired` row takes. */
49525
+ var TemplateEventValueSchema = object({
49526
+ eventIds: array(string().min(1)).min(1).max(64).optional(),
49527
+ sourceIds: array(string().min(1)).min(1).max(64).optional(),
49528
+ kinds: array(EventVocabularyKindSchema).min(1).optional()
49529
+ }).refine((value) => value.eventIds !== void 0 || value.sourceIds !== void 0 || value.kinds !== void 0, { message: "an event names at least one event, part or kind" });
49530
+ /** An `action` input's value: a vocabulary action and its open args. */
49531
+ var TemplateActionValueSchema = object({
49532
+ actionId: string().min(1),
49533
+ args: record(string(), unknown()).optional()
49534
+ });
49535
+ /**
49536
+ * A `time-window` input's value: what a `time-is` leaf holds beside its kind.
49537
+ * "No limit" is the input left without a value, never an empty object.
49538
+ */
49539
+ var TemplateTimeWindowValueSchema = TimeIsWindowSchema;
49540
+ var TemplateEntriesValueSchema = array(record(string(), unknown()));
49541
+ /** The value of one non-group input. Which member is right is the input kind's to judge. */
49542
+ var TemplateScalarValueSchema = union([
49543
+ number$1(),
49544
+ string(),
49545
+ boolean(),
49546
+ array(number$1().int().nonnegative()),
49547
+ TemplateEventValueSchema,
49548
+ TemplateActionValueSchema,
49549
+ TemplateTimeWindowValueSchema,
49550
+ TemplateEntriesValueSchema
49551
+ ]);
49552
+ /** What a group row holds under one key: a value, or the rows of a group it nests. */
49553
+ var TemplateRowValueSchema = union([TemplateScalarValueSchema, array(record(string(), TemplateScalarValueSchema))]);
49554
+ var TemplateInputValueSchema = union([TemplateScalarValueSchema, array(record(string(), TemplateRowValueSchema))]);
49555
+ /** Input key → value. */
49556
+ var TemplateInputValuesSchema = record(string(), TemplateInputValueSchema);
49557
+ var TemplateInstanceProblemSchema = object({
49558
+ code: _enum([
49559
+ "missing-input",
49560
+ "invalid-input",
49561
+ "invalid-recipe",
49562
+ "missing-template",
49563
+ "edited-by-hand"
49564
+ ]),
49565
+ /** Dotted input address (`buttons.2.targets`, `buttons.0.commands.1.targets`); absent for a whole-recipe problem. */
49566
+ input: string().optional(),
49567
+ detail: string()
49568
+ });
49569
+ var AutomationTemplateLinkSchema = object({
49570
+ templateId: string().min(1),
49571
+ values: TemplateInputValuesSchema,
49572
+ /**
49573
+ * Fingerprint of the generated code with this literal blanked
49574
+ * (`instanceCodeHash`), stamped by `generateAutomationBlock`. Code that no
49575
+ * longer matches it was edited by hand, and is never regenerated over.
49576
+ */
49577
+ codeHash: string().optional(),
49578
+ /**
49579
+ * Present when the last re-expansion refused these values. The automation
49580
+ * keeps running the body of its last good expansion; nothing is dropped.
49581
+ */
49582
+ problems: array(TemplateInstanceProblemSchema).min(1).optional()
49583
+ });
49584
+ /**
49049
49585
  * The recipe shape before the single `when` tree: a flat list of triggers
49050
49586
  * (OR'd, each an instant) and an optional condition tree. Stored recipes —
49051
49587
  * the `AUTOMATION_RECIPE` literal of every block saved so far, and whatever an
@@ -55023,6 +55559,16 @@ var HvacModeSchema = _enum([
55023
55559
  var ClimateControlStatusSchema = object({
55024
55560
  /** Active HVAC mode. */
55025
55561
  mode: HvacModeSchema,
55562
+ /** What the device is doing now (`heating` while the valve is open, `idle`
55563
+ * at setpoint). Absent or null = the device does not say; never a guess (D393). */
55564
+ action: _enum([
55565
+ "heating",
55566
+ "cooling",
55567
+ "drying",
55568
+ "fan",
55569
+ "idle",
55570
+ "off"
55571
+ ]).nullable().optional(),
55026
55572
  /** Available HVAC modes the device accepts. Subset of HvacMode. */
55027
55573
  availableModes: array(HvacModeSchema),
55028
55574
  /** Active fan mode (`auto` / `low` / `medium` / `high` / `on` / `off`
@@ -55076,6 +55622,40 @@ var ClimateControlStatusSchema = object({
55076
55622
  /** Ms epoch when the slice was last updated (push or command). */
55077
55623
  lastFetchedAt: number$1()
55078
55624
  });
55625
+ /**
55626
+ * Linked room sensor (Customize): the OPERATOR may replace the thermostat's own
55627
+ * `currentTemp` / `currentHumidity` with a sensor in the room. Never linked on
55628
+ * its own (D356). Once linked it ALWAYS replaces the native value, a null
55629
+ * native included — the operator chose the better reading.
55630
+ */
55631
+ var CLIMATE_CUSTOMIZE_DEVICE_TYPES = [DeviceType.Climate];
55632
+ function roomSensorTarget(id, path) {
55633
+ return {
55634
+ id,
55635
+ cardinality: "scalar",
55636
+ vocabulary: {
55637
+ cap: "climate-control",
55638
+ stateField: path
55639
+ },
55640
+ deviceTypes: CLIMATE_CUSTOMIZE_DEVICE_TYPES,
55641
+ apply: "replace",
55642
+ path,
55643
+ sources: [{
55644
+ kind: "numeric",
55645
+ transforms: [{ kind: "identity" }]
55646
+ }],
55647
+ sourceReport: { status: "assignment-status" }
55648
+ };
55649
+ }
55650
+ var CLIMATE_CONTROL_TARGETS = [roomSensorTarget("room-temperature", "currentTemp"), roomSensorTarget("room-humidity", "currentHumidity")];
55651
+ var climateControlCustomize = {
55652
+ version: 1,
55653
+ overridable: CLIMATE_CONTROL_TARGETS.map((target) => ({
55654
+ path: target.path,
55655
+ by: target.id
55656
+ })),
55657
+ targets: CLIMATE_CONTROL_TARGETS
55658
+ };
55079
55659
  var climateControlCapability = {
55080
55660
  name: "climate-control",
55081
55661
  scope: "device",
@@ -55386,6 +55966,7 @@ var climateControlCapability = {
55386
55966
  * on every push without re-querying the provider.
55387
55967
  */
55388
55968
  runtimeState: ClimateControlStatusSchema,
55969
+ customize: climateControlCustomize,
55389
55970
  /**
55390
55971
  * Runtime-state durability: **session** — as `brightness`; `currentTemp` moves continuously and is re-published on connect.
55391
55972
  *
@@ -56141,13 +56722,14 @@ var connectivityCapability = {
56141
56722
  * its device tracks consumables registers it; the provider names each item
56142
56723
  * verbatim and says which `kind` it is.
56143
56724
  *
56144
- * The admin UI renders it as ONE collapsible section directly under the
56145
- * device page's hero card (`placement: 'hero'`), for every device bound to
56146
- * this cap — never drawn by a device type's hero card itself (D915).
56725
+ * The admin UI shows it in the device card's consumables chip and popover
56726
+ * (kind-aware level, remaining time, "Reset"), for every device bound to this
56727
+ * cap (D1023). It no longer has a section of its own under the hero card
56728
+ * (D915 on that point).
56147
56729
  *
56148
56730
  * Like `childLayout`, the cap is INERT until a provider sets items: no
56149
56731
  * provider populates it by guessing (no HA inference). The UI renders a
56150
- * "No consumables reported" placeholder when `items` is empty.
56732
+ * "No consumables reported" line when `items` is empty.
56151
56733
  */
56152
56734
  /**
56153
56735
  * What an item IS, which decides which direction is bad:
@@ -56234,7 +56816,7 @@ var ConsumablesStatusSchema = object({
56234
56816
  });
56235
56817
  /** Synthesize seed for a link-built status (no native provider).
56236
56818
  *
56237
- * ⚠ NO LIVE CALLER since 2026-08-08 (device-links deleted; see
56819
+ * ! NO LIVE CALLER since 2026-08-08 (device-links deleted; see
56238
56820
  * `CapabilityStatusSchema.empty`). The wiring guidance below was written for
56239
56821
  * exactly the case the operator is asking about again — a Dreame's HA
56240
56822
  * consumable-life sensors — so it is kept for that conversation, not because
@@ -56338,14 +56920,6 @@ var consumablesCapability = {
56338
56920
  deviceNative: true,
56339
56921
  mode: "singleton",
56340
56922
  deviceTypes: Object.values(DeviceType),
56341
- deviceConfig: { ui: {
56342
- kind: "widget",
56343
- widgetId: "host/consumables-panel",
56344
- tab: "consumables",
56345
- placement: "hero",
56346
- label: "Consumables",
56347
- order: 5
56348
- } },
56349
56923
  methods: {
56350
56924
  /** Mark a consumable as replaced — resets its remaining life. Only
56351
56925
  * meaningful when the item's `resettable` is true. */
@@ -56574,20 +57148,95 @@ var ControlSetValueInputSchema = discriminatedUnion("kind", [
56574
57148
  value: string().min(1)
56575
57149
  })
56576
57150
  ]);
57151
+ /**
57152
+ * `next` / `previous` input (#544, operator scripts driving a selector).
57153
+ * `cycle` is a TOP-LEVEL default so a direct `ctx.api.control.next` call
57154
+ * (which skips the tRPC router's Zod parse) still gets `true` via
57155
+ * `CAP_INPUT_DEFAULTS` — matching HA's own `select_next` / `select_previous`
57156
+ * default.
57157
+ */
57158
+ var ControlStepInputSchema = object({
57159
+ deviceId: number$1().int().nonnegative(),
57160
+ cycle: boolean().default(true)
57161
+ });
57162
+ /** `first` / `last` input — no `cycle`, there is no boundary to stop at. */
57163
+ var ControlBoundaryInputSchema = object({ deviceId: number$1().int().nonnegative() });
56577
57164
  var controlCapability = {
56578
57165
  name: "control",
56579
57166
  scope: "device",
56580
57167
  deviceNative: true,
56581
57168
  mode: "singleton",
56582
57169
  deviceTypes: [DeviceType.Control],
56583
- methods: { setValue: method(object({
56584
- deviceId: number$1().int().nonnegative(),
56585
- control: ControlSetValueInputSchema
56586
- }), _void(), {
56587
- kind: "mutation",
56588
- auth: "protected",
56589
- notOperatorFacing: "the value shape depends on the control kind (numeric / select / text / datetime) read from status; a typed picker for it is a follow-up of the automations rework"
56590
- }) },
57170
+ methods: {
57171
+ setValue: method(object({
57172
+ deviceId: number$1().int().nonnegative(),
57173
+ control: ControlSetValueInputSchema
57174
+ }), _void(), {
57175
+ kind: "mutation",
57176
+ auth: "protected",
57177
+ notOperatorFacing: "the value shape depends on the control kind (numeric / select / text / datetime) read from status; a typed picker for it is a follow-up of the automations rework"
57178
+ }),
57179
+ /**
57180
+ * Select one step forward through `options` (#544). `providerOptional`
57181
+ * (hence `optionalMethod`): only a `kind: 'select'` provider implements
57182
+ * it — the stepping math is NOT re-derived per provider, it is the one
57183
+ * shared helper `selectNavigationMethods` (`control-select-navigation.ts`)
57184
+ * every select-kind driver spreads in.
57185
+ */
57186
+ next: optionalMethod(ControlStepInputSchema, _void(), {
57187
+ kind: "mutation",
57188
+ auth: "protected",
57189
+ actions: [{
57190
+ id: "select.next",
57191
+ label: t("Next option", "Opzione successiva"),
57192
+ phrase: t("Select the next option on {device}", "Seleziona l’opzione successiva su {device}"),
57193
+ icon: "skip-forward",
57194
+ openArgs: [{
57195
+ key: "cycle",
57196
+ label: t("Wrap to the first option", "Ricomincia dalla prima opzione"),
57197
+ offer: { kind: "boolean" }
57198
+ }]
57199
+ }]
57200
+ }),
57201
+ /** Select one step backward through `options` (#544) — mirrors `next`. */
57202
+ previous: optionalMethod(ControlStepInputSchema, _void(), {
57203
+ kind: "mutation",
57204
+ auth: "protected",
57205
+ actions: [{
57206
+ id: "select.previous",
57207
+ label: t("Previous option", "Opzione precedente"),
57208
+ phrase: t("Select the previous option on {device}", "Seleziona l’opzione precedente su {device}"),
57209
+ icon: "skip-back",
57210
+ openArgs: [{
57211
+ key: "cycle",
57212
+ label: t("Wrap to the last option", "Ricomincia dall’ultima opzione"),
57213
+ offer: { kind: "boolean" }
57214
+ }]
57215
+ }]
57216
+ }),
57217
+ /** Select the first entry of `options` (#544). */
57218
+ first: optionalMethod(ControlBoundaryInputSchema, _void(), {
57219
+ kind: "mutation",
57220
+ auth: "protected",
57221
+ actions: [{
57222
+ id: "select.first",
57223
+ label: t("First option", "Prima opzione"),
57224
+ phrase: t("Select the first option on {device}", "Seleziona la prima opzione su {device}"),
57225
+ icon: "chevron-first"
57226
+ }]
57227
+ }),
57228
+ /** Select the last entry of `options` (#544). */
57229
+ last: optionalMethod(ControlBoundaryInputSchema, _void(), {
57230
+ kind: "mutation",
57231
+ auth: "protected",
57232
+ actions: [{
57233
+ id: "select.last",
57234
+ label: t("Last option", "Ultima opzione"),
57235
+ phrase: t("Select the last option on {device}", "Seleziona l’ultima opzione su {device}"),
57236
+ icon: "chevron-last"
57237
+ }]
57238
+ })
57239
+ },
56591
57240
  status: {
56592
57241
  schema: ControlStatusSchema,
56593
57242
  kind: "push"
@@ -57564,7 +58213,7 @@ var mediaPlayerCapability = {
57564
58213
  ]
57565
58214
  }
57566
58215
  };
57567
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Climate, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, 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.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Camera, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, 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.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.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.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Gateway, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.PetFeeder, DeviceType.MediaPlayer;
58216
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Climate, DeviceType.EventEmitter, DeviceType.Climate, DeviceType.Climate, DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, 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.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Camera, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, 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.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.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.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Gateway, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation;
57568
58217
  var NUMBER$1 = {
57569
58218
  kind: "fixed",
57570
58219
  result: ["number"]
@@ -63133,6 +63782,10 @@ method(object({
63133
63782
  * system did not report is `null`, never `0` (D393).
63134
63783
  *
63135
63784
  * Signs, all in watts:
63785
+ * - The branches are DISJOINT: `house` is the consumption EXCLUDING the heat pump
63786
+ * whenever `heatPump` is reported, so solar + grid import + storage discharge =
63787
+ * house + heatPump + grid export + storage charge. A provider whose meter totals
63788
+ * include the pump subtracts it; when it cannot, `house` is null (D393).
63136
63789
  * - `solar.powerW`, `house.powerW`, `heatPump.powerW` are magnitudes (≥ 0);
63137
63790
  * - `grid.powerW` is + importing, − exporting;
63138
63791
  * - `storage.powerW` is + charging, − discharging.
@@ -63372,6 +64025,85 @@ var enumSensorCapability = {
63372
64025
  }]
63373
64026
  }
63374
64027
  };
64028
+ /** How loudly a fault wants the operator. Both tiers raise the attention event. */
64029
+ var DeviceErrorSeveritySchema = _enum(["warning", "error"]);
64030
+ /** One fault of a device that can raise several at once. */
64031
+ var DeviceFaultSchema = object({
64032
+ /** The device's own code, as the vendor names it. */
64033
+ code: string(),
64034
+ /** Already translated by the provider (it owns the vendor vocabulary). */
64035
+ label: string(),
64036
+ severity: DeviceErrorSeveritySchema
64037
+ });
64038
+ /**
64039
+ * The fault a device reports right now. One slice per device.
64040
+ *
64041
+ * `active` is tri-state: `null` is "the provider has not read it yet" and is
64042
+ * never drawn as "no fault" nor as a fault (D315, D393). The provider filters
64043
+ * what is not a fault (a completion or reminder code) BEFORE it writes `true`.
64044
+ *
64045
+ * The cap's name is the key a template reads (`{{ alias.error.label }}`, D1021).
64046
+ */
64047
+ var ErrorStatusSchema = object({
64048
+ active: boolean().nullable(),
64049
+ /** The code of the principal fault; `null` when none (or unknown). */
64050
+ code: string().nullable(),
64051
+ /** The principal fault's words, provider-translated; `null` when none. */
64052
+ label: string().nullable(),
64053
+ severity: DeviceErrorSeveritySchema,
64054
+ /** Ms epoch the current fault started; `null` when none or not known. */
64055
+ since: number$1().nullable(),
64056
+ /** Every fault, for a device that raises several. The principal is the first. */
64057
+ faults: array(DeviceFaultSchema).optional(),
64058
+ /** Ms epoch of the last read the provider made of the fault. */
64059
+ lastFetchedAt: number$1()
64060
+ });
64061
+ /** The slice before the provider has read anything: unknown, not "fine". */
64062
+ var ERROR_STATUS_COLD = {
64063
+ active: null,
64064
+ code: null,
64065
+ label: null,
64066
+ severity: "error",
64067
+ since: null,
64068
+ lastFetchedAt: 0
64069
+ };
64070
+ var errorCapability = {
64071
+ name: "error",
64072
+ scope: "device",
64073
+ deviceNative: true,
64074
+ mode: "singleton",
64075
+ methods: {},
64076
+ status: {
64077
+ schema: ErrorStatusSchema,
64078
+ kind: "push",
64079
+ empty: ERROR_STATUS_COLD
64080
+ },
64081
+ runtimeState: ErrorStatusSchema,
64082
+ /**
64083
+ * Runtime-state durability: **restored** — the previous observation is what
64084
+ * lets the first reading after a restart be a COMPARISON: a fault that was
64085
+ * already open must not announce itself a second time.
64086
+ *
64087
+ * See `RuntimeStateDurability`. Enforced by
64088
+ * `scripts/check-runtime-state-durability.ts`.
64089
+ */
64090
+ durability: "restored",
64091
+ volatileStateFields: ["lastFetchedAt"],
64092
+ /** The fault itself is carried by the attention alerts; no series is recorded. */
64093
+ history: "none",
64094
+ vocabulary: {
64095
+ noun: t("Fault", "Guasto"),
64096
+ order: 5265,
64097
+ state: [{
64098
+ kind: "boolean",
64099
+ field: "active",
64100
+ label: t("Fault", "Guasto"),
64101
+ whenTrue: t("When {device} reports a fault", "Quando {device} segnala un guasto"),
64102
+ whenFalse: t("When the fault of {device} clears", "Quando il guasto di {device} si risolve"),
64103
+ icon: "triangle-alert"
64104
+ }]
64105
+ }
64106
+ };
63375
64107
  var IdentitySchema = object({
63376
64108
  id: string(),
63377
64109
  name: string(),
@@ -63386,7 +64118,19 @@ var IdentitySampleInfoSchema = object({
63386
64118
  source: _enum(["detected", "photo"]),
63387
64119
  addedAt: number$1().int(),
63388
64120
  deviceId: number$1().int().optional(),
63389
- base64: string().optional()
64121
+ base64: string().optional(),
64122
+ /**
64123
+ * The sample's own crop, when it has one. The bytes stay on the event-media
64124
+ * plane — this list does not inline them.
64125
+ */
64126
+ mediaId: number$1().int().optional(),
64127
+ /**
64128
+ * The source face's key frame (or the photo the sample was cut from), when
64129
+ * the sample has no crop of its own. Draw {@link faceBbox} on it.
64130
+ */
64131
+ keyFrameMediaId: number$1().int().optional(),
64132
+ /** The face box on {@link keyFrameMediaId}, pixel space. */
64133
+ faceBbox: BoundingBoxSchema.optional()
63390
64134
  });
63391
64135
  /**
63392
64136
  * One enrolled sample, judged against the gallery it lives in.
@@ -63601,6 +64345,12 @@ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly
63601
64345
  }), _void(), {
63602
64346
  kind: "mutation",
63603
64347
  auth: "admin"
64348
+ }), method(object({
64349
+ sampleId: string().min(1),
64350
+ toIdentityId: string().min(1)
64351
+ }), _void(), {
64352
+ kind: "mutation",
64353
+ auth: "admin"
63604
64354
  }), method(object({
63605
64355
  /**
63606
64356
  * Restrict to ONE camera. Absent keeps the cluster-wide gallery view.
@@ -65838,7 +66588,7 @@ var HeroSceneSchema = object({
65838
66588
  /** 0..1: volume, cover position, cycle progress. */
65839
66589
  level: number$1().min(0).max(1).nullable()
65840
66590
  });
65841
- DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, 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.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
66591
+ DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, 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.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
65842
66592
  DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.SwitchHeater, DeviceRole.SwitchFan, DeviceRole.SwitchLight, DeviceRole.ClimateAc, DeviceRole.ClimateThermostat, DeviceRole.ClimateFloor, DeviceRole.ClimateRadiator, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
65843
66593
  var HeroRowIdSchema = _enum([
65844
66594
  "progress",
@@ -77863,6 +78613,8 @@ var NcSystemEventKindSchema = _enum([
77863
78613
  "storage-location-low",
77864
78614
  "storage-location-critical",
77865
78615
  "storage-location-ok",
78616
+ "storage-drain-progress",
78617
+ "storage-drain-complete",
77866
78618
  "addon-crash-loop",
77867
78619
  "addon-update-available",
77868
78620
  "server-update-available",
@@ -82305,7 +83057,19 @@ var VehicleSampleInfoSchema = object({
82305
83057
  score: number$1(),
82306
83058
  addedAt: number$1().int(),
82307
83059
  deviceId: number$1().int().optional(),
82308
- base64: string().optional()
83060
+ base64: string().optional(),
83061
+ /**
83062
+ * The sample's own crop, when it has one. Prefer this over {@link base64}:
83063
+ * the bytes are on the event-media plane (`/m/<mediaId>`), never inlined.
83064
+ */
83065
+ mediaId: number$1().int().optional(),
83066
+ /**
83067
+ * The source plate's key frame, when the sample has no crop of its own.
83068
+ * Draw {@link plateBbox} on it. Absent when {@link mediaId} is set.
83069
+ */
83070
+ keyFrameMediaId: number$1().int().optional(),
83071
+ /** The plate box on {@link keyFrameMediaId}, pixel space. */
83072
+ plateBbox: BoundingBoxSchema.optional()
82309
83073
  });
82310
83074
  var PlateInfoSchema = object({
82311
83075
  plateId: string(),
@@ -82335,7 +83099,17 @@ var PlateInfoSchema = object({
82335
83099
  * and `searchPlates` inline the crop only when their `includeCrops` input is
82336
83100
  * left at its `true` default.
82337
83101
  */
82338
- cropUrl: string().optional()
83102
+ cropUrl: string().optional(),
83103
+ /**
83104
+ * Why {@link cropUrl} is absent. One of `no-frame-handle`,
83105
+ * `live-capture-missed`, `live-capture-threw`, `no-key-frame`,
83106
+ * `derive-not-wired`, `subject-moved`, `temporal-skew`, `key-frame-absent`,
83107
+ * `bad-frame-size`, `decode-empty`, `derive-threw`, `derive-returned-null`,
83108
+ * `put-failed`. A crossing vehicle whose live capture missed is
83109
+ * `subject-moved`: the key frame is a different instant, and the crop is
83110
+ * legitimately absent.
83111
+ */
83112
+ cropMissReason: string().optional()
82339
83113
  });
82340
83114
  /**
82341
83115
  * One gallery media row: what the crop is, how big it is, and WHERE its bytes
@@ -82435,6 +83209,12 @@ includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly(
82435
83209
  }), _void(), {
82436
83210
  kind: "mutation",
82437
83211
  auth: "admin"
83212
+ }), method(object({
83213
+ sampleId: string().min(1),
83214
+ toVehicleId: string().min(1)
83215
+ }), _void(), {
83216
+ kind: "mutation",
83217
+ auth: "admin"
82438
83218
  }), method(object({
82439
83219
  plateId: string(),
82440
83220
  vehicleId: string()
@@ -86263,7 +87043,10 @@ object({
86263
87043
  value: boolean(),
86264
87044
  at: number$1()
86265
87045
  }).nullable(),
86266
- /** The declared source last sent; unknown after a power off or a channel step. */
87046
+ /**
87047
+ * Retired: a sent source is never a state (an infrared set cannot report its
87048
+ * input). Kept only so records written before parse; nothing writes or reads it.
87049
+ */
86267
87050
  source: object({
86268
87051
  name: string(),
86269
87052
  at: number$1()
@@ -89892,31 +90675,57 @@ var TemplateSelectOptionSchema = object({
89892
90675
  value: string().min(1),
89893
90676
  label: string().min(1).max(80)
89894
90677
  });
90678
+ var SelectInputSchema = object({
90679
+ kind: literal("select"),
90680
+ ...inputBase,
90681
+ options: array(TemplateSelectOptionSchema).min(1).max(50),
90682
+ default: string().optional()
90683
+ });
90684
+ var BooleanInputSchema = object({
90685
+ kind: literal("boolean"),
90686
+ ...inputBase,
90687
+ default: boolean().optional()
90688
+ });
90689
+ /** A limit on when the automation may run; only a `gate` binding reads it. */
90690
+ var TimeWindowInputSchema = object({
90691
+ kind: literal("time-window"),
90692
+ ...inputBase
90693
+ });
89895
90694
  var TemplateScalarInputSchema = discriminatedUnion("kind", [
90695
+ object({
90696
+ kind: literal("entries"),
90697
+ ...inputBase,
90698
+ entry: _enum(["trigger", "step"]),
90699
+ allow: array(string().min(1)).min(1).max(10),
90700
+ maxItems: number$1().int().min(1).max(20).optional()
90701
+ }),
89896
90702
  DeviceInputSchema,
89897
90703
  EventInputSchema,
89898
90704
  ActionInputSchema$1,
89899
90705
  NumberInputSchema,
89900
90706
  DurationInputSchema,
89901
90707
  TextInputSchema,
89902
- object({
89903
- kind: literal("select"),
89904
- ...inputBase,
89905
- options: array(TemplateSelectOptionSchema).min(1).max(50),
89906
- default: string().optional()
89907
- }),
89908
- object({
89909
- kind: literal("boolean"),
89910
- ...inputBase,
89911
- default: boolean().optional()
89912
- })
90708
+ SelectInputSchema,
90709
+ BooleanInputSchema,
90710
+ TimeWindowInputSchema
89913
90711
  ]);
89914
- var GroupInputSchema = object({
90712
+ var groupBase = {
89915
90713
  kind: literal("group"),
89916
90714
  ...inputBase,
89917
- inputs: array(TemplateScalarInputSchema).min(1).max(10),
90715
+ /** What one row is, in the singular and lower case ("button"): "Add button". */
90716
+ rowLabel: string().min(1).max(40).optional(),
89918
90717
  minRows: number$1().int().min(0).max(20).optional(),
89919
90718
  maxRows: number$1().int().min(1).max(20).optional()
90719
+ };
90720
+ /** A group inside a group row: its rows hold values only, never another group. */
90721
+ var NestedGroupInputSchema = object({
90722
+ ...groupBase,
90723
+ inputs: array(TemplateScalarInputSchema).min(1).max(10)
90724
+ });
90725
+ var TemplateRowInputSchema = discriminatedUnion("kind", [...TemplateScalarInputSchema.options, NestedGroupInputSchema]);
90726
+ var GroupInputSchema = object({
90727
+ ...groupBase,
90728
+ inputs: array(TemplateRowInputSchema).min(1).max(10)
89920
90729
  });
89921
90730
  var TemplateInputSchema = discriminatedUnion("kind", [...TemplateScalarInputSchema.options, GroupInputSchema]);
89922
90731
  var TemplatePathSchema = array(union([string().min(1), number$1().int().nonnegative()])).max(24);
@@ -89927,41 +90736,154 @@ var TemplateValueBindingSchema = object({
89927
90736
  /** A device value written as a step target (`{ kind: 'devices', ids }`). */
89928
90737
  as: literal("target").optional()
89929
90738
  });
89930
- var TemplateRowItemBindingSchema = discriminatedUnion("kind", [TemplateValueBindingSchema, object({
90739
+ var TemplateRowIdBindingSchema = object({
89931
90740
  kind: literal("row-id"),
89932
90741
  path: TemplatePathSchema
89933
- })]);
89934
- var TemplateBindingSchema = discriminatedUnion("kind", [TemplateValueBindingSchema, object({
89935
- kind: literal("rows"),
90742
+ });
90743
+ /**
90744
+ * Appends an `entries` input's elements, verbatim, to the list at `path`. In a
90745
+ * row's bindings `input` is the row's own entries input and `path` is from the
90746
+ * row's item; at the top level, with `in`, it appends the `input` entries of
90747
+ * EVERY row of the group `in` (all buttons' triggers into one `when`), and
90748
+ * without `in` it appends a top-level entries input.
90749
+ */
90750
+ var TemplateEntriesBindingSchema = object({
90751
+ kind: literal("entries"),
89936
90752
  input: inputKey,
89937
- /** The list in the body the rows are appended to. */
89938
90753
  path: TemplatePathSchema,
89939
- /** The JSON copied once per row. */
89940
- item: unknown(),
89941
- bindings: array(TemplateRowItemBindingSchema).max(40)
89942
- })]);
89943
- function bindingProblems(payload, ctx) {
89944
- const byKey = /* @__PURE__ */ new Map();
89945
- payload.inputs.forEach((input, index) => {
89946
- if (byKey.has(input.key)) ctx.addIssue({
89947
- code: "custom",
90754
+ in: inputKey.optional()
90755
+ });
90756
+ /** What fills the item of a row that holds values only. */
90757
+ var TemplateLeafRowBindingSchema = discriminatedUnion("kind", [TemplateValueBindingSchema, TemplateRowIdBindingSchema]);
90758
+ var TemplateRowItemBindingSchema = discriminatedUnion("kind", [
90759
+ TemplateValueBindingSchema,
90760
+ TemplateEntriesBindingSchema,
90761
+ TemplateRowIdBindingSchema,
90762
+ object({
90763
+ kind: literal("row-ids"),
90764
+ /** The nested group whose rows are named. */
90765
+ input: inputKey,
90766
+ path: TemplatePathSchema
90767
+ }),
90768
+ object({
90769
+ kind: literal("rows"),
90770
+ /** The nested group, declared by the parent row's group. */
90771
+ input: inputKey,
90772
+ /** The list the nested rows are appended to, from the parent row's item. */
90773
+ path: TemplatePathSchema,
90774
+ item: unknown(),
90775
+ bindings: array(TemplateLeafRowBindingSchema).max(40)
90776
+ })
90777
+ ]);
90778
+ var TemplateBindingSchema = discriminatedUnion("kind", [
90779
+ TemplateValueBindingSchema,
90780
+ TemplateEntriesBindingSchema,
90781
+ object({
90782
+ kind: literal("rows"),
90783
+ input: inputKey,
90784
+ /**
90785
+ * A group the rows of `input` hold: the binding then appends one item per
90786
+ * NESTED row of every row, and its bindings read the nested row.
90787
+ */
90788
+ nested: inputKey.optional(),
90789
+ /** The list in the body the rows are appended to. */
90790
+ path: TemplatePathSchema,
90791
+ /** The JSON copied once per row. */
90792
+ item: unknown(),
90793
+ bindings: array(TemplateRowItemBindingSchema).max(40)
90794
+ }),
90795
+ object({
90796
+ kind: literal("gate"),
90797
+ /** A top-level `time-window` input. */
90798
+ input: inputKey,
90799
+ path: TemplatePathSchema
90800
+ })
90801
+ ]);
90802
+ /** Twice-declared keys and event / action inputs that read no device beside them. */
90803
+ function levelProblems(inputs, path, inRow = false) {
90804
+ const issues = [];
90805
+ const seen = /* @__PURE__ */ new Set();
90806
+ for (const input of inputs) {
90807
+ if (seen.has(input.key)) issues.push({
89948
90808
  message: `input "${input.key}" is declared twice`,
89949
- path: ["inputs", index]
90809
+ path
89950
90810
  });
89951
- byKey.set(input.key, input);
89952
- const siblings = input.kind === "group" ? input.inputs : payload.inputs;
89953
- for (const scalar of input.kind === "group" ? input.inputs : [input]) {
89954
- if (scalar.kind !== "event" && scalar.kind !== "action") continue;
89955
- if (scalar.deviceInput === void 0) continue;
89956
- const device = siblings.find((s) => s.key === scalar.deviceInput);
89957
- if (device === void 0 || device.kind !== "device") ctx.addIssue({
89958
- code: "custom",
89959
- message: `"${scalar.key}" reads the device input "${scalar.deviceInput}", which is not a device input beside it`,
89960
- path: ["inputs", index]
89961
- });
90811
+ seen.add(input.key);
90812
+ if (input.kind === "group") {
90813
+ issues.push(...levelProblems(input.inputs, path, true));
90814
+ continue;
90815
+ }
90816
+ if (input.kind === "time-window" && inRow) issues.push({
90817
+ message: `time window "${input.key}" limits the whole automation: it is not a row input`,
90818
+ path
90819
+ });
90820
+ if (input.kind !== "event" && input.kind !== "action") continue;
90821
+ if (input.deviceInput === void 0) continue;
90822
+ const device = inputs.find((sibling) => sibling.key === input.deviceInput);
90823
+ if (device === void 0 || device.kind !== "device") issues.push({
90824
+ message: `"${input.key}" reads the device input "${input.deviceInput}", which is not a device input beside it`,
90825
+ path
90826
+ });
90827
+ }
90828
+ return issues;
90829
+ }
90830
+ function nestedGroupOf(group, key) {
90831
+ const found = group.inputs.find((input) => input.key === key);
90832
+ return found !== void 0 && found.kind === "group" ? found : void 0;
90833
+ }
90834
+ function leafBindingProblems(bindings, owner) {
90835
+ const issues = [];
90836
+ for (const row of bindings) {
90837
+ if (row.kind === "row-id") continue;
90838
+ if (row.kind === "value") {
90839
+ const input = owner.inputs.find((i) => i.key === row.input);
90840
+ if (input === void 0) issues.push(`row binding names "${row.input}", which group "${owner.key}" does not declare`);
90841
+ else if (input.kind === "group") issues.push(`group "${input.key}" is bound with rows, not a value`);
90842
+ else if (input.kind === "entries") issues.push(`entries "${input.key}" are bound with entries, not a value`);
90843
+ continue;
90844
+ }
90845
+ const nested = owner.inputs.find((i) => i.key === row.input);
90846
+ if (row.kind === "entries") {
90847
+ if (nested === void 0 || nested.kind !== "entries") issues.push(`"${row.input}" is not an entries input inside "${owner.key}"`);
90848
+ continue;
90849
+ }
90850
+ if (row.kind === "row-ids") {
90851
+ if (nested === void 0 || nested.kind !== "group" && nested.kind !== "entries") issues.push(`"${row.input}" is not a group or entries input inside "${owner.key}"`);
90852
+ continue;
89962
90853
  }
90854
+ if (nested === void 0 || nested.kind !== "group") {
90855
+ issues.push(`"${row.input}" is not a group inside "${owner.key}"`);
90856
+ continue;
90857
+ }
90858
+ issues.push(...leafBindingProblems(row.bindings, nested));
90859
+ }
90860
+ return issues;
90861
+ }
90862
+ function rowsBindingProblems(binding, group) {
90863
+ if (binding.nested === void 0) return leafBindingProblems(binding.bindings, group);
90864
+ const nested = nestedGroupOf(group, binding.nested);
90865
+ if (nested === void 0) return [`"${binding.nested}" is not a group inside "${group.key}"`];
90866
+ return leafBindingProblems(binding.bindings, nested);
90867
+ }
90868
+ function bindingProblems(payload, ctx) {
90869
+ const byKey = /* @__PURE__ */ new Map();
90870
+ for (const input of payload.inputs) byKey.set(input.key, input);
90871
+ for (const issue of levelProblems(payload.inputs, ["inputs"])) ctx.addIssue({
90872
+ code: "custom",
90873
+ message: issue.message,
90874
+ path: [...issue.path]
89963
90875
  });
89964
90876
  payload.bindings.forEach((binding, index) => {
90877
+ if (binding.kind === "entries" && binding.in !== void 0) {
90878
+ const group = byKey.get(binding.in);
90879
+ const entries = group !== void 0 && group.kind === "group" ? group.inputs.find((i) => i.key === binding.input) : void 0;
90880
+ if (entries === void 0 || entries.kind !== "entries") ctx.addIssue({
90881
+ code: "custom",
90882
+ message: `"${binding.input}" is not an entries input inside the group "${binding.in}"`,
90883
+ path: ["bindings", index]
90884
+ });
90885
+ return;
90886
+ }
89965
90887
  const input = byKey.get(binding.input);
89966
90888
  if (input === void 0) {
89967
90889
  ctx.addIssue({
@@ -89976,6 +90898,18 @@ function bindingProblems(payload, ctx) {
89976
90898
  message: `group "${input.key}" is bound with rows, not a value`,
89977
90899
  path: ["bindings", index]
89978
90900
  });
90901
+ if (binding.kind === "entries" !== (input.kind === "entries") && binding.kind !== "rows") {
90902
+ if (binding.kind === "entries" || binding.kind === "value") ctx.addIssue({
90903
+ code: "custom",
90904
+ message: binding.kind === "entries" ? `entries bindings read an entries input: "${input.key}" is ${input.kind}` : `entries "${input.key}" are bound with entries, not a value`,
90905
+ path: ["bindings", index]
90906
+ });
90907
+ }
90908
+ if (binding.kind === "gate" !== (input.kind === "time-window")) ctx.addIssue({
90909
+ code: "custom",
90910
+ message: `a gate reads a time window and a time window is bound with a gate: "${input.key}" is ${input.kind}`,
90911
+ path: ["bindings", index]
90912
+ });
89979
90913
  if (binding.kind === "rows") {
89980
90914
  if (input.kind !== "group") {
89981
90915
  ctx.addIssue({
@@ -89985,9 +90919,9 @@ function bindingProblems(payload, ctx) {
89985
90919
  });
89986
90920
  return;
89987
90921
  }
89988
- for (const row of binding.bindings) if (row.kind === "value" && !input.inputs.some((i) => i.key === row.input)) ctx.addIssue({
90922
+ for (const message of rowsBindingProblems(binding, input)) ctx.addIssue({
89989
90923
  code: "custom",
89990
- message: `row binding names "${row.input}", which group "${input.key}" does not declare`,
90924
+ message,
89991
90925
  path: ["bindings", index]
89992
90926
  });
89993
90927
  }
@@ -90166,6 +91100,17 @@ var TerminalOutputEventSchema = discriminatedUnion("kind", [object({
90166
91100
  exitCode: number$1().int(),
90167
91101
  signal: number$1().int().optional()
90168
91102
  })]);
91103
+ var ServerShellRefusalSchema = object({
91104
+ code: literal("server-shell-disabled"),
91105
+ nodeId: string(),
91106
+ message: string()
91107
+ });
91108
+ var ServerShellStatusSchema = object({
91109
+ nodeId: string(),
91110
+ enabled: boolean(),
91111
+ idleMinutes: number$1().int().positive(),
91112
+ refusal: ServerShellRefusalSchema.nullable()
91113
+ });
90169
91114
  var TerminalOutputBatchSchema = object({
90170
91115
  cursor: number$1().int().nonnegative(),
90171
91116
  reset: boolean(),
@@ -90178,7 +91123,7 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
90178
91123
  }), TerminalInstanceInfoSchema, {
90179
91124
  kind: "mutation",
90180
91125
  auth: "admin"
90181
- }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
91126
+ }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(_void(), ServerShellStatusSchema, { auth: "admin" }), method(object({
90182
91127
  profileId: string(),
90183
91128
  cols: number$1().int().positive(),
90184
91129
  rows: number$1().int().positive(),
@@ -90188,7 +91133,8 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
90188
91133
  environment: array(string().max(4096)).max(64).optional()
90189
91134
  }), TerminalSessionInfoSchema, {
90190
91135
  kind: "mutation",
90191
- auth: "admin"
91136
+ auth: "admin",
91137
+ caller: "required"
90192
91138
  }), method(object({
90193
91139
  sessionId: string(),
90194
91140
  cols: number$1().int().positive(),
@@ -90209,6 +91155,7 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
90209
91155
  }), TerminalOutputBatchSchema, {
90210
91156
  kind: "mutation",
90211
91157
  auth: "admin",
91158
+ caller: "required",
90212
91159
  timeoutMs: 15e3
90213
91160
  }), method(object({
90214
91161
  sessionId: string(),
@@ -90218,7 +91165,8 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
90218
91165
  auth: "admin"
90219
91166
  }), method(object({ sessionId: string() }), _void(), {
90220
91167
  kind: "mutation",
90221
- auth: "admin"
91168
+ auth: "admin",
91169
+ caller: "required"
90222
91170
  }), NOT_A_DEVICE.CLUSTER;
90223
91171
  /**
90224
91172
  * toast — system-scoped singleton capability that streams toast
@@ -92172,6 +93120,265 @@ var weatherCapability = {
92172
93120
  ]
92173
93121
  }
92174
93122
  };
93123
+ var WeatherStationStatusSchema = object({
93124
+ temperature: number$1().nullable().default(null).meta(readingMeta("temperature", { precision: 1 })),
93125
+ humidity: number$1().min(0).max(100).nullable().default(null).meta(readingMeta("humidity")),
93126
+ pressure: number$1().nullable().default(null).meta(readingMeta("pressure")),
93127
+ windSpeed: number$1().nullable().default(null).meta(readingMeta("speed")),
93128
+ windGust: number$1().nullable().default(null).meta(readingMeta("speed")),
93129
+ windBearing: number$1().nullable().default(null).meta(readingMeta("angle")),
93130
+ rainRate: number$1().nullable().default(null).meta(readingMeta("precipitation-rate", { precision: 1 })),
93131
+ rainDaily: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 })),
93132
+ rainEvent: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 })),
93133
+ uv: number$1().nullable().default(null).meta(readingMeta("index", { precision: 1 })),
93134
+ solarRadiation: number$1().nullable().default(null).meta(readingMeta("irradiance")),
93135
+ dewPoint: number$1().nullable().default(null).meta(readingMeta("temperature", { precision: 1 })),
93136
+ /** Apparent ("feels like") temperature. */
93137
+ feelsLike: number$1().nullable().default(null).meta(readingMeta("temperature", { precision: 1 })),
93138
+ /** Vapour-pressure deficit, stored in kPa (the unit stations and growers read it in). */
93139
+ vpd: number$1().nullable().default(null).meta(readingMeta("none", { precision: 2 })),
93140
+ /** Highest wind gust since local midnight. */
93141
+ windGustMax: number$1().nullable().default(null).meta(readingMeta("speed")),
93142
+ /** Wind bearing averaged over the station's own window. */
93143
+ windBearingAvg: number$1().nullable().default(null).meta(readingMeta("angle")),
93144
+ rain24h: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 })),
93145
+ rainWeekly: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 })),
93146
+ rainMonthly: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 })),
93147
+ rainYearly: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 }))
93148
+ }).extend({
93149
+ /** Ms epoch of the poll that produced this slice. 0 before the first one. */
93150
+ lastFetchedAt: number$1() });
93151
+ var numeric$2 = (field, quantity, label, unit, icon) => ({
93152
+ kind: "numeric",
93153
+ field,
93154
+ quantity,
93155
+ label,
93156
+ unit,
93157
+ icon
93158
+ });
93159
+ /** Every reading of the station with the unit a Customize source must carry ('' = dimensionless). */
93160
+ var STATION_FIELD_UNITS = {
93161
+ temperature: "°C",
93162
+ humidity: "%",
93163
+ pressure: "hPa",
93164
+ windSpeed: "m/s",
93165
+ windGust: "m/s",
93166
+ windBearing: "°",
93167
+ rainRate: "mm/h",
93168
+ rainDaily: "mm",
93169
+ rainEvent: "mm",
93170
+ uv: "",
93171
+ solarRadiation: "W/m²",
93172
+ dewPoint: "°C",
93173
+ feelsLike: "°C",
93174
+ vpd: "kPa",
93175
+ windGustMax: "m/s",
93176
+ windBearingAvg: "°",
93177
+ rain24h: "mm",
93178
+ rainWeekly: "mm",
93179
+ rainMonthly: "mm",
93180
+ rainYearly: "mm"
93181
+ };
93182
+ function isStationField(key) {
93183
+ return Object.prototype.hasOwnProperty.call(STATION_FIELD_UNITS, key);
93184
+ }
93185
+ var STATION_FIELDS = Object.keys(STATION_FIELD_UNITS).filter(isStationField);
93186
+ /**
93187
+ * One scalar target per reading, `apply: 'add'` and listed in `overridable` by its own id
93188
+ * (the running-state / climate-control mechanism, D984 / D990): a station that reports a
93189
+ * reading natively keeps it until the operator assigns another device to that ONE field,
93190
+ * and a device with no `weather-station` slice becomes one from the sources picked. On an
93191
+ * add, every other reading is absent from the claim and the schema defaults it to null
93192
+ * (a missing sensor is never 0, D393) — the fan-control precedent, never a cross-field companion
93193
+ * (the scalar merge treats a companion as owned by its target).
93194
+ */
93195
+ function stationTarget(field) {
93196
+ const unit = STATION_FIELD_UNITS[field];
93197
+ return {
93198
+ id: field,
93199
+ cardinality: "scalar",
93200
+ vocabulary: {
93201
+ cap: "weather-station",
93202
+ stateField: field
93203
+ },
93204
+ deviceTypes: [DeviceType.WeatherStation],
93205
+ apply: "add",
93206
+ path: field,
93207
+ sources: [{
93208
+ kind: "numeric",
93209
+ ...unit.length > 0 ? { requiredUnit: unit } : {},
93210
+ transforms: [{ kind: "identity" }]
93211
+ }],
93212
+ companions: [{
93213
+ kind: "clock",
93214
+ path: "lastFetchedAt",
93215
+ when: "add"
93216
+ }],
93217
+ sourceReport: { status: "assignment-status" }
93218
+ };
93219
+ }
93220
+ var weatherStationCustomize = {
93221
+ version: 1,
93222
+ overridable: STATION_FIELDS.map((field) => ({
93223
+ path: field,
93224
+ by: field
93225
+ })),
93226
+ targets: STATION_FIELDS.map(stationTarget)
93227
+ };
93228
+ var weatherStationCapability = {
93229
+ name: "weather-station",
93230
+ scope: "device",
93231
+ deviceNative: true,
93232
+ mode: "singleton",
93233
+ deviceTypes: [DeviceType.WeatherStation],
93234
+ methods: {},
93235
+ status: {
93236
+ schema: WeatherStationStatusSchema,
93237
+ kind: "push"
93238
+ },
93239
+ runtimeState: WeatherStationStatusSchema,
93240
+ /**
93241
+ * History (D850): the station records its own readings. Rain totals that
93242
+ * reset (event, day, week, month, year) and the sliding 24 h total are
93243
+ * levels, so `gauge`; `counter` is for a monotonic meter only.
93244
+ */
93245
+ history: { fields: {
93246
+ temperature: {
93247
+ kind: "gauge",
93248
+ unit: "°C",
93249
+ deadband: .1
93250
+ },
93251
+ humidity: {
93252
+ kind: "gauge",
93253
+ unit: "%",
93254
+ deadband: 1
93255
+ },
93256
+ pressure: {
93257
+ kind: "gauge",
93258
+ unit: "hPa",
93259
+ deadband: .2
93260
+ },
93261
+ windSpeed: {
93262
+ kind: "gauge",
93263
+ unit: "m/s",
93264
+ deadband: .2
93265
+ },
93266
+ windGust: {
93267
+ kind: "gauge",
93268
+ unit: "m/s",
93269
+ deadband: .2
93270
+ },
93271
+ windBearing: {
93272
+ kind: "gauge",
93273
+ unit: "°",
93274
+ deadband: 5
93275
+ },
93276
+ rainRate: {
93277
+ kind: "gauge",
93278
+ unit: "mm/h",
93279
+ deadband: .1
93280
+ },
93281
+ rainDaily: {
93282
+ kind: "gauge",
93283
+ unit: "mm",
93284
+ deadband: .1
93285
+ },
93286
+ rainEvent: {
93287
+ kind: "gauge",
93288
+ unit: "mm",
93289
+ deadband: .1
93290
+ },
93291
+ uv: {
93292
+ kind: "gauge",
93293
+ unit: "index",
93294
+ deadband: .1
93295
+ },
93296
+ solarRadiation: {
93297
+ kind: "gauge",
93298
+ unit: "W/m²",
93299
+ deadband: 5
93300
+ },
93301
+ dewPoint: {
93302
+ kind: "gauge",
93303
+ unit: "°C",
93304
+ deadband: .1
93305
+ },
93306
+ feelsLike: {
93307
+ kind: "gauge",
93308
+ unit: "°C",
93309
+ deadband: .1
93310
+ },
93311
+ vpd: {
93312
+ kind: "gauge",
93313
+ unit: "kPa",
93314
+ deadband: .05
93315
+ },
93316
+ windGustMax: {
93317
+ kind: "gauge",
93318
+ unit: "m/s",
93319
+ deadband: .2
93320
+ },
93321
+ windBearingAvg: {
93322
+ kind: "gauge",
93323
+ unit: "°",
93324
+ deadband: 5
93325
+ },
93326
+ rain24h: {
93327
+ kind: "gauge",
93328
+ unit: "mm",
93329
+ deadband: .1
93330
+ },
93331
+ rainWeekly: {
93332
+ kind: "gauge",
93333
+ unit: "mm",
93334
+ deadband: .1
93335
+ },
93336
+ rainMonthly: {
93337
+ kind: "gauge",
93338
+ unit: "mm",
93339
+ deadband: .1
93340
+ },
93341
+ rainYearly: {
93342
+ kind: "gauge",
93343
+ unit: "mm",
93344
+ deadband: .1
93345
+ }
93346
+ } },
93347
+ customize: weatherStationCustomize,
93348
+ /** The poll clock is written but is no reason to commit a row. */
93349
+ volatileStateFields: ["lastFetchedAt"],
93350
+ /**
93351
+ * Runtime-state durability: **session** — a station reading is stale the
93352
+ * moment the hub is down; the provider re-reads on the next poll.
93353
+ */
93354
+ durability: "session",
93355
+ vocabulary: {
93356
+ noun: t("Weather station", "Stazione meteo"),
93357
+ order: 5235,
93358
+ state: [
93359
+ numeric$2("temperature", t("the outdoor temperature", "la temperatura esterna"), t("Temperature", "Temperatura"), "°C", "thermometer"),
93360
+ numeric$2("humidity", t("the outdoor humidity", "l’umidità esterna"), t("Humidity", "Umidità"), "%", "droplets"),
93361
+ numeric$2("pressure", t("the air pressure", "la pressione atmosferica"), t("Pressure", "Pressione"), "hPa", "gauge"),
93362
+ numeric$2("windSpeed", t("the wind speed", "la velocità del vento"), t("Wind", "Vento"), "m/s", "wind"),
93363
+ numeric$2("windGust", t("the wind gust", "la raffica di vento"), t("Gust", "Raffica"), "m/s", "wind"),
93364
+ numeric$2("windBearing", t("the wind bearing", "la direzione del vento"), t("Wind bearing", "Direzione del vento"), "°", "wind"),
93365
+ numeric$2("rainRate", t("the rain rate", "l’intensità della pioggia"), t("Rain rate", "Intensità pioggia"), "mm/h", "droplets"),
93366
+ numeric$2("rainDaily", t("the rain today", "la pioggia di oggi"), t("Rain today", "Pioggia oggi"), "mm", "droplets"),
93367
+ numeric$2("rainEvent", t("the rain this event", "la pioggia di questo evento"), t("Rain event", "Evento di pioggia"), "mm", "droplets"),
93368
+ numeric$2("uv", t("the UV index", "l’indice UV"), t("UV", "UV"), "", "cloud-sun"),
93369
+ numeric$2("solarRadiation", t("the solar radiation", "la radiazione solare"), t("Solar", "Sole"), "W/m²", "cloud-sun"),
93370
+ numeric$2("dewPoint", t("the dew point", "il punto di rugiada"), t("Dew point", "Punto di rugiada"), "°C", "thermometer"),
93371
+ numeric$2("feelsLike", t("the feels-like temperature", "la temperatura percepita"), t("Feels like", "Percepita"), "°C", "thermometer"),
93372
+ numeric$2("vpd", t("the vapour-pressure deficit", "il deficit di pressione di vapore"), t("VPD", "VPD"), "kPa", "gauge"),
93373
+ numeric$2("windGustMax", t("the highest gust today", "la raffica massima di oggi"), t("Max gust", "Raffica max"), "m/s", "wind"),
93374
+ numeric$2("windBearingAvg", t("the average wind bearing", "la direzione media del vento"), t("Avg bearing", "Direzione media"), "°", "wind"),
93375
+ numeric$2("rain24h", t("the rain in the last 24 hours", "la pioggia delle ultime 24 ore"), t("Rain 24 h", "Pioggia 24 h"), "mm", "droplets"),
93376
+ numeric$2("rainWeekly", t("the rain this week", "la pioggia di questa settimana"), t("Rain week", "Pioggia settimana"), "mm", "droplets"),
93377
+ numeric$2("rainMonthly", t("the rain this month", "la pioggia di questo mese"), t("Rain month", "Pioggia mese"), "mm", "droplets"),
93378
+ numeric$2("rainYearly", t("the rain this year", "la pioggia di quest’anno"), t("Rain year", "Pioggia anno"), "mm", "droplets")
93379
+ ]
93380
+ }
93381
+ };
92175
93382
  /**
92176
93383
  * Optional client-side hints sent at session creation to help the provider
92177
93384
  * pick the best native source. All fields optional — a viewer that knows
@@ -93737,6 +94944,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
93737
94944
  doorbell: doorbellCapability,
93738
94945
  energySystem: energySystemCapability,
93739
94946
  enumSensor: enumSensorCapability,
94947
+ error: errorCapability,
93740
94948
  eventEmitter: eventEmitterCapability,
93741
94949
  fanControl: fanControlCapability,
93742
94950
  featureProbe: featureProbeCapability,
@@ -93793,10 +95001,59 @@ var DEVICE_LOCAL_STATE_CAPS = {
93793
95001
  vibration: vibrationCapability,
93794
95002
  waterHeater: waterHeaterCapability,
93795
95003
  weather: weatherCapability,
95004
+ weatherStation: weatherStationCapability,
93796
95005
  zoneAnalytics: zoneAnalyticsCapability,
93797
95006
  zoneRules: zoneRulesCapability,
93798
95007
  zones: zonesCapability
93799
95008
  };
95009
+ /**
95010
+ * The edge between two readings, or `null` for no edge.
95011
+ *
95012
+ * - `null`/cold → `true` is a START (a fault already open at boot is one);
95013
+ * - `true` → `false` is a RESOLVE;
95014
+ * - an UNKNOWN reading (`active: null`) is never an edge, in either direction —
95015
+ * a failed read must neither raise nor close an episode (D393, D49);
95016
+ * - `true` → `true` is the same episode, whatever the code did.
95017
+ */
95018
+ function deviceErrorEdge(previous, next) {
95019
+ if (next.active === true && previous?.active !== true) return "start";
95020
+ if (next.active === false && previous?.active === true) return "resolve";
95021
+ return null;
95022
+ }
95023
+ function deviceErrorAlertId(deviceId) {
95024
+ return `device-error:${String(deviceId)}`;
95025
+ }
95026
+ /** The alert that opens an episode. `since` is the fault's own start, else `now`. */
95027
+ function buildDeviceErrorAlert(deviceId, deviceName, status, now) {
95028
+ const label = status.label ?? status.code ?? "Fault";
95029
+ const startedAt = status.since ?? now;
95030
+ const alertId = deviceErrorAlertId(deviceId);
95031
+ return {
95032
+ id: alertId,
95033
+ category: "device.error",
95034
+ severity: "critical",
95035
+ title: `${deviceName}: ${label}`,
95036
+ message: `${deviceName} reports a fault: ${label}.`,
95037
+ action: {
95038
+ label: "Open device",
95039
+ href: `/devices/${String(deviceId)}`,
95040
+ navigation: "app"
95041
+ },
95042
+ status: "active",
95043
+ read: false,
95044
+ createdAt: now,
95045
+ updatedAt: now,
95046
+ source: {
95047
+ type: "device",
95048
+ id: String(deviceId)
95049
+ },
95050
+ metadata: {
95051
+ episodeId: `${alertId}@${String(startedAt)}`,
95052
+ code: status.code,
95053
+ deviceSeverity: status.severity
95054
+ }
95055
+ };
95056
+ }
93800
95057
  var BaseDevice = class {
93801
95058
  id;
93802
95059
  stableId;
@@ -94096,6 +95353,68 @@ var BaseDevice = class {
94096
95353
  } catch (err) {}
94097
95354
  }
94098
95355
  /**
95356
+ * Declare that this device can report a fault (the `error` cap). The slice
95357
+ * starts UNKNOWN — a restored slice is kept, so a fault that was open before
95358
+ * a restart is compared, not announced again.
95359
+ */
95360
+ registerErrorCap() {
95361
+ this.ctx.registerNativeCap?.(errorCapability, {});
95362
+ if (this.runtimeState.getCapState(errorCapability.name) === void 0) this.setCapSlice(errorCapability, ERROR_STATUS_COLD);
95363
+ }
95364
+ /**
95365
+ * Publish the device's fault. The slice is written; on an EDGE (a fault
95366
+ * starts, or one resolves) an attention episode opens or closes through the
95367
+ * alerts cap, which the notification center announces as
95368
+ * `device-attention-required` / `-resolved`. An unknown reading is never an
95369
+ * edge. No rule exists by default: it notifies when the operator makes one.
95370
+ */
95371
+ async publishError(reading) {
95372
+ const previous = this.getCapSlice(errorCapability);
95373
+ if (reading.active === null && previous !== null && previous.active !== null) return;
95374
+ const now = Date.now();
95375
+ const edge = deviceErrorEdge(previous, reading);
95376
+ const next = edge === "start" && reading.since === null ? {
95377
+ ...reading,
95378
+ since: now
95379
+ } : reading;
95380
+ this.setCapSlice(errorCapability, next);
95381
+ if (edge === null) return;
95382
+ const alerts = this.ctx.api.alerts;
95383
+ if (alerts === void 0) {
95384
+ this.ctx.logger.warn("device error: attention event skipped — alerts capability missing", {
95385
+ tags: { deviceId: this.id },
95386
+ meta: { edge }
95387
+ });
95388
+ return;
95389
+ }
95390
+ try {
95391
+ if (edge === "start") await alerts.emit.mutate(buildDeviceErrorAlert(this.id, this.name, next, now));
95392
+ else await alerts.update.mutate({
95393
+ alertId: deviceErrorAlertId(this.id),
95394
+ patch: {
95395
+ status: "completed",
95396
+ read: true,
95397
+ updatedAt: now
95398
+ }
95399
+ });
95400
+ this.ctx.logger.info(`device error: ${edge === "start" ? "raised" : "resolved"}`, {
95401
+ tags: { deviceId: this.id },
95402
+ meta: {
95403
+ code: next.code,
95404
+ label: next.label
95405
+ }
95406
+ });
95407
+ } catch (err) {
95408
+ this.ctx.logger.warn("device error: attention event failed", {
95409
+ tags: { deviceId: this.id },
95410
+ meta: {
95411
+ edge,
95412
+ error: errMsg(err)
95413
+ }
95414
+ });
95415
+ }
95416
+ }
95417
+ /**
94099
95418
  * Typed read-through to a cap-keyed runtime-state slice. Drivers
94100
95419
  * call `this.getCapSlice(batteryCapability)` and the return type
94101
95420
  * is inferred from the cap's `runtimeState` Zod schema — no string
@@ -95043,7 +96362,7 @@ var BaseDeviceProvider = class extends BaseAddon {
95043
96362
  }
95044
96363
  };
95045
96364
  DeviceType.HeatPump;
95046
- DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Camera, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, 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.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
96365
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Camera, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, 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.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
95047
96366
  new Set(Object.values(DeviceType));
95048
96367
  /** Parses the raw slice with the cap's own schema; `undefined` when it does not parse. */
95049
96368
  function contribution(cap, schema, build) {
@@ -96412,6 +97731,17 @@ var SENSORS_FAMILY_CONTRIBUTIONS = {
96412
97731
  doorbell: doorbellContribution,
96413
97732
  meter: meterContribution,
96414
97733
  weather: weatherContribution,
97734
+ "weather-station": contribution("weather-station", WeatherStationStatusSchema, (slice) => {
97735
+ if (slice === void 0 || slice.temperature === null) return { reading: reading("weather", "unknown") };
97736
+ const celsius = `${number(slice.temperature)}°C`;
97737
+ const facts = [fact("reading", "thermometer", t(celsius, celsius), "primary")];
97738
+ if (slice.humidity !== null) facts.push(fact("reading", "droplets", t(`${Math.round(slice.humidity)}%`, `${Math.round(slice.humidity)}%`)));
97739
+ if (slice.windSpeed !== null) facts.push(fact("reading", "wind", t(`${number(slice.windSpeed)} m/s`, `${number(slice.windSpeed)} m/s`)));
97740
+ return {
97741
+ reading: reading("weather", "on", false, t(celsius, celsius)),
97742
+ facts
97743
+ };
97744
+ }),
96415
97745
  update: updateContribution,
96416
97746
  image: imageContribution,
96417
97747
  button: buttonContribution,
@@ -97088,7 +98418,7 @@ var HEAT_PUMP_FAMILY_CONTRIBUTIONS = { "heat-pump-status": contribution("heat-pu
97088
98418
  ...Object.values(CLIMATE_FAMILY_CONTRIBUTIONS),
97089
98419
  ...Object.values(HEAT_PUMP_FAMILY_CONTRIBUTIONS)
97090
98420
  ];
97091
- DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, 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.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
98421
+ DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, 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.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
97092
98422
  /**
97093
98423
  * Who looks like what (D1023). One look per {@link DeviceType}, refined by a
97094
98424
  * {@link DeviceRole} where the role changes how the device reads (a leak
@@ -97732,7 +99062,7 @@ lookOf({
97732
99062
  standby: MUTED
97733
99063
  });
97734
99064
  DeviceType.Climate;
97735
- DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, 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.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
99065
+ DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, 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.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
97736
99066
  DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.SwitchHeater, DeviceRole.SwitchFan, DeviceRole.SwitchLight, DeviceRole.ClimateAc, DeviceRole.ClimateThermostat, DeviceRole.ClimateFloor, DeviceRole.ClimateRadiator, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
97737
99067
  Object.freeze({
97738
99068
  "accessories.getStatus": {
@@ -99037,6 +100367,13 @@ Object.freeze({
99037
100367
  access: "view",
99038
100368
  role: "viewer"
99039
100369
  },
100370
+ "control.first": {
100371
+ capName: "control",
100372
+ capScope: "device",
100373
+ addonId: null,
100374
+ access: "create",
100375
+ role: "viewer"
100376
+ },
99040
100377
  "control.getStatus": {
99041
100378
  capName: "control",
99042
100379
  capScope: "device",
@@ -99044,6 +100381,27 @@ Object.freeze({
99044
100381
  access: "view",
99045
100382
  role: "viewer"
99046
100383
  },
100384
+ "control.last": {
100385
+ capName: "control",
100386
+ capScope: "device",
100387
+ addonId: null,
100388
+ access: "create",
100389
+ role: "viewer"
100390
+ },
100391
+ "control.next": {
100392
+ capName: "control",
100393
+ capScope: "device",
100394
+ addonId: null,
100395
+ access: "create",
100396
+ role: "viewer"
100397
+ },
100398
+ "control.previous": {
100399
+ capName: "control",
100400
+ capScope: "device",
100401
+ addonId: null,
100402
+ access: "create",
100403
+ role: "viewer"
100404
+ },
99047
100405
  "control.setValue": {
99048
100406
  capName: "control",
99049
100407
  capScope: "device",
@@ -100997,6 +102355,13 @@ Object.freeze({
100997
102355
  access: "view",
100998
102356
  role: "viewer"
100999
102357
  },
102358
+ "error.getStatus": {
102359
+ capName: "error",
102360
+ capScope: "device",
102361
+ addonId: null,
102362
+ access: "view",
102363
+ role: "viewer"
102364
+ },
101000
102365
  "eventEmitter.getStatus": {
101001
102366
  capName: "event-emitter",
101002
102367
  capScope: "device",
@@ -101081,6 +102446,13 @@ Object.freeze({
101081
102446
  access: "view",
101082
102447
  role: "feature:face-assignment"
101083
102448
  },
102449
+ "faceGallery.moveIdentitySample": {
102450
+ capName: "face-gallery",
102451
+ capScope: "system",
102452
+ addonId: null,
102453
+ access: "create",
102454
+ role: "feature:face-assignment"
102455
+ },
101084
102456
  "faceGallery.removeSample": {
101085
102457
  capName: "face-gallery",
101086
102458
  capScope: "system",
@@ -105001,6 +106373,13 @@ Object.freeze({
105001
106373
  access: "view",
105002
106374
  role: "feature:plate-assignment"
105003
106375
  },
106376
+ "plateGallery.moveVehicleSample": {
106377
+ capName: "plate-gallery",
106378
+ capScope: "system",
106379
+ addonId: null,
106380
+ access: "create",
106381
+ role: "feature:plate-assignment"
106382
+ },
105004
106383
  "plateGallery.removeVehicleSample": {
105005
106384
  capName: "plate-gallery",
105006
106385
  capScope: "system",
@@ -107255,6 +108634,13 @@ Object.freeze({
107255
108634
  access: "create",
107256
108635
  role: "admin"
107257
108636
  },
108637
+ "terminalSession.serverShellStatus": {
108638
+ capName: "terminal-session",
108639
+ capScope: "system",
108640
+ addonId: null,
108641
+ access: "view",
108642
+ role: "admin"
108643
+ },
107258
108644
  "terminalSession.writeInput": {
107259
108645
  capName: "terminal-session",
107260
108646
  capScope: "system",
@@ -107990,6 +109376,13 @@ Object.freeze({
107990
109376
  access: "view",
107991
109377
  role: "viewer"
107992
109378
  },
109379
+ "weatherStation.getStatus": {
109380
+ capName: "weather-station",
109381
+ capScope: "device",
109382
+ addonId: null,
109383
+ access: "view",
109384
+ role: "viewer"
109385
+ },
107993
109386
  "webrtcSession.addIceCandidate": {
107994
109387
  capName: "webrtc-session",
107995
109388
  capScope: "device",
@@ -108203,7 +109596,7 @@ function offered(received) {
108203
109596
  };
108204
109597
  }
108205
109598
  offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
108206
- DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.HeatPump, DeviceType.EnergySystem, 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.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
109599
+ DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.HeatPump, DeviceType.EnergySystem, 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.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
108207
109600
  DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.SwitchHeater, DeviceRole.SwitchFan, DeviceRole.SwitchLight, DeviceRole.ClimateAc, DeviceRole.ClimateThermostat, DeviceRole.ClimateFloor, DeviceRole.ClimateRadiator, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
108208
109601
  new Set(Object.values(DeviceType));
108209
109602
  new Set(Object.values(DeviceRole));
@@ -108542,11 +109935,31 @@ Object.freeze({
108542
109935
  form: "single",
108543
109936
  optional: false
108544
109937
  }],
109938
+ "control.first": [{
109939
+ name: "deviceId",
109940
+ form: "single",
109941
+ optional: false
109942
+ }],
108545
109943
  "control.getStatus": [{
108546
109944
  name: "deviceId",
108547
109945
  form: "single",
108548
109946
  optional: false
108549
109947
  }],
109948
+ "control.last": [{
109949
+ name: "deviceId",
109950
+ form: "single",
109951
+ optional: false
109952
+ }],
109953
+ "control.next": [{
109954
+ name: "deviceId",
109955
+ form: "single",
109956
+ optional: false
109957
+ }],
109958
+ "control.previous": [{
109959
+ name: "deviceId",
109960
+ form: "single",
109961
+ optional: false
109962
+ }],
108550
109963
  "control.setValue": [{
108551
109964
  name: "deviceId",
108552
109965
  form: "single",
@@ -109171,6 +110584,11 @@ Object.freeze({
109171
110584
  form: "single",
109172
110585
  optional: false
109173
110586
  }],
110587
+ "error.getStatus": [{
110588
+ name: "deviceId",
110589
+ form: "single",
110590
+ optional: false
110591
+ }],
109174
110592
  "eventEmitter.getStatus": [{
109175
110593
  name: "deviceId",
109176
110594
  form: "single",
@@ -111212,6 +112630,11 @@ Object.freeze({
111212
112630
  form: "single",
111213
112631
  optional: false
111214
112632
  }],
112633
+ "weatherStation.getStatus": [{
112634
+ name: "deviceId",
112635
+ form: "single",
112636
+ optional: false
112637
+ }],
111215
112638
  "webrtcSession.addIceCandidate": [{
111216
112639
  name: "deviceId",
111217
112640
  form: "single",
@@ -111448,6 +112871,8 @@ new Map(Object.entries({
111448
112871
  "storage-location-low": "system",
111449
112872
  "storage-location-critical": "system",
111450
112873
  "storage-location-ok": "system",
112874
+ "storage-drain-progress": "system",
112875
+ "storage-drain-complete": "system",
111451
112876
  "alarm-triggered": "alarm",
111452
112877
  "alarm-armed": "alarm",
111453
112878
  "alarm-disarmed": "alarm",
@@ -111583,6 +113008,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
111583
113008
  "storage-location-ok"
111584
113009
  ]
111585
113010
  },
113011
+ {
113012
+ id: "storage-drain",
113013
+ family: "system",
113014
+ label: "Storage drain progress",
113015
+ kinds: ["storage-drain-progress", "storage-drain-complete"]
113016
+ },
111586
113017
  {
111587
113018
  id: "alerts",
111588
113019
  family: "system",
@@ -111777,6 +113208,7 @@ var ALERT_EPISODE = [
111777
113208
  "alert-error",
111778
113209
  "alert-critical-resolved"
111779
113210
  ];
113211
+ var STORAGE_DRAIN = ["storage-drain-progress", "storage-drain-complete"];
111780
113212
  var DEVICE_ATTENTION = ["device-attention-required", "device-attention-resolved"];
111781
113213
  var UPDATES = [
111782
113214
  "addon-update-available",
@@ -111988,6 +113420,7 @@ var STATE_SUMMARY_VARS = [
111988
113420
  [...RULE_TEXT],
111989
113421
  [...RULE_TEXT],
111990
113422
  [...RULE_TEXT],
113423
+ [...RULE_TEXT],
111991
113424
  [...RULE_BODY_ONLY],
111992
113425
  [...RULE_TEXT],
111993
113426
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
@@ -112000,8 +113433,16 @@ var STATE_SUMMARY_VARS = [
112000
113433
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
112001
113434
  ([...RULE_TEXT], [...CONSUMABLE]),
112002
113435
  [...RULE_TEXT],
112003
- ([...RULE_TEXT], [...UPDATES, ...ALERT_EPISODE]),
112004
- ([...RULE_TEXT], [...UPDATES, ...ALERT_EPISODE]),
113436
+ ([...RULE_TEXT], [
113437
+ ...UPDATES,
113438
+ ...STORAGE_DRAIN,
113439
+ ...ALERT_EPISODE
113440
+ ]),
113441
+ ([...RULE_TEXT], [
113442
+ ...UPDATES,
113443
+ ...STORAGE_DRAIN,
113444
+ ...ALERT_EPISODE
113445
+ ]),
112005
113446
  [...RULE_TEXT],
112006
113447
  [...RULE_TEXT],
112007
113448
  [...RULE_TEXT],
@@ -112015,6 +113456,9 @@ var STATE_SUMMARY_VARS = [
112015
113456
  [...RULE_TEXT],
112016
113457
  [...RULE_TEXT],
112017
113458
  [...RULE_TEXT],
113459
+ ([...RULE_TEXT], [...STORAGE_DRAIN]),
113460
+ ([...RULE_TEXT], [...STORAGE_DRAIN]),
113461
+ [...RULE_TEXT],
112018
113462
  [...RULE_TEXT],
112019
113463
  [...RULE_TEXT],
112020
113464
  [...RULE_TEXT],
@@ -113790,6 +115234,17 @@ function present(part) {
113790
115234
  return Object.values(part).some((v) => v !== null) ? part : null;
113791
115235
  }
113792
115236
  /**
115237
+ * A house reading without the heat pump, from Enpal's total that includes it. Both known:
115238
+ * the difference, floored at 0. No pump in the system: the total as reported. A pump that
115239
+ * exists but whose reading is unknown makes the result unknown, never the inflated total.
115240
+ */
115241
+ function withoutPump(total, pump, hasPump) {
115242
+ if (total === null) return null;
115243
+ if (!hasPump) return total;
115244
+ if (pump === null) return null;
115245
+ return Math.max(0, total - pump);
115246
+ }
115247
+ /**
113793
115248
  * The grid power, + import / − export, from the two halves the catalog splits it into.
113794
115249
  * With both halves known it is their difference. With one half only, it is that half
113795
115250
  * while it flows: a zero import with no export reading cannot say whether the house
@@ -113819,16 +115274,17 @@ function computeEnergySystemStatus(snapshot, options, now) {
113819
115274
  importTodayKwh: read.numeric("grid-import-day"),
113820
115275
  exportTodayKwh: read.numeric("grid-export-day")
113821
115276
  };
113822
- const house = {
113823
- powerW: read.power("house"),
113824
- energyKwh: read.energy("house"),
113825
- todayKwh: read.numeric("house-energy-day")
113826
- };
113827
115277
  const heatPump = {
113828
115278
  powerW: read.power("heatpump"),
113829
115279
  energyKwh: read.energy("heatpump"),
113830
115280
  todayKwh: null
113831
115281
  };
115282
+ const hasPump = present(heatPump) !== null;
115283
+ const house = {
115284
+ powerW: withoutPump(read.power("house"), heatPump.powerW, hasPump),
115285
+ energyKwh: withoutPump(read.energy("house"), heatPump.energyKwh, hasPump),
115286
+ todayKwh: hasPump ? null : read.numeric("house-energy-day")
115287
+ };
113832
115288
  const storage = {
113833
115289
  stateOfChargePct: read.numeric("battery"),
113834
115290
  storedKwh: read.numeric("battery-stored-energy"),