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