@camstack/addon-provider-enpal 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -3,7 +3,7 @@ let node_child_process = require("node:child_process");
3
3
  let node_fs = require("node:fs");
4
4
  let node_path = require("node:path");
5
5
  let node_url = require("node:url");
6
- //#region ../types/dist/event-category-Dv-Gcv1f.mjs
6
+ //#region ../types/dist/event-category-Dhcd0TRH.mjs
7
7
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
8
8
  EventCategory["SystemBoot"] = "system.boot";
9
9
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -277,6 +277,18 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
277
277
  EventCategory["StorageLocationCritical"] = "storage.location.critical";
278
278
  /** Recovery from low/critical on a storage location. */
279
279
  EventCategory["StorageLocationOk"] = "storage.location.ok";
280
+ /**
281
+ * A `drain` location's regular progress ping (#538) — location id + name,
282
+ * bytes remaining, the observed cede rate and the estimated empty date, or
283
+ * `null` fields when the occupancy read that sweep failed (D393: never
284
+ * folded to 0). Cadence is the named constant
285
+ * `DRAIN_PROGRESS_EVENT_INTERVAL_MS` (15 min) plus once on drain start — no
286
+ * operator knob. Telemetry (D8): a lost tick is a stale progress bar the
287
+ * next sweep corrects.
288
+ */
289
+ EventCategory["StorageDrainProgress"] = "storage.drain.progress";
290
+ /** Fires once when a draining location reaches zero evictable bytes. */
291
+ EventCategory["StorageDrainComplete"] = "storage.drain.complete";
280
292
  EventCategory["RecordingSegmentWritten"] = "recording.segment.written";
281
293
  EventCategory["RecordingPolicyFallback"] = "recording.policy.fallback";
282
294
  EventCategory["RecordingRetentionCompleted"] = "recording.retention.completed";
@@ -815,7 +827,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
815
827
  return EventCategory;
816
828
  }({});
817
829
  //#endregion
818
- //#region ../types/dist/composed-stable-id-B3wcvtvW.mjs
830
+ //#region ../types/dist/composed-stable-id-RvQIEpqg.mjs
819
831
  /**
820
832
  import { errMsg } from '@camstack/types'
821
833
  * Extract a human-readable message from an unknown error value.
@@ -828,8 +840,6 @@ function errMsg(err) {
828
840
  }
829
841
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
830
842
  DeviceType["Camera"] = "camera";
831
- /** Legacy stored value, read only by the hub-to-integration migrations; no live device carries it. */
832
- DeviceType["Hub"] = "hub";
833
843
  DeviceType["Light"] = "light";
834
844
  DeviceType["Siren"] = "siren";
835
845
  DeviceType["Switch"] = "switch";
@@ -899,6 +909,9 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
899
909
  /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
900
910
  * `weather` cap. */
901
911
  DeviceType["Weather"] = "weather";
912
+ /** A physical weather station (Ecowitt outdoor array). `weather-station`
913
+ * cap. Distinct from {@link Weather}, which is a forecast provider. */
914
+ DeviceType["WeatherStation"] = "weather-station";
902
915
  /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
903
916
  DeviceType["Vacuum"] = "vacuum";
904
917
  /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
@@ -6066,7 +6079,7 @@ var ZodIssueCode = {
6066
6079
  var ZodFirstPartyTypeKind;
6067
6080
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6068
6081
  //#endregion
6069
- //#region ../types/dist/sleep-D7hCDd7y.mjs
6082
+ //#region ../types/dist/sleep-CLDVTB5R.mjs
6070
6083
  /**
6071
6084
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6072
6085
  * window to float samples (D455).
@@ -6729,7 +6742,8 @@ async function runAddonMigrations(host, migrations, logger, options = {}) {
6729
6742
  try {
6730
6743
  declaration = await host.declareOwner({
6731
6744
  owner,
6732
- metas: migrations.map((m) => m.meta)
6745
+ metas: migrations.map((m) => m.meta),
6746
+ ...options.retired === void 0 ? {} : { retired: [...options.retired] }
6733
6747
  });
6734
6748
  } catch (err) {
6735
6749
  log.error("the migration engine refused or could not take this addon catalogue — none ran", { meta: { error: errMsg(err) } });
@@ -8722,6 +8736,14 @@ var BaseAddon = class {
8722
8736
  declareRegistrySwitch(spec) {
8723
8737
  this.registrySwitchSpec = spec;
8724
8738
  }
8739
+ /**
8740
+ * Ids this addon removed from its catalogue on purpose (`declareRetiredMigrations`): sent with
8741
+ * every declaration so the engine does not read their applied rows as a newer database.
8742
+ */
8743
+ retiredMigrationIds = [];
8744
+ declareRetiredMigrations(ids) {
8745
+ this.retiredMigrationIds = ids;
8746
+ }
8725
8747
  registrySwitchSpec = null;
8726
8748
  registrySwitchBound() {
8727
8749
  const spec = this.registrySwitchSpec;
@@ -8745,10 +8767,14 @@ var BaseAddon = class {
8745
8767
  async runBound(declared, options) {
8746
8768
  const added = this.registrySwitchBound();
8747
8769
  const migrations = [...declared, ...added];
8748
- const phased = options?.only === void 0 || added.length === 0 ? options : {
8770
+ const widened = options?.only === void 0 || added.length === 0 ? options : {
8749
8771
  ...options,
8750
8772
  only: [...options.only, ...added.map((m) => m.meta.id)]
8751
8773
  };
8774
+ const phased = this.retiredMigrationIds.length === 0 ? widened ?? {} : {
8775
+ ...widened,
8776
+ retired: this.retiredMigrationIds
8777
+ };
8752
8778
  const api = this.ctx.api.migrations;
8753
8779
  const report = await runAddonMigrations({
8754
8780
  declareOwner: (input) => api.declareOwner.mutate(input),
@@ -13435,6 +13461,76 @@ var CAP_VOCABULARY_BY_NAME = {
13435
13461
  en: "Control",
13436
13462
  it: "Controllo"
13437
13463
  },
13464
+ actions: [
13465
+ {
13466
+ id: "select.next",
13467
+ method: "next",
13468
+ label: {
13469
+ en: "Next option",
13470
+ it: "Opzione successiva"
13471
+ },
13472
+ phrase: {
13473
+ en: "Select the next option on {device}",
13474
+ it: "Seleziona l’opzione successiva su {device}"
13475
+ },
13476
+ icon: "skip-forward",
13477
+ openArgs: [{
13478
+ key: "cycle",
13479
+ label: {
13480
+ en: "Wrap to the first option",
13481
+ it: "Ricomincia dalla prima opzione"
13482
+ },
13483
+ offer: { kind: "boolean" }
13484
+ }]
13485
+ },
13486
+ {
13487
+ id: "select.previous",
13488
+ method: "previous",
13489
+ label: {
13490
+ en: "Previous option",
13491
+ it: "Opzione precedente"
13492
+ },
13493
+ phrase: {
13494
+ en: "Select the previous option on {device}",
13495
+ it: "Seleziona l’opzione precedente su {device}"
13496
+ },
13497
+ icon: "skip-back",
13498
+ openArgs: [{
13499
+ key: "cycle",
13500
+ label: {
13501
+ en: "Wrap to the last option",
13502
+ it: "Ricomincia dall’ultima opzione"
13503
+ },
13504
+ offer: { kind: "boolean" }
13505
+ }]
13506
+ },
13507
+ {
13508
+ id: "select.first",
13509
+ method: "first",
13510
+ label: {
13511
+ en: "First option",
13512
+ it: "Prima opzione"
13513
+ },
13514
+ phrase: {
13515
+ en: "Select the first option on {device}",
13516
+ it: "Seleziona la prima opzione su {device}"
13517
+ },
13518
+ icon: "chevron-first"
13519
+ },
13520
+ {
13521
+ id: "select.last",
13522
+ method: "last",
13523
+ label: {
13524
+ en: "Last option",
13525
+ it: "Ultima opzione"
13526
+ },
13527
+ phrase: {
13528
+ en: "Select the last option on {device}",
13529
+ it: "Seleziona l’ultima opzione su {device}"
13530
+ },
13531
+ icon: "chevron-last"
13532
+ }
13533
+ ],
13438
13534
  notOperatorFacing: { setValue: "the value shape depends on the control kind (numeric / select / text / datetime) read from status; a typed picker for it is a follow-up of the automations rework" },
13439
13535
  state: [{
13440
13536
  kind: "value",
@@ -18581,6 +18677,295 @@ var CAP_VOCABULARY_BY_NAME = {
18581
18677
  }
18582
18678
  ]
18583
18679
  },
18680
+ "weather-station": {
18681
+ scope: "device",
18682
+ noun: {
18683
+ en: "Weather station",
18684
+ it: "Stazione meteo"
18685
+ },
18686
+ state: [
18687
+ {
18688
+ kind: "numeric",
18689
+ field: "temperature",
18690
+ quantity: {
18691
+ en: "the outdoor temperature",
18692
+ it: "la temperatura esterna"
18693
+ },
18694
+ label: {
18695
+ en: "Temperature",
18696
+ it: "Temperatura"
18697
+ },
18698
+ unit: "°C",
18699
+ icon: "thermometer"
18700
+ },
18701
+ {
18702
+ kind: "numeric",
18703
+ field: "humidity",
18704
+ quantity: {
18705
+ en: "the outdoor humidity",
18706
+ it: "l’umidità esterna"
18707
+ },
18708
+ label: {
18709
+ en: "Humidity",
18710
+ it: "Umidità"
18711
+ },
18712
+ unit: "%",
18713
+ icon: "droplets"
18714
+ },
18715
+ {
18716
+ kind: "numeric",
18717
+ field: "pressure",
18718
+ quantity: {
18719
+ en: "the air pressure",
18720
+ it: "la pressione atmosferica"
18721
+ },
18722
+ label: {
18723
+ en: "Pressure",
18724
+ it: "Pressione"
18725
+ },
18726
+ unit: "hPa",
18727
+ icon: "gauge"
18728
+ },
18729
+ {
18730
+ kind: "numeric",
18731
+ field: "windSpeed",
18732
+ quantity: {
18733
+ en: "the wind speed",
18734
+ it: "la velocità del vento"
18735
+ },
18736
+ label: {
18737
+ en: "Wind",
18738
+ it: "Vento"
18739
+ },
18740
+ unit: "m/s",
18741
+ icon: "wind"
18742
+ },
18743
+ {
18744
+ kind: "numeric",
18745
+ field: "windGust",
18746
+ quantity: {
18747
+ en: "the wind gust",
18748
+ it: "la raffica di vento"
18749
+ },
18750
+ label: {
18751
+ en: "Gust",
18752
+ it: "Raffica"
18753
+ },
18754
+ unit: "m/s",
18755
+ icon: "wind"
18756
+ },
18757
+ {
18758
+ kind: "numeric",
18759
+ field: "windBearing",
18760
+ quantity: {
18761
+ en: "the wind bearing",
18762
+ it: "la direzione del vento"
18763
+ },
18764
+ label: {
18765
+ en: "Wind bearing",
18766
+ it: "Direzione del vento"
18767
+ },
18768
+ unit: "°",
18769
+ icon: "wind"
18770
+ },
18771
+ {
18772
+ kind: "numeric",
18773
+ field: "rainRate",
18774
+ quantity: {
18775
+ en: "the rain rate",
18776
+ it: "l’intensità della pioggia"
18777
+ },
18778
+ label: {
18779
+ en: "Rain rate",
18780
+ it: "Intensità pioggia"
18781
+ },
18782
+ unit: "mm/h",
18783
+ icon: "droplets"
18784
+ },
18785
+ {
18786
+ kind: "numeric",
18787
+ field: "rainDaily",
18788
+ quantity: {
18789
+ en: "the rain today",
18790
+ it: "la pioggia di oggi"
18791
+ },
18792
+ label: {
18793
+ en: "Rain today",
18794
+ it: "Pioggia oggi"
18795
+ },
18796
+ unit: "mm",
18797
+ icon: "droplets"
18798
+ },
18799
+ {
18800
+ kind: "numeric",
18801
+ field: "rainEvent",
18802
+ quantity: {
18803
+ en: "the rain this event",
18804
+ it: "la pioggia di questo evento"
18805
+ },
18806
+ label: {
18807
+ en: "Rain event",
18808
+ it: "Evento di pioggia"
18809
+ },
18810
+ unit: "mm",
18811
+ icon: "droplets"
18812
+ },
18813
+ {
18814
+ kind: "numeric",
18815
+ field: "uv",
18816
+ quantity: {
18817
+ en: "the UV index",
18818
+ it: "l’indice UV"
18819
+ },
18820
+ label: {
18821
+ en: "UV",
18822
+ it: "UV"
18823
+ },
18824
+ unit: "",
18825
+ icon: "cloud-sun"
18826
+ },
18827
+ {
18828
+ kind: "numeric",
18829
+ field: "solarRadiation",
18830
+ quantity: {
18831
+ en: "the solar radiation",
18832
+ it: "la radiazione solare"
18833
+ },
18834
+ label: {
18835
+ en: "Solar",
18836
+ it: "Sole"
18837
+ },
18838
+ unit: "W/m²",
18839
+ icon: "cloud-sun"
18840
+ },
18841
+ {
18842
+ kind: "numeric",
18843
+ field: "dewPoint",
18844
+ quantity: {
18845
+ en: "the dew point",
18846
+ it: "il punto di rugiada"
18847
+ },
18848
+ label: {
18849
+ en: "Dew point",
18850
+ it: "Punto di rugiada"
18851
+ },
18852
+ unit: "°C",
18853
+ icon: "thermometer"
18854
+ },
18855
+ {
18856
+ kind: "numeric",
18857
+ field: "feelsLike",
18858
+ quantity: {
18859
+ en: "the feels-like temperature",
18860
+ it: "la temperatura percepita"
18861
+ },
18862
+ label: {
18863
+ en: "Feels like",
18864
+ it: "Percepita"
18865
+ },
18866
+ unit: "°C",
18867
+ icon: "thermometer"
18868
+ },
18869
+ {
18870
+ kind: "numeric",
18871
+ field: "vpd",
18872
+ quantity: {
18873
+ en: "the vapour-pressure deficit",
18874
+ it: "il deficit di pressione di vapore"
18875
+ },
18876
+ label: {
18877
+ en: "VPD",
18878
+ it: "VPD"
18879
+ },
18880
+ unit: "kPa",
18881
+ icon: "gauge"
18882
+ },
18883
+ {
18884
+ kind: "numeric",
18885
+ field: "windGustMax",
18886
+ quantity: {
18887
+ en: "the highest gust today",
18888
+ it: "la raffica massima di oggi"
18889
+ },
18890
+ label: {
18891
+ en: "Max gust",
18892
+ it: "Raffica max"
18893
+ },
18894
+ unit: "m/s",
18895
+ icon: "wind"
18896
+ },
18897
+ {
18898
+ kind: "numeric",
18899
+ field: "windBearingAvg",
18900
+ quantity: {
18901
+ en: "the average wind bearing",
18902
+ it: "la direzione media del vento"
18903
+ },
18904
+ label: {
18905
+ en: "Avg bearing",
18906
+ it: "Direzione media"
18907
+ },
18908
+ unit: "°",
18909
+ icon: "wind"
18910
+ },
18911
+ {
18912
+ kind: "numeric",
18913
+ field: "rain24h",
18914
+ quantity: {
18915
+ en: "the rain in the last 24 hours",
18916
+ it: "la pioggia delle ultime 24 ore"
18917
+ },
18918
+ label: {
18919
+ en: "Rain 24 h",
18920
+ it: "Pioggia 24 h"
18921
+ },
18922
+ unit: "mm",
18923
+ icon: "droplets"
18924
+ },
18925
+ {
18926
+ kind: "numeric",
18927
+ field: "rainWeekly",
18928
+ quantity: {
18929
+ en: "the rain this week",
18930
+ it: "la pioggia di questa settimana"
18931
+ },
18932
+ label: {
18933
+ en: "Rain week",
18934
+ it: "Pioggia settimana"
18935
+ },
18936
+ unit: "mm",
18937
+ icon: "droplets"
18938
+ },
18939
+ {
18940
+ kind: "numeric",
18941
+ field: "rainMonthly",
18942
+ quantity: {
18943
+ en: "the rain this month",
18944
+ it: "la pioggia di questo mese"
18945
+ },
18946
+ label: {
18947
+ en: "Rain month",
18948
+ it: "Pioggia mese"
18949
+ },
18950
+ unit: "mm",
18951
+ icon: "droplets"
18952
+ },
18953
+ {
18954
+ kind: "numeric",
18955
+ field: "rainYearly",
18956
+ quantity: {
18957
+ en: "the rain this year",
18958
+ it: "la pioggia di quest’anno"
18959
+ },
18960
+ label: {
18961
+ en: "Rain year",
18962
+ it: "Pioggia anno"
18963
+ },
18964
+ unit: "mm",
18965
+ icon: "droplets"
18966
+ }
18967
+ ]
18968
+ },
18584
18969
  "battery": {
18585
18970
  scope: "device",
18586
18971
  noun: {
@@ -18805,6 +19190,30 @@ var CAP_VOCABULARY_BY_NAME = {
18805
19190
  icon: "wifi-off"
18806
19191
  }]
18807
19192
  },
19193
+ "error": {
19194
+ scope: "device",
19195
+ noun: {
19196
+ en: "Fault",
19197
+ it: "Guasto"
19198
+ },
19199
+ state: [{
19200
+ kind: "boolean",
19201
+ field: "active",
19202
+ label: {
19203
+ en: "Fault",
19204
+ it: "Guasto"
19205
+ },
19206
+ whenTrue: {
19207
+ en: "When {device} reports a fault",
19208
+ it: "Quando {device} segnala un guasto"
19209
+ },
19210
+ whenFalse: {
19211
+ en: "When the fault of {device} clears",
19212
+ it: "Quando il guasto di {device} si risolve"
19213
+ },
19214
+ icon: "triangle-alert"
19215
+ }]
19216
+ },
18808
19217
  "doorbell": {
18809
19218
  scope: "device",
18810
19219
  noun: {
@@ -19495,6 +19904,7 @@ var CAP_NAMES = Object.freeze([
19495
19904
  "embedding-encoder",
19496
19905
  "energy-system",
19497
19906
  "enum-sensor",
19907
+ "error",
19498
19908
  "event-emitter",
19499
19909
  "face-gallery",
19500
19910
  "failure-contribution",
@@ -19625,6 +20035,7 @@ var CAP_NAMES = Object.freeze([
19625
20035
  "viewer-ui",
19626
20036
  "water-heater",
19627
20037
  "weather",
20038
+ "weather-station",
19628
20039
  "webrtc-session",
19629
20040
  "zone-analytics",
19630
20041
  "zone-rules",
@@ -19904,6 +20315,18 @@ Object.freeze({
19904
20315
  "dry"
19905
20316
  ]
19906
20317
  },
20318
+ {
20319
+ "path": "action",
20320
+ "valueType": "enum",
20321
+ "enumValues": [
20322
+ "heating",
20323
+ "cooling",
20324
+ "drying",
20325
+ "fan",
20326
+ "idle",
20327
+ "off"
20328
+ ]
20329
+ },
19907
20330
  {
19908
20331
  "path": "fanMode",
19909
20332
  "valueType": "string"
@@ -20372,6 +20795,29 @@ Object.freeze({
20372
20795
  "path": "timestamp",
20373
20796
  "valueType": "boolean"
20374
20797
  }],
20798
+ "error": [
20799
+ {
20800
+ "path": "active",
20801
+ "valueType": "boolean"
20802
+ },
20803
+ {
20804
+ "path": "code",
20805
+ "valueType": "string"
20806
+ },
20807
+ {
20808
+ "path": "label",
20809
+ "valueType": "string"
20810
+ },
20811
+ {
20812
+ "path": "severity",
20813
+ "valueType": "enum",
20814
+ "enumValues": ["warning", "error"]
20815
+ },
20816
+ {
20817
+ "path": "since",
20818
+ "valueType": "number"
20819
+ }
20820
+ ],
20375
20821
  "event-emitter": [
20376
20822
  {
20377
20823
  "path": "lastEvent.deviceId",
@@ -21591,6 +22037,88 @@ Object.freeze({
21591
22037
  "valueType": "number"
21592
22038
  }
21593
22039
  ],
22040
+ "weather-station": [
22041
+ {
22042
+ "path": "temperature",
22043
+ "valueType": "number"
22044
+ },
22045
+ {
22046
+ "path": "humidity",
22047
+ "valueType": "number"
22048
+ },
22049
+ {
22050
+ "path": "pressure",
22051
+ "valueType": "number"
22052
+ },
22053
+ {
22054
+ "path": "windSpeed",
22055
+ "valueType": "number"
22056
+ },
22057
+ {
22058
+ "path": "windGust",
22059
+ "valueType": "number"
22060
+ },
22061
+ {
22062
+ "path": "windBearing",
22063
+ "valueType": "number"
22064
+ },
22065
+ {
22066
+ "path": "rainRate",
22067
+ "valueType": "number"
22068
+ },
22069
+ {
22070
+ "path": "rainDaily",
22071
+ "valueType": "number"
22072
+ },
22073
+ {
22074
+ "path": "rainEvent",
22075
+ "valueType": "number"
22076
+ },
22077
+ {
22078
+ "path": "uv",
22079
+ "valueType": "number"
22080
+ },
22081
+ {
22082
+ "path": "solarRadiation",
22083
+ "valueType": "number"
22084
+ },
22085
+ {
22086
+ "path": "dewPoint",
22087
+ "valueType": "number"
22088
+ },
22089
+ {
22090
+ "path": "feelsLike",
22091
+ "valueType": "number"
22092
+ },
22093
+ {
22094
+ "path": "vpd",
22095
+ "valueType": "number"
22096
+ },
22097
+ {
22098
+ "path": "windGustMax",
22099
+ "valueType": "number"
22100
+ },
22101
+ {
22102
+ "path": "windBearingAvg",
22103
+ "valueType": "number"
22104
+ },
22105
+ {
22106
+ "path": "rain24h",
22107
+ "valueType": "number"
22108
+ },
22109
+ {
22110
+ "path": "rainWeekly",
22111
+ "valueType": "number"
22112
+ },
22113
+ {
22114
+ "path": "rainMonthly",
22115
+ "valueType": "number"
22116
+ },
22117
+ {
22118
+ "path": "rainYearly",
22119
+ "valueType": "number"
22120
+ }
22121
+ ],
21594
22122
  "zone-analytics": [
21595
22123
  {
21596
22124
  "path": "ts",
@@ -21683,7 +22211,7 @@ Object.freeze({
21683
22211
  }
21684
22212
  });
21685
22213
  //#endregion
21686
- //#region ../types/dist/implemented-methods-CgUBu6uM.mjs
22214
+ //#region ../types/dist/implemented-methods-C5FMXLGP.mjs
21687
22215
  /**
21688
22216
  * The ffmpeg build every CamStack node downloads, per platform and arch.
21689
22217
  *
@@ -48012,69 +48540,6 @@ var EventVocabularyEntrySchema = object({
48012
48540
  kind: EventVocabularyKindSchema.optional(),
48013
48541
  payload: array(EventVocabularyFieldSchema).optional()
48014
48542
  });
48015
- /**
48016
- * The link an automation made FROM a template carries (#527): which template,
48017
- * the values the operator filled in, and — when the template has since changed
48018
- * so that those values no longer fit — the problems that say why.
48019
- *
48020
- * It lives inside the recipe (`AutomationRecipe.template`), so the one row in
48021
- * `core_blocks` stays the one artefact: there is no second store of instances
48022
- * to drift from the blocks, and deleting the automation deletes the link.
48023
- * "Convert to a free automation" is dropping this field.
48024
- *
48025
- * This file depends on nothing in `recipe.ts`, so the recipe can embed it and
48026
- * the template engine (`template/automation-template.ts`) can import both.
48027
- */
48028
- /** An `event` input's value: the same three narrowing lists a `device-fired` row takes. */
48029
- var TemplateEventValueSchema = object({
48030
- eventIds: array(string().min(1)).min(1).max(64).optional(),
48031
- sourceIds: array(string().min(1)).min(1).max(64).optional(),
48032
- kinds: array(EventVocabularyKindSchema).min(1).optional()
48033
- }).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" });
48034
- /** An `action` input's value: a vocabulary action and its open args. */
48035
- var TemplateActionValueSchema = object({
48036
- actionId: string().min(1),
48037
- args: record(string(), unknown()).optional()
48038
- });
48039
- /** The value of one non-group input. Which member is right is the input kind's to judge. */
48040
- var TemplateScalarValueSchema = union([
48041
- number$1(),
48042
- string(),
48043
- boolean(),
48044
- array(number$1().int().nonnegative()),
48045
- TemplateEventValueSchema,
48046
- TemplateActionValueSchema
48047
- ]);
48048
- var TemplateInputValueSchema = union([TemplateScalarValueSchema, array(record(string(), TemplateScalarValueSchema))]);
48049
- /** Input key → value. */
48050
- var TemplateInputValuesSchema = record(string(), TemplateInputValueSchema);
48051
- var TemplateInstanceProblemSchema = object({
48052
- code: _enum([
48053
- "missing-input",
48054
- "invalid-input",
48055
- "invalid-recipe",
48056
- "missing-template",
48057
- "edited-by-hand"
48058
- ]),
48059
- /** Dotted input address (`buttons.2.targets`); absent for a whole-recipe problem. */
48060
- input: string().optional(),
48061
- detail: string()
48062
- });
48063
- var AutomationTemplateLinkSchema = object({
48064
- templateId: string().min(1),
48065
- values: TemplateInputValuesSchema,
48066
- /**
48067
- * Fingerprint of the generated code with this literal blanked
48068
- * (`instanceCodeHash`), stamped by `generateAutomationBlock`. Code that no
48069
- * longer matches it was edited by hand, and is never regenerated over.
48070
- */
48071
- codeHash: string().optional(),
48072
- /**
48073
- * Present when the last re-expansion refused these values. The automation
48074
- * keeps running the body of its last good expansion; nothing is dropped.
48075
- */
48076
- problems: array(TemplateInstanceProblemSchema).min(1).optional()
48077
- });
48078
48543
  var ExpressionBindingSourceSchema = union([
48079
48544
  object({
48080
48545
  kind: literal("field").optional(),
@@ -48110,6 +48575,58 @@ object({
48110
48575
  path: ["expr"]
48111
48576
  });
48112
48577
  });
48578
+ Math.PI / 180;
48579
+ /**
48580
+ * WHEN something happens, as data (D983) — the one time vocabulary of the
48581
+ * scheduler and of an automation's schedule leaf. Three shapes, no cron:
48582
+ *
48583
+ * - `clock` — a wall-clock time every day (`07:30`), read in the site's zone;
48584
+ * - `sun` — a sun event (`sunrise`, `sunset`, `dawn`, `dusk`) plus or minus
48585
+ * up to twelve hours, from the site's coordinates;
48586
+ * - `date` — one local date and time (`2026-12-24T18:00`), once.
48587
+ *
48588
+ * A site is a place and its IANA zone ({@link TimeSite}). Every function here
48589
+ * is pure: the zone arithmetic uses `Intl`, no date library.
48590
+ *
48591
+ * Wall-clock honesty across a DST change: a local time that does not exist
48592
+ * (02:30 on the spring-forward day) happens at the first minute that does
48593
+ * (03:00); one that happens twice (02:30 on the fall-back day) happens at its
48594
+ * FIRST occurrence.
48595
+ */
48596
+ var SunEventSchema = _enum([
48597
+ "sunrise",
48598
+ "sunset",
48599
+ "dawn",
48600
+ "dusk"
48601
+ ]);
48602
+ var TimeSpecSchema = discriminatedUnion("kind", [
48603
+ object({
48604
+ kind: literal("clock"),
48605
+ hh: number$1().int().min(0).max(23),
48606
+ mm: number$1().int().min(0).max(59)
48607
+ }),
48608
+ object({
48609
+ kind: literal("sun"),
48610
+ event: SunEventSchema,
48611
+ /** Minutes after the event (negative: before). */
48612
+ offsetMin: number$1().int().min(-720).max(720)
48613
+ }),
48614
+ object({
48615
+ kind: literal("date"),
48616
+ /** `YYYY-MM-DDTHH:mm`, site-local. */
48617
+ local: string().regex(/^(\d{4}-\d{2}-\d{2})T([01]\d|2[0-3]):([0-5]\d)$/, "expected YYYY-MM-DDTHH:mm")
48618
+ })
48619
+ ]);
48620
+ /** A day of the week as a schedule names it. */
48621
+ var WeekdayKeySchema = _enum([
48622
+ "mon",
48623
+ "tue",
48624
+ "wed",
48625
+ "thu",
48626
+ "fri",
48627
+ "sat",
48628
+ "sun"
48629
+ ]);
48113
48630
  /**
48114
48631
  * Identity envelope for a device's upstream-system metadata.
48115
48632
  *
@@ -48502,58 +49019,6 @@ function macroOnly(macro) {
48502
49019
  };
48503
49020
  }
48504
49021
  }
48505
- Math.PI / 180;
48506
- /**
48507
- * WHEN something happens, as data (D983) — the one time vocabulary of the
48508
- * scheduler and of an automation's schedule leaf. Three shapes, no cron:
48509
- *
48510
- * - `clock` — a wall-clock time every day (`07:30`), read in the site's zone;
48511
- * - `sun` — a sun event (`sunrise`, `sunset`, `dawn`, `dusk`) plus or minus
48512
- * up to twelve hours, from the site's coordinates;
48513
- * - `date` — one local date and time (`2026-12-24T18:00`), once.
48514
- *
48515
- * A site is a place and its IANA zone ({@link TimeSite}). Every function here
48516
- * is pure: the zone arithmetic uses `Intl`, no date library.
48517
- *
48518
- * Wall-clock honesty across a DST change: a local time that does not exist
48519
- * (02:30 on the spring-forward day) happens at the first minute that does
48520
- * (03:00); one that happens twice (02:30 on the fall-back day) happens at its
48521
- * FIRST occurrence.
48522
- */
48523
- var SunEventSchema = _enum([
48524
- "sunrise",
48525
- "sunset",
48526
- "dawn",
48527
- "dusk"
48528
- ]);
48529
- var TimeSpecSchema = discriminatedUnion("kind", [
48530
- object({
48531
- kind: literal("clock"),
48532
- hh: number$1().int().min(0).max(23),
48533
- mm: number$1().int().min(0).max(59)
48534
- }),
48535
- object({
48536
- kind: literal("sun"),
48537
- event: SunEventSchema,
48538
- /** Minutes after the event (negative: before). */
48539
- offsetMin: number$1().int().min(-720).max(720)
48540
- }),
48541
- object({
48542
- kind: literal("date"),
48543
- /** `YYYY-MM-DDTHH:mm`, site-local. */
48544
- local: string().regex(/^(\d{4}-\d{2}-\d{2})T([01]\d|2[0-3]):([0-5]\d)$/, "expected YYYY-MM-DDTHH:mm")
48545
- })
48546
- ]);
48547
- /** A day of the week as a schedule names it. */
48548
- var WeekdayKeySchema = _enum([
48549
- "mon",
48550
- "tue",
48551
- "wed",
48552
- "thu",
48553
- "fri",
48554
- "sat",
48555
- "sun"
48556
- ]);
48557
49022
  /**
48558
49023
  * Hold semantics — "for N seconds" on a leaf of the `when` tree (#498).
48559
49024
  *
@@ -48810,24 +49275,33 @@ var StationaryPresentLeafSchema = object({
48810
49275
  * (`kind: 'date'`) is refused: only a clock time or a sun event makes a window
48811
49276
  * that repeats every day.
48812
49277
  */
48813
- var TimeIsLeafSchema = object({
48814
- kind: literal("time-is"),
49278
+ var timeIsWindowShape = {
48815
49279
  between: object({
48816
49280
  from: TimeSpecSchema,
48817
49281
  to: TimeSpecSchema
48818
49282
  }).optional(),
48819
49283
  days: array(WeekdayKeySchema).min(1).max(7).optional()
48820
- }).superRefine((leaf, ctx) => {
48821
- if (leaf.between === void 0 && leaf.days === void 0) ctx.addIssue({
49284
+ };
49285
+ function timeIsWindowProblems(window, ctx) {
49286
+ if (window.between === void 0 && window.days === void 0) ctx.addIssue({
48822
49287
  code: "custom",
48823
49288
  message: "a time-is leaf needs a between window, days, or both"
48824
49289
  });
48825
- for (const end of ["from", "to"]) if (leaf.between?.[end].kind === "date") ctx.addIssue({
49290
+ for (const end of ["from", "to"]) if (window.between?.[end].kind === "date") ctx.addIssue({
48826
49291
  code: "custom",
48827
49292
  message: "a one-date time cannot bound a daily window: use a clock time or a sun event",
48828
49293
  path: ["between", end]
48829
49294
  });
48830
- });
49295
+ }
49296
+ /**
49297
+ * What a `time-is` leaf holds beside its kind, judged by the leaf's own rules.
49298
+ * A template's time-window input stores exactly this.
49299
+ */
49300
+ var TimeIsWindowSchema = object(timeIsWindowShape).superRefine(timeIsWindowProblems);
49301
+ var TimeIsLeafSchema = object({
49302
+ kind: literal("time-is"),
49303
+ ...timeIsWindowShape
49304
+ }).superRefine(timeIsWindowProblems);
48831
49305
  /**
48832
49306
  * True for the run that one of the named event leaves started. It reads the
48833
49307
  * run, not a device: in the tree it is judged against the leaf that fired, in
@@ -49035,6 +49509,79 @@ function validateWhenTree(when) {
49035
49509
  return null;
49036
49510
  }
49037
49511
  /**
49512
+ * The link an automation made FROM a template carries (#527): which template,
49513
+ * the values the operator filled in, and — when the template has since changed
49514
+ * so that those values no longer fit — the problems that say why.
49515
+ *
49516
+ * It lives inside the recipe (`AutomationRecipe.template`), so the one row in
49517
+ * `core_blocks` stays the one artefact: there is no second store of instances
49518
+ * to drift from the blocks, and deleting the automation deletes the link.
49519
+ * "Convert to a free automation" is dropping this field.
49520
+ *
49521
+ * This file depends on nothing in `recipe.ts`, so the recipe can embed it and
49522
+ * the template engine (`template/automation-template.ts`) can import both.
49523
+ */
49524
+ /** An `event` input's value: the same three narrowing lists a `device-fired` row takes. */
49525
+ var TemplateEventValueSchema = object({
49526
+ eventIds: array(string().min(1)).min(1).max(64).optional(),
49527
+ sourceIds: array(string().min(1)).min(1).max(64).optional(),
49528
+ kinds: array(EventVocabularyKindSchema).min(1).optional()
49529
+ }).refine((value) => value.eventIds !== void 0 || value.sourceIds !== void 0 || value.kinds !== void 0, { message: "an event names at least one event, part or kind" });
49530
+ /** An `action` input's value: a vocabulary action and its open args. */
49531
+ var TemplateActionValueSchema = object({
49532
+ actionId: string().min(1),
49533
+ args: record(string(), unknown()).optional()
49534
+ });
49535
+ /**
49536
+ * A `time-window` input's value: what a `time-is` leaf holds beside its kind.
49537
+ * "No limit" is the input left without a value, never an empty object.
49538
+ */
49539
+ var TemplateTimeWindowValueSchema = TimeIsWindowSchema;
49540
+ var TemplateEntriesValueSchema = array(record(string(), unknown()));
49541
+ /** The value of one non-group input. Which member is right is the input kind's to judge. */
49542
+ var TemplateScalarValueSchema = union([
49543
+ number$1(),
49544
+ string(),
49545
+ boolean(),
49546
+ array(number$1().int().nonnegative()),
49547
+ TemplateEventValueSchema,
49548
+ TemplateActionValueSchema,
49549
+ TemplateTimeWindowValueSchema,
49550
+ TemplateEntriesValueSchema
49551
+ ]);
49552
+ /** What a group row holds under one key: a value, or the rows of a group it nests. */
49553
+ var TemplateRowValueSchema = union([TemplateScalarValueSchema, array(record(string(), TemplateScalarValueSchema))]);
49554
+ var TemplateInputValueSchema = union([TemplateScalarValueSchema, array(record(string(), TemplateRowValueSchema))]);
49555
+ /** Input key → value. */
49556
+ var TemplateInputValuesSchema = record(string(), TemplateInputValueSchema);
49557
+ var TemplateInstanceProblemSchema = object({
49558
+ code: _enum([
49559
+ "missing-input",
49560
+ "invalid-input",
49561
+ "invalid-recipe",
49562
+ "missing-template",
49563
+ "edited-by-hand"
49564
+ ]),
49565
+ /** Dotted input address (`buttons.2.targets`, `buttons.0.commands.1.targets`); absent for a whole-recipe problem. */
49566
+ input: string().optional(),
49567
+ detail: string()
49568
+ });
49569
+ var AutomationTemplateLinkSchema = object({
49570
+ templateId: string().min(1),
49571
+ values: TemplateInputValuesSchema,
49572
+ /**
49573
+ * Fingerprint of the generated code with this literal blanked
49574
+ * (`instanceCodeHash`), stamped by `generateAutomationBlock`. Code that no
49575
+ * longer matches it was edited by hand, and is never regenerated over.
49576
+ */
49577
+ codeHash: string().optional(),
49578
+ /**
49579
+ * Present when the last re-expansion refused these values. The automation
49580
+ * keeps running the body of its last good expansion; nothing is dropped.
49581
+ */
49582
+ problems: array(TemplateInstanceProblemSchema).min(1).optional()
49583
+ });
49584
+ /**
49038
49585
  * The recipe shape before the single `when` tree: a flat list of triggers
49039
49586
  * (OR'd, each an instant) and an optional condition tree. Stored recipes —
49040
49587
  * the `AUTOMATION_RECIPE` literal of every block saved so far, and whatever an
@@ -55012,6 +55559,16 @@ var HvacModeSchema = _enum([
55012
55559
  var ClimateControlStatusSchema = object({
55013
55560
  /** Active HVAC mode. */
55014
55561
  mode: HvacModeSchema,
55562
+ /** What the device is doing now (`heating` while the valve is open, `idle`
55563
+ * at setpoint). Absent or null = the device does not say; never a guess (D393). */
55564
+ action: _enum([
55565
+ "heating",
55566
+ "cooling",
55567
+ "drying",
55568
+ "fan",
55569
+ "idle",
55570
+ "off"
55571
+ ]).nullable().optional(),
55015
55572
  /** Available HVAC modes the device accepts. Subset of HvacMode. */
55016
55573
  availableModes: array(HvacModeSchema),
55017
55574
  /** Active fan mode (`auto` / `low` / `medium` / `high` / `on` / `off`
@@ -55065,6 +55622,40 @@ var ClimateControlStatusSchema = object({
55065
55622
  /** Ms epoch when the slice was last updated (push or command). */
55066
55623
  lastFetchedAt: number$1()
55067
55624
  });
55625
+ /**
55626
+ * Linked room sensor (Customize): the OPERATOR may replace the thermostat's own
55627
+ * `currentTemp` / `currentHumidity` with a sensor in the room. Never linked on
55628
+ * its own (D356). Once linked it ALWAYS replaces the native value, a null
55629
+ * native included — the operator chose the better reading.
55630
+ */
55631
+ var CLIMATE_CUSTOMIZE_DEVICE_TYPES = [DeviceType.Climate];
55632
+ function roomSensorTarget(id, path) {
55633
+ return {
55634
+ id,
55635
+ cardinality: "scalar",
55636
+ vocabulary: {
55637
+ cap: "climate-control",
55638
+ stateField: path
55639
+ },
55640
+ deviceTypes: CLIMATE_CUSTOMIZE_DEVICE_TYPES,
55641
+ apply: "replace",
55642
+ path,
55643
+ sources: [{
55644
+ kind: "numeric",
55645
+ transforms: [{ kind: "identity" }]
55646
+ }],
55647
+ sourceReport: { status: "assignment-status" }
55648
+ };
55649
+ }
55650
+ var CLIMATE_CONTROL_TARGETS = [roomSensorTarget("room-temperature", "currentTemp"), roomSensorTarget("room-humidity", "currentHumidity")];
55651
+ var climateControlCustomize = {
55652
+ version: 1,
55653
+ overridable: CLIMATE_CONTROL_TARGETS.map((target) => ({
55654
+ path: target.path,
55655
+ by: target.id
55656
+ })),
55657
+ targets: CLIMATE_CONTROL_TARGETS
55658
+ };
55068
55659
  var climateControlCapability = {
55069
55660
  name: "climate-control",
55070
55661
  scope: "device",
@@ -55375,6 +55966,7 @@ var climateControlCapability = {
55375
55966
  * on every push without re-querying the provider.
55376
55967
  */
55377
55968
  runtimeState: ClimateControlStatusSchema,
55969
+ customize: climateControlCustomize,
55378
55970
  /**
55379
55971
  * Runtime-state durability: **session** — as `brightness`; `currentTemp` moves continuously and is re-published on connect.
55380
55972
  *
@@ -56130,13 +56722,14 @@ var connectivityCapability = {
56130
56722
  * its device tracks consumables registers it; the provider names each item
56131
56723
  * verbatim and says which `kind` it is.
56132
56724
  *
56133
- * The admin UI renders it as ONE collapsible section directly under the
56134
- * device page's hero card (`placement: 'hero'`), for every device bound to
56135
- * this cap — never drawn by a device type's hero card itself (D915).
56725
+ * The admin UI shows it in the device card's consumables chip and popover
56726
+ * (kind-aware level, remaining time, "Reset"), for every device bound to this
56727
+ * cap (D1023). It no longer has a section of its own under the hero card
56728
+ * (D915 on that point).
56136
56729
  *
56137
56730
  * Like `childLayout`, the cap is INERT until a provider sets items: no
56138
56731
  * provider populates it by guessing (no HA inference). The UI renders a
56139
- * "No consumables reported" placeholder when `items` is empty.
56732
+ * "No consumables reported" line when `items` is empty.
56140
56733
  */
56141
56734
  /**
56142
56735
  * What an item IS, which decides which direction is bad:
@@ -56223,7 +56816,7 @@ var ConsumablesStatusSchema = object({
56223
56816
  });
56224
56817
  /** Synthesize seed for a link-built status (no native provider).
56225
56818
  *
56226
- * ⚠ NO LIVE CALLER since 2026-08-08 (device-links deleted; see
56819
+ * ! NO LIVE CALLER since 2026-08-08 (device-links deleted; see
56227
56820
  * `CapabilityStatusSchema.empty`). The wiring guidance below was written for
56228
56821
  * exactly the case the operator is asking about again — a Dreame's HA
56229
56822
  * consumable-life sensors — so it is kept for that conversation, not because
@@ -56327,14 +56920,6 @@ var consumablesCapability = {
56327
56920
  deviceNative: true,
56328
56921
  mode: "singleton",
56329
56922
  deviceTypes: Object.values(DeviceType),
56330
- deviceConfig: { ui: {
56331
- kind: "widget",
56332
- widgetId: "host/consumables-panel",
56333
- tab: "consumables",
56334
- placement: "hero",
56335
- label: "Consumables",
56336
- order: 5
56337
- } },
56338
56923
  methods: {
56339
56924
  /** Mark a consumable as replaced — resets its remaining life. Only
56340
56925
  * meaningful when the item's `resettable` is true. */
@@ -56563,20 +57148,95 @@ var ControlSetValueInputSchema = discriminatedUnion("kind", [
56563
57148
  value: string().min(1)
56564
57149
  })
56565
57150
  ]);
57151
+ /**
57152
+ * `next` / `previous` input (#544, operator scripts driving a selector).
57153
+ * `cycle` is a TOP-LEVEL default so a direct `ctx.api.control.next` call
57154
+ * (which skips the tRPC router's Zod parse) still gets `true` via
57155
+ * `CAP_INPUT_DEFAULTS` — matching HA's own `select_next` / `select_previous`
57156
+ * default.
57157
+ */
57158
+ var ControlStepInputSchema = object({
57159
+ deviceId: number$1().int().nonnegative(),
57160
+ cycle: boolean().default(true)
57161
+ });
57162
+ /** `first` / `last` input — no `cycle`, there is no boundary to stop at. */
57163
+ var ControlBoundaryInputSchema = object({ deviceId: number$1().int().nonnegative() });
56566
57164
  var controlCapability = {
56567
57165
  name: "control",
56568
57166
  scope: "device",
56569
57167
  deviceNative: true,
56570
57168
  mode: "singleton",
56571
57169
  deviceTypes: [DeviceType.Control],
56572
- methods: { setValue: method(object({
56573
- deviceId: number$1().int().nonnegative(),
56574
- control: ControlSetValueInputSchema
56575
- }), _void(), {
56576
- kind: "mutation",
56577
- auth: "protected",
56578
- 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"
56579
- }) },
57170
+ methods: {
57171
+ setValue: method(object({
57172
+ deviceId: number$1().int().nonnegative(),
57173
+ control: ControlSetValueInputSchema
57174
+ }), _void(), {
57175
+ kind: "mutation",
57176
+ auth: "protected",
57177
+ notOperatorFacing: "the value shape depends on the control kind (numeric / select / text / datetime) read from status; a typed picker for it is a follow-up of the automations rework"
57178
+ }),
57179
+ /**
57180
+ * Select one step forward through `options` (#544). `providerOptional`
57181
+ * (hence `optionalMethod`): only a `kind: 'select'` provider implements
57182
+ * it — the stepping math is NOT re-derived per provider, it is the one
57183
+ * shared helper `selectNavigationMethods` (`control-select-navigation.ts`)
57184
+ * every select-kind driver spreads in.
57185
+ */
57186
+ next: optionalMethod(ControlStepInputSchema, _void(), {
57187
+ kind: "mutation",
57188
+ auth: "protected",
57189
+ actions: [{
57190
+ id: "select.next",
57191
+ label: t("Next option", "Opzione successiva"),
57192
+ phrase: t("Select the next option on {device}", "Seleziona l’opzione successiva su {device}"),
57193
+ icon: "skip-forward",
57194
+ openArgs: [{
57195
+ key: "cycle",
57196
+ label: t("Wrap to the first option", "Ricomincia dalla prima opzione"),
57197
+ offer: { kind: "boolean" }
57198
+ }]
57199
+ }]
57200
+ }),
57201
+ /** Select one step backward through `options` (#544) — mirrors `next`. */
57202
+ previous: optionalMethod(ControlStepInputSchema, _void(), {
57203
+ kind: "mutation",
57204
+ auth: "protected",
57205
+ actions: [{
57206
+ id: "select.previous",
57207
+ label: t("Previous option", "Opzione precedente"),
57208
+ phrase: t("Select the previous option on {device}", "Seleziona l’opzione precedente su {device}"),
57209
+ icon: "skip-back",
57210
+ openArgs: [{
57211
+ key: "cycle",
57212
+ label: t("Wrap to the last option", "Ricomincia dall’ultima opzione"),
57213
+ offer: { kind: "boolean" }
57214
+ }]
57215
+ }]
57216
+ }),
57217
+ /** Select the first entry of `options` (#544). */
57218
+ first: optionalMethod(ControlBoundaryInputSchema, _void(), {
57219
+ kind: "mutation",
57220
+ auth: "protected",
57221
+ actions: [{
57222
+ id: "select.first",
57223
+ label: t("First option", "Prima opzione"),
57224
+ phrase: t("Select the first option on {device}", "Seleziona la prima opzione su {device}"),
57225
+ icon: "chevron-first"
57226
+ }]
57227
+ }),
57228
+ /** Select the last entry of `options` (#544). */
57229
+ last: optionalMethod(ControlBoundaryInputSchema, _void(), {
57230
+ kind: "mutation",
57231
+ auth: "protected",
57232
+ actions: [{
57233
+ id: "select.last",
57234
+ label: t("Last option", "Ultima opzione"),
57235
+ phrase: t("Select the last option on {device}", "Seleziona l’ultima opzione su {device}"),
57236
+ icon: "chevron-last"
57237
+ }]
57238
+ })
57239
+ },
56580
57240
  status: {
56581
57241
  schema: ControlStatusSchema,
56582
57242
  kind: "push"
@@ -57553,7 +58213,7 @@ var mediaPlayerCapability = {
57553
58213
  ]
57554
58214
  }
57555
58215
  };
57556
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Climate, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Camera, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Gateway, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.PetFeeder, DeviceType.MediaPlayer;
58216
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Climate, DeviceType.EventEmitter, DeviceType.Climate, DeviceType.Climate, DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Camera, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Gateway, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation, DeviceType.WeatherStation;
57557
58217
  var NUMBER$1 = {
57558
58218
  kind: "fixed",
57559
58219
  result: ["number"]
@@ -61880,7 +62540,7 @@ var TransitionsStatusSchema = object({
61880
62540
  /** Refused rows since start, by reason. */
61881
62541
  refused: record(string(), number$1())
61882
62542
  });
61883
- Object.values(DeviceType).filter((type) => type !== DeviceType.Hub && type !== DeviceType.Container), method(object({
62543
+ Object.values(DeviceType).filter((type) => type !== DeviceType.Container), method(object({
61884
62544
  deviceId: number$1().int(),
61885
62545
  capName: string().optional(),
61886
62546
  actorKind: TransitionActorKindSchema.optional(),
@@ -63122,6 +63782,10 @@ method(object({
63122
63782
  * system did not report is `null`, never `0` (D393).
63123
63783
  *
63124
63784
  * Signs, all in watts:
63785
+ * - The branches are DISJOINT: `house` is the consumption EXCLUDING the heat pump
63786
+ * whenever `heatPump` is reported, so solar + grid import + storage discharge =
63787
+ * house + heatPump + grid export + storage charge. A provider whose meter totals
63788
+ * include the pump subtracts it; when it cannot, `house` is null (D393).
63125
63789
  * - `solar.powerW`, `house.powerW`, `heatPump.powerW` are magnitudes (≥ 0);
63126
63790
  * - `grid.powerW` is + importing, − exporting;
63127
63791
  * - `storage.powerW` is + charging, − discharging.
@@ -63361,6 +64025,85 @@ var enumSensorCapability = {
63361
64025
  }]
63362
64026
  }
63363
64027
  };
64028
+ /** How loudly a fault wants the operator. Both tiers raise the attention event. */
64029
+ var DeviceErrorSeveritySchema = _enum(["warning", "error"]);
64030
+ /** One fault of a device that can raise several at once. */
64031
+ var DeviceFaultSchema = object({
64032
+ /** The device's own code, as the vendor names it. */
64033
+ code: string(),
64034
+ /** Already translated by the provider (it owns the vendor vocabulary). */
64035
+ label: string(),
64036
+ severity: DeviceErrorSeveritySchema
64037
+ });
64038
+ /**
64039
+ * The fault a device reports right now. One slice per device.
64040
+ *
64041
+ * `active` is tri-state: `null` is "the provider has not read it yet" and is
64042
+ * never drawn as "no fault" nor as a fault (D315, D393). The provider filters
64043
+ * what is not a fault (a completion or reminder code) BEFORE it writes `true`.
64044
+ *
64045
+ * The cap's name is the key a template reads (`{{ alias.error.label }}`, D1021).
64046
+ */
64047
+ var ErrorStatusSchema = object({
64048
+ active: boolean().nullable(),
64049
+ /** The code of the principal fault; `null` when none (or unknown). */
64050
+ code: string().nullable(),
64051
+ /** The principal fault's words, provider-translated; `null` when none. */
64052
+ label: string().nullable(),
64053
+ severity: DeviceErrorSeveritySchema,
64054
+ /** Ms epoch the current fault started; `null` when none or not known. */
64055
+ since: number$1().nullable(),
64056
+ /** Every fault, for a device that raises several. The principal is the first. */
64057
+ faults: array(DeviceFaultSchema).optional(),
64058
+ /** Ms epoch of the last read the provider made of the fault. */
64059
+ lastFetchedAt: number$1()
64060
+ });
64061
+ /** The slice before the provider has read anything: unknown, not "fine". */
64062
+ var ERROR_STATUS_COLD = {
64063
+ active: null,
64064
+ code: null,
64065
+ label: null,
64066
+ severity: "error",
64067
+ since: null,
64068
+ lastFetchedAt: 0
64069
+ };
64070
+ var errorCapability = {
64071
+ name: "error",
64072
+ scope: "device",
64073
+ deviceNative: true,
64074
+ mode: "singleton",
64075
+ methods: {},
64076
+ status: {
64077
+ schema: ErrorStatusSchema,
64078
+ kind: "push",
64079
+ empty: ERROR_STATUS_COLD
64080
+ },
64081
+ runtimeState: ErrorStatusSchema,
64082
+ /**
64083
+ * Runtime-state durability: **restored** — the previous observation is what
64084
+ * lets the first reading after a restart be a COMPARISON: a fault that was
64085
+ * already open must not announce itself a second time.
64086
+ *
64087
+ * See `RuntimeStateDurability`. Enforced by
64088
+ * `scripts/check-runtime-state-durability.ts`.
64089
+ */
64090
+ durability: "restored",
64091
+ volatileStateFields: ["lastFetchedAt"],
64092
+ /** The fault itself is carried by the attention alerts; no series is recorded. */
64093
+ history: "none",
64094
+ vocabulary: {
64095
+ noun: t("Fault", "Guasto"),
64096
+ order: 5265,
64097
+ state: [{
64098
+ kind: "boolean",
64099
+ field: "active",
64100
+ label: t("Fault", "Guasto"),
64101
+ whenTrue: t("When {device} reports a fault", "Quando {device} segnala un guasto"),
64102
+ whenFalse: t("When the fault of {device} clears", "Quando il guasto di {device} si risolve"),
64103
+ icon: "triangle-alert"
64104
+ }]
64105
+ }
64106
+ };
63364
64107
  var IdentitySchema = object({
63365
64108
  id: string(),
63366
64109
  name: string(),
@@ -63375,7 +64118,19 @@ var IdentitySampleInfoSchema = object({
63375
64118
  source: _enum(["detected", "photo"]),
63376
64119
  addedAt: number$1().int(),
63377
64120
  deviceId: number$1().int().optional(),
63378
- base64: string().optional()
64121
+ base64: string().optional(),
64122
+ /**
64123
+ * The sample's own crop, when it has one. The bytes stay on the event-media
64124
+ * plane — this list does not inline them.
64125
+ */
64126
+ mediaId: number$1().int().optional(),
64127
+ /**
64128
+ * The source face's key frame (or the photo the sample was cut from), when
64129
+ * the sample has no crop of its own. Draw {@link faceBbox} on it.
64130
+ */
64131
+ keyFrameMediaId: number$1().int().optional(),
64132
+ /** The face box on {@link keyFrameMediaId}, pixel space. */
64133
+ faceBbox: BoundingBoxSchema.optional()
63379
64134
  });
63380
64135
  /**
63381
64136
  * One enrolled sample, judged against the gallery it lives in.
@@ -63590,6 +64345,12 @@ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly
63590
64345
  }), _void(), {
63591
64346
  kind: "mutation",
63592
64347
  auth: "admin"
64348
+ }), method(object({
64349
+ sampleId: string().min(1),
64350
+ toIdentityId: string().min(1)
64351
+ }), _void(), {
64352
+ kind: "mutation",
64353
+ auth: "admin"
63593
64354
  }), method(object({
63594
64355
  /**
63595
64356
  * Restrict to ONE camera. Absent keeps the cluster-wide gallery view.
@@ -65827,7 +66588,7 @@ var HeroSceneSchema = object({
65827
66588
  /** 0..1: volume, cover position, cycle progress. */
65828
66589
  level: number$1().min(0).max(1).nullable()
65829
66590
  });
65830
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
66591
+ DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
65831
66592
  DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.SwitchHeater, DeviceRole.SwitchFan, DeviceRole.SwitchLight, DeviceRole.ClimateAc, DeviceRole.ClimateThermostat, DeviceRole.ClimateFloor, DeviceRole.ClimateRadiator, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
65832
66593
  var HeroRowIdSchema = _enum([
65833
66594
  "progress",
@@ -70583,7 +71344,9 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
70583
71344
  auth: "admin"
70584
71345
  }), method(object({
70585
71346
  owner: string().min(1),
70586
- metas: array(MigrationMetaSchema)
71347
+ metas: array(MigrationMetaSchema),
71348
+ /** Ids the owner deliberately removed: an applied row of one is fine, never "newer". */
71349
+ retired: array(string().min(1)).optional()
70587
71350
  }), object({
70588
71351
  bootId: string(),
70589
71352
  snapshot: _enum([
@@ -77850,6 +78613,8 @@ var NcSystemEventKindSchema = _enum([
77850
78613
  "storage-location-low",
77851
78614
  "storage-location-critical",
77852
78615
  "storage-location-ok",
78616
+ "storage-drain-progress",
78617
+ "storage-drain-complete",
77853
78618
  "addon-crash-loop",
77854
78619
  "addon-update-available",
77855
78620
  "server-update-available",
@@ -82292,7 +83057,19 @@ var VehicleSampleInfoSchema = object({
82292
83057
  score: number$1(),
82293
83058
  addedAt: number$1().int(),
82294
83059
  deviceId: number$1().int().optional(),
82295
- base64: string().optional()
83060
+ base64: string().optional(),
83061
+ /**
83062
+ * The sample's own crop, when it has one. Prefer this over {@link base64}:
83063
+ * the bytes are on the event-media plane (`/m/<mediaId>`), never inlined.
83064
+ */
83065
+ mediaId: number$1().int().optional(),
83066
+ /**
83067
+ * The source plate's key frame, when the sample has no crop of its own.
83068
+ * Draw {@link plateBbox} on it. Absent when {@link mediaId} is set.
83069
+ */
83070
+ keyFrameMediaId: number$1().int().optional(),
83071
+ /** The plate box on {@link keyFrameMediaId}, pixel space. */
83072
+ plateBbox: BoundingBoxSchema.optional()
82296
83073
  });
82297
83074
  var PlateInfoSchema = object({
82298
83075
  plateId: string(),
@@ -82322,7 +83099,17 @@ var PlateInfoSchema = object({
82322
83099
  * and `searchPlates` inline the crop only when their `includeCrops` input is
82323
83100
  * left at its `true` default.
82324
83101
  */
82325
- cropUrl: string().optional()
83102
+ cropUrl: string().optional(),
83103
+ /**
83104
+ * Why {@link cropUrl} is absent. One of `no-frame-handle`,
83105
+ * `live-capture-missed`, `live-capture-threw`, `no-key-frame`,
83106
+ * `derive-not-wired`, `subject-moved`, `temporal-skew`, `key-frame-absent`,
83107
+ * `bad-frame-size`, `decode-empty`, `derive-threw`, `derive-returned-null`,
83108
+ * `put-failed`. A crossing vehicle whose live capture missed is
83109
+ * `subject-moved`: the key frame is a different instant, and the crop is
83110
+ * legitimately absent.
83111
+ */
83112
+ cropMissReason: string().optional()
82326
83113
  });
82327
83114
  /**
82328
83115
  * One gallery media row: what the crop is, how big it is, and WHERE its bytes
@@ -82422,6 +83209,12 @@ includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly(
82422
83209
  }), _void(), {
82423
83210
  kind: "mutation",
82424
83211
  auth: "admin"
83212
+ }), method(object({
83213
+ sampleId: string().min(1),
83214
+ toVehicleId: string().min(1)
83215
+ }), _void(), {
83216
+ kind: "mutation",
83217
+ auth: "admin"
82425
83218
  }), method(object({
82426
83219
  plateId: string(),
82427
83220
  vehicleId: string()
@@ -86250,7 +87043,10 @@ object({
86250
87043
  value: boolean(),
86251
87044
  at: number$1()
86252
87045
  }).nullable(),
86253
- /** The declared source last sent; unknown after a power off or a channel step. */
87046
+ /**
87047
+ * Retired: a sent source is never a state (an infrared set cannot report its
87048
+ * input). Kept only so records written before parse; nothing writes or reads it.
87049
+ */
86254
87050
  source: object({
86255
87051
  name: string(),
86256
87052
  at: number$1()
@@ -89879,31 +90675,57 @@ var TemplateSelectOptionSchema = object({
89879
90675
  value: string().min(1),
89880
90676
  label: string().min(1).max(80)
89881
90677
  });
90678
+ var SelectInputSchema = object({
90679
+ kind: literal("select"),
90680
+ ...inputBase,
90681
+ options: array(TemplateSelectOptionSchema).min(1).max(50),
90682
+ default: string().optional()
90683
+ });
90684
+ var BooleanInputSchema = object({
90685
+ kind: literal("boolean"),
90686
+ ...inputBase,
90687
+ default: boolean().optional()
90688
+ });
90689
+ /** A limit on when the automation may run; only a `gate` binding reads it. */
90690
+ var TimeWindowInputSchema = object({
90691
+ kind: literal("time-window"),
90692
+ ...inputBase
90693
+ });
89882
90694
  var TemplateScalarInputSchema = discriminatedUnion("kind", [
90695
+ object({
90696
+ kind: literal("entries"),
90697
+ ...inputBase,
90698
+ entry: _enum(["trigger", "step"]),
90699
+ allow: array(string().min(1)).min(1).max(10),
90700
+ maxItems: number$1().int().min(1).max(20).optional()
90701
+ }),
89883
90702
  DeviceInputSchema,
89884
90703
  EventInputSchema,
89885
90704
  ActionInputSchema$1,
89886
90705
  NumberInputSchema,
89887
90706
  DurationInputSchema,
89888
90707
  TextInputSchema,
89889
- object({
89890
- kind: literal("select"),
89891
- ...inputBase,
89892
- options: array(TemplateSelectOptionSchema).min(1).max(50),
89893
- default: string().optional()
89894
- }),
89895
- object({
89896
- kind: literal("boolean"),
89897
- ...inputBase,
89898
- default: boolean().optional()
89899
- })
90708
+ SelectInputSchema,
90709
+ BooleanInputSchema,
90710
+ TimeWindowInputSchema
89900
90711
  ]);
89901
- var GroupInputSchema = object({
90712
+ var groupBase = {
89902
90713
  kind: literal("group"),
89903
90714
  ...inputBase,
89904
- inputs: array(TemplateScalarInputSchema).min(1).max(10),
90715
+ /** What one row is, in the singular and lower case ("button"): "Add button". */
90716
+ rowLabel: string().min(1).max(40).optional(),
89905
90717
  minRows: number$1().int().min(0).max(20).optional(),
89906
90718
  maxRows: number$1().int().min(1).max(20).optional()
90719
+ };
90720
+ /** A group inside a group row: its rows hold values only, never another group. */
90721
+ var NestedGroupInputSchema = object({
90722
+ ...groupBase,
90723
+ inputs: array(TemplateScalarInputSchema).min(1).max(10)
90724
+ });
90725
+ var TemplateRowInputSchema = discriminatedUnion("kind", [...TemplateScalarInputSchema.options, NestedGroupInputSchema]);
90726
+ var GroupInputSchema = object({
90727
+ ...groupBase,
90728
+ inputs: array(TemplateRowInputSchema).min(1).max(10)
89907
90729
  });
89908
90730
  var TemplateInputSchema = discriminatedUnion("kind", [...TemplateScalarInputSchema.options, GroupInputSchema]);
89909
90731
  var TemplatePathSchema = array(union([string().min(1), number$1().int().nonnegative()])).max(24);
@@ -89914,41 +90736,154 @@ var TemplateValueBindingSchema = object({
89914
90736
  /** A device value written as a step target (`{ kind: 'devices', ids }`). */
89915
90737
  as: literal("target").optional()
89916
90738
  });
89917
- var TemplateRowItemBindingSchema = discriminatedUnion("kind", [TemplateValueBindingSchema, object({
90739
+ var TemplateRowIdBindingSchema = object({
89918
90740
  kind: literal("row-id"),
89919
90741
  path: TemplatePathSchema
89920
- })]);
89921
- var TemplateBindingSchema = discriminatedUnion("kind", [TemplateValueBindingSchema, object({
89922
- kind: literal("rows"),
90742
+ });
90743
+ /**
90744
+ * Appends an `entries` input's elements, verbatim, to the list at `path`. In a
90745
+ * row's bindings `input` is the row's own entries input and `path` is from the
90746
+ * row's item; at the top level, with `in`, it appends the `input` entries of
90747
+ * EVERY row of the group `in` (all buttons' triggers into one `when`), and
90748
+ * without `in` it appends a top-level entries input.
90749
+ */
90750
+ var TemplateEntriesBindingSchema = object({
90751
+ kind: literal("entries"),
89923
90752
  input: inputKey,
89924
- /** The list in the body the rows are appended to. */
89925
90753
  path: TemplatePathSchema,
89926
- /** The JSON copied once per row. */
89927
- item: unknown(),
89928
- bindings: array(TemplateRowItemBindingSchema).max(40)
89929
- })]);
89930
- function bindingProblems(payload, ctx) {
89931
- const byKey = /* @__PURE__ */ new Map();
89932
- payload.inputs.forEach((input, index) => {
89933
- if (byKey.has(input.key)) ctx.addIssue({
89934
- code: "custom",
90754
+ in: inputKey.optional()
90755
+ });
90756
+ /** What fills the item of a row that holds values only. */
90757
+ var TemplateLeafRowBindingSchema = discriminatedUnion("kind", [TemplateValueBindingSchema, TemplateRowIdBindingSchema]);
90758
+ var TemplateRowItemBindingSchema = discriminatedUnion("kind", [
90759
+ TemplateValueBindingSchema,
90760
+ TemplateEntriesBindingSchema,
90761
+ TemplateRowIdBindingSchema,
90762
+ object({
90763
+ kind: literal("row-ids"),
90764
+ /** The nested group whose rows are named. */
90765
+ input: inputKey,
90766
+ path: TemplatePathSchema
90767
+ }),
90768
+ object({
90769
+ kind: literal("rows"),
90770
+ /** The nested group, declared by the parent row's group. */
90771
+ input: inputKey,
90772
+ /** The list the nested rows are appended to, from the parent row's item. */
90773
+ path: TemplatePathSchema,
90774
+ item: unknown(),
90775
+ bindings: array(TemplateLeafRowBindingSchema).max(40)
90776
+ })
90777
+ ]);
90778
+ var TemplateBindingSchema = discriminatedUnion("kind", [
90779
+ TemplateValueBindingSchema,
90780
+ TemplateEntriesBindingSchema,
90781
+ object({
90782
+ kind: literal("rows"),
90783
+ input: inputKey,
90784
+ /**
90785
+ * A group the rows of `input` hold: the binding then appends one item per
90786
+ * NESTED row of every row, and its bindings read the nested row.
90787
+ */
90788
+ nested: inputKey.optional(),
90789
+ /** The list in the body the rows are appended to. */
90790
+ path: TemplatePathSchema,
90791
+ /** The JSON copied once per row. */
90792
+ item: unknown(),
90793
+ bindings: array(TemplateRowItemBindingSchema).max(40)
90794
+ }),
90795
+ object({
90796
+ kind: literal("gate"),
90797
+ /** A top-level `time-window` input. */
90798
+ input: inputKey,
90799
+ path: TemplatePathSchema
90800
+ })
90801
+ ]);
90802
+ /** Twice-declared keys and event / action inputs that read no device beside them. */
90803
+ function levelProblems(inputs, path, inRow = false) {
90804
+ const issues = [];
90805
+ const seen = /* @__PURE__ */ new Set();
90806
+ for (const input of inputs) {
90807
+ if (seen.has(input.key)) issues.push({
89935
90808
  message: `input "${input.key}" is declared twice`,
89936
- path: ["inputs", index]
90809
+ path
89937
90810
  });
89938
- byKey.set(input.key, input);
89939
- const siblings = input.kind === "group" ? input.inputs : payload.inputs;
89940
- for (const scalar of input.kind === "group" ? input.inputs : [input]) {
89941
- if (scalar.kind !== "event" && scalar.kind !== "action") continue;
89942
- if (scalar.deviceInput === void 0) continue;
89943
- const device = siblings.find((s) => s.key === scalar.deviceInput);
89944
- if (device === void 0 || device.kind !== "device") ctx.addIssue({
89945
- code: "custom",
89946
- message: `"${scalar.key}" reads the device input "${scalar.deviceInput}", which is not a device input beside it`,
89947
- path: ["inputs", index]
89948
- });
90811
+ seen.add(input.key);
90812
+ if (input.kind === "group") {
90813
+ issues.push(...levelProblems(input.inputs, path, true));
90814
+ continue;
90815
+ }
90816
+ if (input.kind === "time-window" && inRow) issues.push({
90817
+ message: `time window "${input.key}" limits the whole automation: it is not a row input`,
90818
+ path
90819
+ });
90820
+ if (input.kind !== "event" && input.kind !== "action") continue;
90821
+ if (input.deviceInput === void 0) continue;
90822
+ const device = inputs.find((sibling) => sibling.key === input.deviceInput);
90823
+ if (device === void 0 || device.kind !== "device") issues.push({
90824
+ message: `"${input.key}" reads the device input "${input.deviceInput}", which is not a device input beside it`,
90825
+ path
90826
+ });
90827
+ }
90828
+ return issues;
90829
+ }
90830
+ function nestedGroupOf(group, key) {
90831
+ const found = group.inputs.find((input) => input.key === key);
90832
+ return found !== void 0 && found.kind === "group" ? found : void 0;
90833
+ }
90834
+ function leafBindingProblems(bindings, owner) {
90835
+ const issues = [];
90836
+ for (const row of bindings) {
90837
+ if (row.kind === "row-id") continue;
90838
+ if (row.kind === "value") {
90839
+ const input = owner.inputs.find((i) => i.key === row.input);
90840
+ if (input === void 0) issues.push(`row binding names "${row.input}", which group "${owner.key}" does not declare`);
90841
+ else if (input.kind === "group") issues.push(`group "${input.key}" is bound with rows, not a value`);
90842
+ else if (input.kind === "entries") issues.push(`entries "${input.key}" are bound with entries, not a value`);
90843
+ continue;
90844
+ }
90845
+ const nested = owner.inputs.find((i) => i.key === row.input);
90846
+ if (row.kind === "entries") {
90847
+ if (nested === void 0 || nested.kind !== "entries") issues.push(`"${row.input}" is not an entries input inside "${owner.key}"`);
90848
+ continue;
89949
90849
  }
90850
+ if (row.kind === "row-ids") {
90851
+ if (nested === void 0 || nested.kind !== "group" && nested.kind !== "entries") issues.push(`"${row.input}" is not a group or entries input inside "${owner.key}"`);
90852
+ continue;
90853
+ }
90854
+ if (nested === void 0 || nested.kind !== "group") {
90855
+ issues.push(`"${row.input}" is not a group inside "${owner.key}"`);
90856
+ continue;
90857
+ }
90858
+ issues.push(...leafBindingProblems(row.bindings, nested));
90859
+ }
90860
+ return issues;
90861
+ }
90862
+ function rowsBindingProblems(binding, group) {
90863
+ if (binding.nested === void 0) return leafBindingProblems(binding.bindings, group);
90864
+ const nested = nestedGroupOf(group, binding.nested);
90865
+ if (nested === void 0) return [`"${binding.nested}" is not a group inside "${group.key}"`];
90866
+ return leafBindingProblems(binding.bindings, nested);
90867
+ }
90868
+ function bindingProblems(payload, ctx) {
90869
+ const byKey = /* @__PURE__ */ new Map();
90870
+ for (const input of payload.inputs) byKey.set(input.key, input);
90871
+ for (const issue of levelProblems(payload.inputs, ["inputs"])) ctx.addIssue({
90872
+ code: "custom",
90873
+ message: issue.message,
90874
+ path: [...issue.path]
89950
90875
  });
89951
90876
  payload.bindings.forEach((binding, index) => {
90877
+ if (binding.kind === "entries" && binding.in !== void 0) {
90878
+ const group = byKey.get(binding.in);
90879
+ const entries = group !== void 0 && group.kind === "group" ? group.inputs.find((i) => i.key === binding.input) : void 0;
90880
+ if (entries === void 0 || entries.kind !== "entries") ctx.addIssue({
90881
+ code: "custom",
90882
+ message: `"${binding.input}" is not an entries input inside the group "${binding.in}"`,
90883
+ path: ["bindings", index]
90884
+ });
90885
+ return;
90886
+ }
89952
90887
  const input = byKey.get(binding.input);
89953
90888
  if (input === void 0) {
89954
90889
  ctx.addIssue({
@@ -89963,6 +90898,18 @@ function bindingProblems(payload, ctx) {
89963
90898
  message: `group "${input.key}" is bound with rows, not a value`,
89964
90899
  path: ["bindings", index]
89965
90900
  });
90901
+ if (binding.kind === "entries" !== (input.kind === "entries") && binding.kind !== "rows") {
90902
+ if (binding.kind === "entries" || binding.kind === "value") ctx.addIssue({
90903
+ code: "custom",
90904
+ message: binding.kind === "entries" ? `entries bindings read an entries input: "${input.key}" is ${input.kind}` : `entries "${input.key}" are bound with entries, not a value`,
90905
+ path: ["bindings", index]
90906
+ });
90907
+ }
90908
+ if (binding.kind === "gate" !== (input.kind === "time-window")) ctx.addIssue({
90909
+ code: "custom",
90910
+ message: `a gate reads a time window and a time window is bound with a gate: "${input.key}" is ${input.kind}`,
90911
+ path: ["bindings", index]
90912
+ });
89966
90913
  if (binding.kind === "rows") {
89967
90914
  if (input.kind !== "group") {
89968
90915
  ctx.addIssue({
@@ -89972,9 +90919,9 @@ function bindingProblems(payload, ctx) {
89972
90919
  });
89973
90920
  return;
89974
90921
  }
89975
- for (const row of binding.bindings) if (row.kind === "value" && !input.inputs.some((i) => i.key === row.input)) ctx.addIssue({
90922
+ for (const message of rowsBindingProblems(binding, input)) ctx.addIssue({
89976
90923
  code: "custom",
89977
- message: `row binding names "${row.input}", which group "${input.key}" does not declare`,
90924
+ message,
89978
90925
  path: ["bindings", index]
89979
90926
  });
89980
90927
  }
@@ -90153,6 +91100,17 @@ var TerminalOutputEventSchema = discriminatedUnion("kind", [object({
90153
91100
  exitCode: number$1().int(),
90154
91101
  signal: number$1().int().optional()
90155
91102
  })]);
91103
+ var ServerShellRefusalSchema = object({
91104
+ code: literal("server-shell-disabled"),
91105
+ nodeId: string(),
91106
+ message: string()
91107
+ });
91108
+ var ServerShellStatusSchema = object({
91109
+ nodeId: string(),
91110
+ enabled: boolean(),
91111
+ idleMinutes: number$1().int().positive(),
91112
+ refusal: ServerShellRefusalSchema.nullable()
91113
+ });
90156
91114
  var TerminalOutputBatchSchema = object({
90157
91115
  cursor: number$1().int().nonnegative(),
90158
91116
  reset: boolean(),
@@ -90165,7 +91123,7 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
90165
91123
  }), TerminalInstanceInfoSchema, {
90166
91124
  kind: "mutation",
90167
91125
  auth: "admin"
90168
- }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
91126
+ }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(_void(), ServerShellStatusSchema, { auth: "admin" }), method(object({
90169
91127
  profileId: string(),
90170
91128
  cols: number$1().int().positive(),
90171
91129
  rows: number$1().int().positive(),
@@ -90175,7 +91133,8 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
90175
91133
  environment: array(string().max(4096)).max(64).optional()
90176
91134
  }), TerminalSessionInfoSchema, {
90177
91135
  kind: "mutation",
90178
- auth: "admin"
91136
+ auth: "admin",
91137
+ caller: "required"
90179
91138
  }), method(object({
90180
91139
  sessionId: string(),
90181
91140
  cols: number$1().int().positive(),
@@ -90196,6 +91155,7 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
90196
91155
  }), TerminalOutputBatchSchema, {
90197
91156
  kind: "mutation",
90198
91157
  auth: "admin",
91158
+ caller: "required",
90199
91159
  timeoutMs: 15e3
90200
91160
  }), method(object({
90201
91161
  sessionId: string(),
@@ -90205,7 +91165,8 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
90205
91165
  auth: "admin"
90206
91166
  }), method(object({ sessionId: string() }), _void(), {
90207
91167
  kind: "mutation",
90208
- auth: "admin"
91168
+ auth: "admin",
91169
+ caller: "required"
90209
91170
  }), NOT_A_DEVICE.CLUSTER;
90210
91171
  /**
90211
91172
  * toast — system-scoped singleton capability that streams toast
@@ -92159,6 +93120,265 @@ var weatherCapability = {
92159
93120
  ]
92160
93121
  }
92161
93122
  };
93123
+ var WeatherStationStatusSchema = object({
93124
+ temperature: number$1().nullable().default(null).meta(readingMeta("temperature", { precision: 1 })),
93125
+ humidity: number$1().min(0).max(100).nullable().default(null).meta(readingMeta("humidity")),
93126
+ pressure: number$1().nullable().default(null).meta(readingMeta("pressure")),
93127
+ windSpeed: number$1().nullable().default(null).meta(readingMeta("speed")),
93128
+ windGust: number$1().nullable().default(null).meta(readingMeta("speed")),
93129
+ windBearing: number$1().nullable().default(null).meta(readingMeta("angle")),
93130
+ rainRate: number$1().nullable().default(null).meta(readingMeta("precipitation-rate", { precision: 1 })),
93131
+ rainDaily: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 })),
93132
+ rainEvent: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 })),
93133
+ uv: number$1().nullable().default(null).meta(readingMeta("index", { precision: 1 })),
93134
+ solarRadiation: number$1().nullable().default(null).meta(readingMeta("irradiance")),
93135
+ dewPoint: number$1().nullable().default(null).meta(readingMeta("temperature", { precision: 1 })),
93136
+ /** Apparent ("feels like") temperature. */
93137
+ feelsLike: number$1().nullable().default(null).meta(readingMeta("temperature", { precision: 1 })),
93138
+ /** Vapour-pressure deficit, stored in kPa (the unit stations and growers read it in). */
93139
+ vpd: number$1().nullable().default(null).meta(readingMeta("none", { precision: 2 })),
93140
+ /** Highest wind gust since local midnight. */
93141
+ windGustMax: number$1().nullable().default(null).meta(readingMeta("speed")),
93142
+ /** Wind bearing averaged over the station's own window. */
93143
+ windBearingAvg: number$1().nullable().default(null).meta(readingMeta("angle")),
93144
+ rain24h: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 })),
93145
+ rainWeekly: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 })),
93146
+ rainMonthly: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 })),
93147
+ rainYearly: number$1().nullable().default(null).meta(readingMeta("precipitation", { precision: 1 }))
93148
+ }).extend({
93149
+ /** Ms epoch of the poll that produced this slice. 0 before the first one. */
93150
+ lastFetchedAt: number$1() });
93151
+ var numeric$2 = (field, quantity, label, unit, icon) => ({
93152
+ kind: "numeric",
93153
+ field,
93154
+ quantity,
93155
+ label,
93156
+ unit,
93157
+ icon
93158
+ });
93159
+ /** Every reading of the station with the unit a Customize source must carry ('' = dimensionless). */
93160
+ var STATION_FIELD_UNITS = {
93161
+ temperature: "°C",
93162
+ humidity: "%",
93163
+ pressure: "hPa",
93164
+ windSpeed: "m/s",
93165
+ windGust: "m/s",
93166
+ windBearing: "°",
93167
+ rainRate: "mm/h",
93168
+ rainDaily: "mm",
93169
+ rainEvent: "mm",
93170
+ uv: "",
93171
+ solarRadiation: "W/m²",
93172
+ dewPoint: "°C",
93173
+ feelsLike: "°C",
93174
+ vpd: "kPa",
93175
+ windGustMax: "m/s",
93176
+ windBearingAvg: "°",
93177
+ rain24h: "mm",
93178
+ rainWeekly: "mm",
93179
+ rainMonthly: "mm",
93180
+ rainYearly: "mm"
93181
+ };
93182
+ function isStationField(key) {
93183
+ return Object.prototype.hasOwnProperty.call(STATION_FIELD_UNITS, key);
93184
+ }
93185
+ var STATION_FIELDS = Object.keys(STATION_FIELD_UNITS).filter(isStationField);
93186
+ /**
93187
+ * One scalar target per reading, `apply: 'add'` and listed in `overridable` by its own id
93188
+ * (the running-state / climate-control mechanism, D984 / D990): a station that reports a
93189
+ * reading natively keeps it until the operator assigns another device to that ONE field,
93190
+ * and a device with no `weather-station` slice becomes one from the sources picked. On an
93191
+ * add, every other reading is absent from the claim and the schema defaults it to null
93192
+ * (a missing sensor is never 0, D393) — the fan-control precedent, never a cross-field companion
93193
+ * (the scalar merge treats a companion as owned by its target).
93194
+ */
93195
+ function stationTarget(field) {
93196
+ const unit = STATION_FIELD_UNITS[field];
93197
+ return {
93198
+ id: field,
93199
+ cardinality: "scalar",
93200
+ vocabulary: {
93201
+ cap: "weather-station",
93202
+ stateField: field
93203
+ },
93204
+ deviceTypes: [DeviceType.WeatherStation],
93205
+ apply: "add",
93206
+ path: field,
93207
+ sources: [{
93208
+ kind: "numeric",
93209
+ ...unit.length > 0 ? { requiredUnit: unit } : {},
93210
+ transforms: [{ kind: "identity" }]
93211
+ }],
93212
+ companions: [{
93213
+ kind: "clock",
93214
+ path: "lastFetchedAt",
93215
+ when: "add"
93216
+ }],
93217
+ sourceReport: { status: "assignment-status" }
93218
+ };
93219
+ }
93220
+ var weatherStationCustomize = {
93221
+ version: 1,
93222
+ overridable: STATION_FIELDS.map((field) => ({
93223
+ path: field,
93224
+ by: field
93225
+ })),
93226
+ targets: STATION_FIELDS.map(stationTarget)
93227
+ };
93228
+ var weatherStationCapability = {
93229
+ name: "weather-station",
93230
+ scope: "device",
93231
+ deviceNative: true,
93232
+ mode: "singleton",
93233
+ deviceTypes: [DeviceType.WeatherStation],
93234
+ methods: {},
93235
+ status: {
93236
+ schema: WeatherStationStatusSchema,
93237
+ kind: "push"
93238
+ },
93239
+ runtimeState: WeatherStationStatusSchema,
93240
+ /**
93241
+ * History (D850): the station records its own readings. Rain totals that
93242
+ * reset (event, day, week, month, year) and the sliding 24 h total are
93243
+ * levels, so `gauge`; `counter` is for a monotonic meter only.
93244
+ */
93245
+ history: { fields: {
93246
+ temperature: {
93247
+ kind: "gauge",
93248
+ unit: "°C",
93249
+ deadband: .1
93250
+ },
93251
+ humidity: {
93252
+ kind: "gauge",
93253
+ unit: "%",
93254
+ deadband: 1
93255
+ },
93256
+ pressure: {
93257
+ kind: "gauge",
93258
+ unit: "hPa",
93259
+ deadband: .2
93260
+ },
93261
+ windSpeed: {
93262
+ kind: "gauge",
93263
+ unit: "m/s",
93264
+ deadband: .2
93265
+ },
93266
+ windGust: {
93267
+ kind: "gauge",
93268
+ unit: "m/s",
93269
+ deadband: .2
93270
+ },
93271
+ windBearing: {
93272
+ kind: "gauge",
93273
+ unit: "°",
93274
+ deadband: 5
93275
+ },
93276
+ rainRate: {
93277
+ kind: "gauge",
93278
+ unit: "mm/h",
93279
+ deadband: .1
93280
+ },
93281
+ rainDaily: {
93282
+ kind: "gauge",
93283
+ unit: "mm",
93284
+ deadband: .1
93285
+ },
93286
+ rainEvent: {
93287
+ kind: "gauge",
93288
+ unit: "mm",
93289
+ deadband: .1
93290
+ },
93291
+ uv: {
93292
+ kind: "gauge",
93293
+ unit: "index",
93294
+ deadband: .1
93295
+ },
93296
+ solarRadiation: {
93297
+ kind: "gauge",
93298
+ unit: "W/m²",
93299
+ deadband: 5
93300
+ },
93301
+ dewPoint: {
93302
+ kind: "gauge",
93303
+ unit: "°C",
93304
+ deadband: .1
93305
+ },
93306
+ feelsLike: {
93307
+ kind: "gauge",
93308
+ unit: "°C",
93309
+ deadband: .1
93310
+ },
93311
+ vpd: {
93312
+ kind: "gauge",
93313
+ unit: "kPa",
93314
+ deadband: .05
93315
+ },
93316
+ windGustMax: {
93317
+ kind: "gauge",
93318
+ unit: "m/s",
93319
+ deadband: .2
93320
+ },
93321
+ windBearingAvg: {
93322
+ kind: "gauge",
93323
+ unit: "°",
93324
+ deadband: 5
93325
+ },
93326
+ rain24h: {
93327
+ kind: "gauge",
93328
+ unit: "mm",
93329
+ deadband: .1
93330
+ },
93331
+ rainWeekly: {
93332
+ kind: "gauge",
93333
+ unit: "mm",
93334
+ deadband: .1
93335
+ },
93336
+ rainMonthly: {
93337
+ kind: "gauge",
93338
+ unit: "mm",
93339
+ deadband: .1
93340
+ },
93341
+ rainYearly: {
93342
+ kind: "gauge",
93343
+ unit: "mm",
93344
+ deadband: .1
93345
+ }
93346
+ } },
93347
+ customize: weatherStationCustomize,
93348
+ /** The poll clock is written but is no reason to commit a row. */
93349
+ volatileStateFields: ["lastFetchedAt"],
93350
+ /**
93351
+ * Runtime-state durability: **session** — a station reading is stale the
93352
+ * moment the hub is down; the provider re-reads on the next poll.
93353
+ */
93354
+ durability: "session",
93355
+ vocabulary: {
93356
+ noun: t("Weather station", "Stazione meteo"),
93357
+ order: 5235,
93358
+ state: [
93359
+ numeric$2("temperature", t("the outdoor temperature", "la temperatura esterna"), t("Temperature", "Temperatura"), "°C", "thermometer"),
93360
+ numeric$2("humidity", t("the outdoor humidity", "l’umidità esterna"), t("Humidity", "Umidità"), "%", "droplets"),
93361
+ numeric$2("pressure", t("the air pressure", "la pressione atmosferica"), t("Pressure", "Pressione"), "hPa", "gauge"),
93362
+ numeric$2("windSpeed", t("the wind speed", "la velocità del vento"), t("Wind", "Vento"), "m/s", "wind"),
93363
+ numeric$2("windGust", t("the wind gust", "la raffica di vento"), t("Gust", "Raffica"), "m/s", "wind"),
93364
+ numeric$2("windBearing", t("the wind bearing", "la direzione del vento"), t("Wind bearing", "Direzione del vento"), "°", "wind"),
93365
+ numeric$2("rainRate", t("the rain rate", "l’intensità della pioggia"), t("Rain rate", "Intensità pioggia"), "mm/h", "droplets"),
93366
+ numeric$2("rainDaily", t("the rain today", "la pioggia di oggi"), t("Rain today", "Pioggia oggi"), "mm", "droplets"),
93367
+ numeric$2("rainEvent", t("the rain this event", "la pioggia di questo evento"), t("Rain event", "Evento di pioggia"), "mm", "droplets"),
93368
+ numeric$2("uv", t("the UV index", "l’indice UV"), t("UV", "UV"), "", "cloud-sun"),
93369
+ numeric$2("solarRadiation", t("the solar radiation", "la radiazione solare"), t("Solar", "Sole"), "W/m²", "cloud-sun"),
93370
+ numeric$2("dewPoint", t("the dew point", "il punto di rugiada"), t("Dew point", "Punto di rugiada"), "°C", "thermometer"),
93371
+ numeric$2("feelsLike", t("the feels-like temperature", "la temperatura percepita"), t("Feels like", "Percepita"), "°C", "thermometer"),
93372
+ numeric$2("vpd", t("the vapour-pressure deficit", "il deficit di pressione di vapore"), t("VPD", "VPD"), "kPa", "gauge"),
93373
+ numeric$2("windGustMax", t("the highest gust today", "la raffica massima di oggi"), t("Max gust", "Raffica max"), "m/s", "wind"),
93374
+ numeric$2("windBearingAvg", t("the average wind bearing", "la direzione media del vento"), t("Avg bearing", "Direzione media"), "°", "wind"),
93375
+ numeric$2("rain24h", t("the rain in the last 24 hours", "la pioggia delle ultime 24 ore"), t("Rain 24 h", "Pioggia 24 h"), "mm", "droplets"),
93376
+ numeric$2("rainWeekly", t("the rain this week", "la pioggia di questa settimana"), t("Rain week", "Pioggia settimana"), "mm", "droplets"),
93377
+ numeric$2("rainMonthly", t("the rain this month", "la pioggia di questo mese"), t("Rain month", "Pioggia mese"), "mm", "droplets"),
93378
+ numeric$2("rainYearly", t("the rain this year", "la pioggia di quest’anno"), t("Rain year", "Pioggia anno"), "mm", "droplets")
93379
+ ]
93380
+ }
93381
+ };
92162
93382
  /**
92163
93383
  * Optional client-side hints sent at session creation to help the provider
92164
93384
  * pick the best native source. All fields optional — a viewer that knows
@@ -93724,6 +94944,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
93724
94944
  doorbell: doorbellCapability,
93725
94945
  energySystem: energySystemCapability,
93726
94946
  enumSensor: enumSensorCapability,
94947
+ error: errorCapability,
93727
94948
  eventEmitter: eventEmitterCapability,
93728
94949
  fanControl: fanControlCapability,
93729
94950
  featureProbe: featureProbeCapability,
@@ -93780,10 +95001,59 @@ var DEVICE_LOCAL_STATE_CAPS = {
93780
95001
  vibration: vibrationCapability,
93781
95002
  waterHeater: waterHeaterCapability,
93782
95003
  weather: weatherCapability,
95004
+ weatherStation: weatherStationCapability,
93783
95005
  zoneAnalytics: zoneAnalyticsCapability,
93784
95006
  zoneRules: zoneRulesCapability,
93785
95007
  zones: zonesCapability
93786
95008
  };
95009
+ /**
95010
+ * The edge between two readings, or `null` for no edge.
95011
+ *
95012
+ * - `null`/cold → `true` is a START (a fault already open at boot is one);
95013
+ * - `true` → `false` is a RESOLVE;
95014
+ * - an UNKNOWN reading (`active: null`) is never an edge, in either direction —
95015
+ * a failed read must neither raise nor close an episode (D393, D49);
95016
+ * - `true` → `true` is the same episode, whatever the code did.
95017
+ */
95018
+ function deviceErrorEdge(previous, next) {
95019
+ if (next.active === true && previous?.active !== true) return "start";
95020
+ if (next.active === false && previous?.active === true) return "resolve";
95021
+ return null;
95022
+ }
95023
+ function deviceErrorAlertId(deviceId) {
95024
+ return `device-error:${String(deviceId)}`;
95025
+ }
95026
+ /** The alert that opens an episode. `since` is the fault's own start, else `now`. */
95027
+ function buildDeviceErrorAlert(deviceId, deviceName, status, now) {
95028
+ const label = status.label ?? status.code ?? "Fault";
95029
+ const startedAt = status.since ?? now;
95030
+ const alertId = deviceErrorAlertId(deviceId);
95031
+ return {
95032
+ id: alertId,
95033
+ category: "device.error",
95034
+ severity: "critical",
95035
+ title: `${deviceName}: ${label}`,
95036
+ message: `${deviceName} reports a fault: ${label}.`,
95037
+ action: {
95038
+ label: "Open device",
95039
+ href: `/devices/${String(deviceId)}`,
95040
+ navigation: "app"
95041
+ },
95042
+ status: "active",
95043
+ read: false,
95044
+ createdAt: now,
95045
+ updatedAt: now,
95046
+ source: {
95047
+ type: "device",
95048
+ id: String(deviceId)
95049
+ },
95050
+ metadata: {
95051
+ episodeId: `${alertId}@${String(startedAt)}`,
95052
+ code: status.code,
95053
+ deviceSeverity: status.severity
95054
+ }
95055
+ };
95056
+ }
93787
95057
  var BaseDevice = class {
93788
95058
  id;
93789
95059
  stableId;
@@ -94083,6 +95353,68 @@ var BaseDevice = class {
94083
95353
  } catch (err) {}
94084
95354
  }
94085
95355
  /**
95356
+ * Declare that this device can report a fault (the `error` cap). The slice
95357
+ * starts UNKNOWN — a restored slice is kept, so a fault that was open before
95358
+ * a restart is compared, not announced again.
95359
+ */
95360
+ registerErrorCap() {
95361
+ this.ctx.registerNativeCap?.(errorCapability, {});
95362
+ if (this.runtimeState.getCapState(errorCapability.name) === void 0) this.setCapSlice(errorCapability, ERROR_STATUS_COLD);
95363
+ }
95364
+ /**
95365
+ * Publish the device's fault. The slice is written; on an EDGE (a fault
95366
+ * starts, or one resolves) an attention episode opens or closes through the
95367
+ * alerts cap, which the notification center announces as
95368
+ * `device-attention-required` / `-resolved`. An unknown reading is never an
95369
+ * edge. No rule exists by default: it notifies when the operator makes one.
95370
+ */
95371
+ async publishError(reading) {
95372
+ const previous = this.getCapSlice(errorCapability);
95373
+ if (reading.active === null && previous !== null && previous.active !== null) return;
95374
+ const now = Date.now();
95375
+ const edge = deviceErrorEdge(previous, reading);
95376
+ const next = edge === "start" && reading.since === null ? {
95377
+ ...reading,
95378
+ since: now
95379
+ } : reading;
95380
+ this.setCapSlice(errorCapability, next);
95381
+ if (edge === null) return;
95382
+ const alerts = this.ctx.api.alerts;
95383
+ if (alerts === void 0) {
95384
+ this.ctx.logger.warn("device error: attention event skipped — alerts capability missing", {
95385
+ tags: { deviceId: this.id },
95386
+ meta: { edge }
95387
+ });
95388
+ return;
95389
+ }
95390
+ try {
95391
+ if (edge === "start") await alerts.emit.mutate(buildDeviceErrorAlert(this.id, this.name, next, now));
95392
+ else await alerts.update.mutate({
95393
+ alertId: deviceErrorAlertId(this.id),
95394
+ patch: {
95395
+ status: "completed",
95396
+ read: true,
95397
+ updatedAt: now
95398
+ }
95399
+ });
95400
+ this.ctx.logger.info(`device error: ${edge === "start" ? "raised" : "resolved"}`, {
95401
+ tags: { deviceId: this.id },
95402
+ meta: {
95403
+ code: next.code,
95404
+ label: next.label
95405
+ }
95406
+ });
95407
+ } catch (err) {
95408
+ this.ctx.logger.warn("device error: attention event failed", {
95409
+ tags: { deviceId: this.id },
95410
+ meta: {
95411
+ edge,
95412
+ error: errMsg(err)
95413
+ }
95414
+ });
95415
+ }
95416
+ }
95417
+ /**
94086
95418
  * Typed read-through to a cap-keyed runtime-state slice. Drivers
94087
95419
  * call `this.getCapSlice(batteryCapability)` and the return type
94088
95420
  * is inferred from the cap's `runtimeState` Zod schema — no string
@@ -95030,7 +96362,7 @@ var BaseDeviceProvider = class extends BaseAddon {
95030
96362
  }
95031
96363
  };
95032
96364
  DeviceType.HeatPump;
95033
- DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
96365
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Camera, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
95034
96366
  new Set(Object.values(DeviceType));
95035
96367
  /** Parses the raw slice with the cap's own schema; `undefined` when it does not parse. */
95036
96368
  function contribution(cap, schema, build) {
@@ -96399,6 +97731,17 @@ var SENSORS_FAMILY_CONTRIBUTIONS = {
96399
97731
  doorbell: doorbellContribution,
96400
97732
  meter: meterContribution,
96401
97733
  weather: weatherContribution,
97734
+ "weather-station": contribution("weather-station", WeatherStationStatusSchema, (slice) => {
97735
+ if (slice === void 0 || slice.temperature === null) return { reading: reading("weather", "unknown") };
97736
+ const celsius = `${number(slice.temperature)}°C`;
97737
+ const facts = [fact("reading", "thermometer", t(celsius, celsius), "primary")];
97738
+ if (slice.humidity !== null) facts.push(fact("reading", "droplets", t(`${Math.round(slice.humidity)}%`, `${Math.round(slice.humidity)}%`)));
97739
+ if (slice.windSpeed !== null) facts.push(fact("reading", "wind", t(`${number(slice.windSpeed)} m/s`, `${number(slice.windSpeed)} m/s`)));
97740
+ return {
97741
+ reading: reading("weather", "on", false, t(celsius, celsius)),
97742
+ facts
97743
+ };
97744
+ }),
96402
97745
  update: updateContribution,
96403
97746
  image: imageContribution,
96404
97747
  button: buttonContribution,
@@ -97075,7 +98418,7 @@ var HEAT_PUMP_FAMILY_CONTRIBUTIONS = { "heat-pump-status": contribution("heat-pu
97075
98418
  ...Object.values(CLIMATE_FAMILY_CONTRIBUTIONS),
97076
98419
  ...Object.values(HEAT_PUMP_FAMILY_CONTRIBUTIONS)
97077
98420
  ];
97078
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
98421
+ DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
97079
98422
  /**
97080
98423
  * Who looks like what (D1023). One look per {@link DeviceType}, refined by a
97081
98424
  * {@link DeviceRole} where the role changes how the device reads (a leak
@@ -97719,7 +99062,7 @@ lookOf({
97719
99062
  standby: MUTED
97720
99063
  });
97721
99064
  DeviceType.Climate;
97722
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
99065
+ DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
97723
99066
  DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.SwitchHeater, DeviceRole.SwitchFan, DeviceRole.SwitchLight, DeviceRole.ClimateAc, DeviceRole.ClimateThermostat, DeviceRole.ClimateFloor, DeviceRole.ClimateRadiator, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
97724
99067
  Object.freeze({
97725
99068
  "accessories.getStatus": {
@@ -99024,6 +100367,13 @@ Object.freeze({
99024
100367
  access: "view",
99025
100368
  role: "viewer"
99026
100369
  },
100370
+ "control.first": {
100371
+ capName: "control",
100372
+ capScope: "device",
100373
+ addonId: null,
100374
+ access: "create",
100375
+ role: "viewer"
100376
+ },
99027
100377
  "control.getStatus": {
99028
100378
  capName: "control",
99029
100379
  capScope: "device",
@@ -99031,6 +100381,27 @@ Object.freeze({
99031
100381
  access: "view",
99032
100382
  role: "viewer"
99033
100383
  },
100384
+ "control.last": {
100385
+ capName: "control",
100386
+ capScope: "device",
100387
+ addonId: null,
100388
+ access: "create",
100389
+ role: "viewer"
100390
+ },
100391
+ "control.next": {
100392
+ capName: "control",
100393
+ capScope: "device",
100394
+ addonId: null,
100395
+ access: "create",
100396
+ role: "viewer"
100397
+ },
100398
+ "control.previous": {
100399
+ capName: "control",
100400
+ capScope: "device",
100401
+ addonId: null,
100402
+ access: "create",
100403
+ role: "viewer"
100404
+ },
99034
100405
  "control.setValue": {
99035
100406
  capName: "control",
99036
100407
  capScope: "device",
@@ -100984,6 +102355,13 @@ Object.freeze({
100984
102355
  access: "view",
100985
102356
  role: "viewer"
100986
102357
  },
102358
+ "error.getStatus": {
102359
+ capName: "error",
102360
+ capScope: "device",
102361
+ addonId: null,
102362
+ access: "view",
102363
+ role: "viewer"
102364
+ },
100987
102365
  "eventEmitter.getStatus": {
100988
102366
  capName: "event-emitter",
100989
102367
  capScope: "device",
@@ -101068,6 +102446,13 @@ Object.freeze({
101068
102446
  access: "view",
101069
102447
  role: "feature:face-assignment"
101070
102448
  },
102449
+ "faceGallery.moveIdentitySample": {
102450
+ capName: "face-gallery",
102451
+ capScope: "system",
102452
+ addonId: null,
102453
+ access: "create",
102454
+ role: "feature:face-assignment"
102455
+ },
101071
102456
  "faceGallery.removeSample": {
101072
102457
  capName: "face-gallery",
101073
102458
  capScope: "system",
@@ -104988,6 +106373,13 @@ Object.freeze({
104988
106373
  access: "view",
104989
106374
  role: "feature:plate-assignment"
104990
106375
  },
106376
+ "plateGallery.moveVehicleSample": {
106377
+ capName: "plate-gallery",
106378
+ capScope: "system",
106379
+ addonId: null,
106380
+ access: "create",
106381
+ role: "feature:plate-assignment"
106382
+ },
104991
106383
  "plateGallery.removeVehicleSample": {
104992
106384
  capName: "plate-gallery",
104993
106385
  capScope: "system",
@@ -107242,6 +108634,13 @@ Object.freeze({
107242
108634
  access: "create",
107243
108635
  role: "admin"
107244
108636
  },
108637
+ "terminalSession.serverShellStatus": {
108638
+ capName: "terminal-session",
108639
+ capScope: "system",
108640
+ addonId: null,
108641
+ access: "view",
108642
+ role: "admin"
108643
+ },
107245
108644
  "terminalSession.writeInput": {
107246
108645
  capName: "terminal-session",
107247
108646
  capScope: "system",
@@ -107977,6 +109376,13 @@ Object.freeze({
107977
109376
  access: "view",
107978
109377
  role: "viewer"
107979
109378
  },
109379
+ "weatherStation.getStatus": {
109380
+ capName: "weather-station",
109381
+ capScope: "device",
109382
+ addonId: null,
109383
+ access: "view",
109384
+ role: "viewer"
109385
+ },
107980
109386
  "webrtcSession.addIceCandidate": {
107981
109387
  capName: "webrtc-session",
107982
109388
  capScope: "device",
@@ -108190,7 +109596,7 @@ function offered(received) {
108190
109596
  };
108191
109597
  }
108192
109598
  offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
108193
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
109599
+ DeviceType.Camera, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.HeatPump, DeviceType.EnergySystem, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.WeatherStation, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
108194
109600
  DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.SwitchHeater, DeviceRole.SwitchFan, DeviceRole.SwitchLight, DeviceRole.ClimateAc, DeviceRole.ClimateThermostat, DeviceRole.ClimateFloor, DeviceRole.ClimateRadiator, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
108195
109601
  new Set(Object.values(DeviceType));
108196
109602
  new Set(Object.values(DeviceRole));
@@ -108529,11 +109935,31 @@ Object.freeze({
108529
109935
  form: "single",
108530
109936
  optional: false
108531
109937
  }],
109938
+ "control.first": [{
109939
+ name: "deviceId",
109940
+ form: "single",
109941
+ optional: false
109942
+ }],
108532
109943
  "control.getStatus": [{
108533
109944
  name: "deviceId",
108534
109945
  form: "single",
108535
109946
  optional: false
108536
109947
  }],
109948
+ "control.last": [{
109949
+ name: "deviceId",
109950
+ form: "single",
109951
+ optional: false
109952
+ }],
109953
+ "control.next": [{
109954
+ name: "deviceId",
109955
+ form: "single",
109956
+ optional: false
109957
+ }],
109958
+ "control.previous": [{
109959
+ name: "deviceId",
109960
+ form: "single",
109961
+ optional: false
109962
+ }],
108537
109963
  "control.setValue": [{
108538
109964
  name: "deviceId",
108539
109965
  form: "single",
@@ -109158,6 +110584,11 @@ Object.freeze({
109158
110584
  form: "single",
109159
110585
  optional: false
109160
110586
  }],
110587
+ "error.getStatus": [{
110588
+ name: "deviceId",
110589
+ form: "single",
110590
+ optional: false
110591
+ }],
109161
110592
  "eventEmitter.getStatus": [{
109162
110593
  name: "deviceId",
109163
110594
  form: "single",
@@ -111199,6 +112630,11 @@ Object.freeze({
111199
112630
  form: "single",
111200
112631
  optional: false
111201
112632
  }],
112633
+ "weatherStation.getStatus": [{
112634
+ name: "deviceId",
112635
+ form: "single",
112636
+ optional: false
112637
+ }],
111202
112638
  "webrtcSession.addIceCandidate": [{
111203
112639
  name: "deviceId",
111204
112640
  form: "single",
@@ -111435,6 +112871,8 @@ new Map(Object.entries({
111435
112871
  "storage-location-low": "system",
111436
112872
  "storage-location-critical": "system",
111437
112873
  "storage-location-ok": "system",
112874
+ "storage-drain-progress": "system",
112875
+ "storage-drain-complete": "system",
111438
112876
  "alarm-triggered": "alarm",
111439
112877
  "alarm-armed": "alarm",
111440
112878
  "alarm-disarmed": "alarm",
@@ -111570,6 +113008,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
111570
113008
  "storage-location-ok"
111571
113009
  ]
111572
113010
  },
113011
+ {
113012
+ id: "storage-drain",
113013
+ family: "system",
113014
+ label: "Storage drain progress",
113015
+ kinds: ["storage-drain-progress", "storage-drain-complete"]
113016
+ },
111573
113017
  {
111574
113018
  id: "alerts",
111575
113019
  family: "system",
@@ -111764,6 +113208,7 @@ var ALERT_EPISODE = [
111764
113208
  "alert-error",
111765
113209
  "alert-critical-resolved"
111766
113210
  ];
113211
+ var STORAGE_DRAIN = ["storage-drain-progress", "storage-drain-complete"];
111767
113212
  var DEVICE_ATTENTION = ["device-attention-required", "device-attention-resolved"];
111768
113213
  var UPDATES = [
111769
113214
  "addon-update-available",
@@ -111975,6 +113420,7 @@ var STATE_SUMMARY_VARS = [
111975
113420
  [...RULE_TEXT],
111976
113421
  [...RULE_TEXT],
111977
113422
  [...RULE_TEXT],
113423
+ [...RULE_TEXT],
111978
113424
  [...RULE_BODY_ONLY],
111979
113425
  [...RULE_TEXT],
111980
113426
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
@@ -111987,8 +113433,16 @@ var STATE_SUMMARY_VARS = [
111987
113433
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
111988
113434
  ([...RULE_TEXT], [...CONSUMABLE]),
111989
113435
  [...RULE_TEXT],
111990
- ([...RULE_TEXT], [...UPDATES, ...ALERT_EPISODE]),
111991
- ([...RULE_TEXT], [...UPDATES, ...ALERT_EPISODE]),
113436
+ ([...RULE_TEXT], [
113437
+ ...UPDATES,
113438
+ ...STORAGE_DRAIN,
113439
+ ...ALERT_EPISODE
113440
+ ]),
113441
+ ([...RULE_TEXT], [
113442
+ ...UPDATES,
113443
+ ...STORAGE_DRAIN,
113444
+ ...ALERT_EPISODE
113445
+ ]),
111992
113446
  [...RULE_TEXT],
111993
113447
  [...RULE_TEXT],
111994
113448
  [...RULE_TEXT],
@@ -112002,6 +113456,9 @@ var STATE_SUMMARY_VARS = [
112002
113456
  [...RULE_TEXT],
112003
113457
  [...RULE_TEXT],
112004
113458
  [...RULE_TEXT],
113459
+ ([...RULE_TEXT], [...STORAGE_DRAIN]),
113460
+ ([...RULE_TEXT], [...STORAGE_DRAIN]),
113461
+ [...RULE_TEXT],
112005
113462
  [...RULE_TEXT],
112006
113463
  [...RULE_TEXT],
112007
113464
  [...RULE_TEXT],
@@ -113777,6 +115234,17 @@ function present(part) {
113777
115234
  return Object.values(part).some((v) => v !== null) ? part : null;
113778
115235
  }
113779
115236
  /**
115237
+ * A house reading without the heat pump, from Enpal's total that includes it. Both known:
115238
+ * the difference, floored at 0. No pump in the system: the total as reported. A pump that
115239
+ * exists but whose reading is unknown makes the result unknown, never the inflated total.
115240
+ */
115241
+ function withoutPump(total, pump, hasPump) {
115242
+ if (total === null) return null;
115243
+ if (!hasPump) return total;
115244
+ if (pump === null) return null;
115245
+ return Math.max(0, total - pump);
115246
+ }
115247
+ /**
113780
115248
  * The grid power, + import / − export, from the two halves the catalog splits it into.
113781
115249
  * With both halves known it is their difference. With one half only, it is that half
113782
115250
  * while it flows: a zero import with no export reading cannot say whether the house
@@ -113806,16 +115274,17 @@ function computeEnergySystemStatus(snapshot, options, now) {
113806
115274
  importTodayKwh: read.numeric("grid-import-day"),
113807
115275
  exportTodayKwh: read.numeric("grid-export-day")
113808
115276
  };
113809
- const house = {
113810
- powerW: read.power("house"),
113811
- energyKwh: read.energy("house"),
113812
- todayKwh: read.numeric("house-energy-day")
113813
- };
113814
115277
  const heatPump = {
113815
115278
  powerW: read.power("heatpump"),
113816
115279
  energyKwh: read.energy("heatpump"),
113817
115280
  todayKwh: null
113818
115281
  };
115282
+ const hasPump = present(heatPump) !== null;
115283
+ const house = {
115284
+ powerW: withoutPump(read.power("house"), heatPump.powerW, hasPump),
115285
+ energyKwh: withoutPump(read.energy("house"), heatPump.energyKwh, hasPump),
115286
+ todayKwh: hasPump ? null : read.numeric("house-energy-day")
115287
+ };
113819
115288
  const storage = {
113820
115289
  stateOfChargePct: read.numeric("battery"),
113821
115290
  storedKwh: read.numeric("battery-stored-energy"),
@@ -114218,6 +115687,53 @@ function accessoryChildSpecs(config) {
114218
115687
  return out;
114219
115688
  }
114220
115689
  //#endregion
115690
+ //#region src/devices/enpal-catalog-rename.ts
115691
+ /**
115692
+ * House and heat-pump meters used to be created as "Consumption". A restore
115693
+ * may replace that default; a name the operator chose stays.
115694
+ */
115695
+ /** Keep unless the stored name is still the old catalogue default for this meter. */
115696
+ function catalogNameWrite(input) {
115697
+ const spec = findSpec(input.specId);
115698
+ if (spec === void 0) return { kind: "keep" };
115699
+ if (input.specId !== "house" && input.specId !== "heatpump") return { kind: "keep" };
115700
+ if (input.name === spec.name) return { kind: "keep" };
115701
+ if (input.name !== "Consumption") return { kind: "keep" };
115702
+ return {
115703
+ kind: "rename",
115704
+ name: spec.name,
115705
+ stampUserNamed: input.userNamed === true
115706
+ };
115707
+ }
115708
+ /** Rename each child that still carries the old default. Logs every rename. */
115709
+ async function renameLegacyCatalogDevices(input) {
115710
+ for (const child of input.children) {
115711
+ const decision = catalogNameWrite(child);
115712
+ if (decision.kind === "keep") continue;
115713
+ try {
115714
+ await input.rename(child.deviceId, decision.name, decision.stampUserNamed);
115715
+ } catch (err) {
115716
+ input.logger.warn("enpal: could not rename a device still carrying the old catalogue name", {
115717
+ tags: { deviceId: child.deviceId },
115718
+ meta: {
115719
+ from: child.name,
115720
+ to: decision.name,
115721
+ error: errMsg(err)
115722
+ }
115723
+ });
115724
+ continue;
115725
+ }
115726
+ input.logger.info("enpal: renamed a device that still had the old catalogue name", {
115727
+ tags: { deviceId: child.deviceId },
115728
+ meta: {
115729
+ specId: child.specId,
115730
+ from: child.name,
115731
+ to: decision.name
115732
+ }
115733
+ });
115734
+ }
115735
+ }
115736
+ //#endregion
114221
115737
  //#region src/devices/enpal-metadata-sync.ts
114222
115738
  /**
114223
115739
  * Writes the identity facts a piece reported (model, serial, firmware) as device attributes.
@@ -114333,6 +115849,7 @@ var EnpalEquipmentDevice = class extends BaseDevice {
114333
115849
  },
114334
115850
  logger: this.ctx.logger
114335
115851
  });
115852
+ await this.renameLegacyNames();
114336
115853
  const metadata = new EnpalMetadataSync({
114337
115854
  equipment: this.equipment,
114338
115855
  deviceId: this.id,
@@ -114350,6 +115867,45 @@ var EnpalEquipmentDevice = class extends BaseDevice {
114350
115867
  });
114351
115868
  await metadata.apply();
114352
115869
  }
115870
+ /**
115871
+ * A meter created as "Consumption" takes the current catalogue name. A name
115872
+ * the operator changed is left as it is.
115873
+ */
115874
+ async renameLegacyNames() {
115875
+ const manager = this.ctx.api.deviceManager;
115876
+ try {
115877
+ const legacy = (await manager.getChildren.query({ parentDeviceId: this.id })).flatMap((child) => {
115878
+ const specId = child.config["specId"];
115879
+ if (typeof specId !== "string" || child.name !== "Consumption") return [];
115880
+ return [{
115881
+ deviceId: child.id,
115882
+ specId,
115883
+ name: child.name
115884
+ }];
115885
+ });
115886
+ await renameLegacyCatalogDevices({
115887
+ children: await Promise.all(legacy.map(async (child) => {
115888
+ const meta = await manager.loadMeta.query({ deviceId: child.deviceId });
115889
+ return {
115890
+ ...child,
115891
+ name: meta?.name ?? child.name,
115892
+ userNamed: meta?.userNamed
115893
+ };
115894
+ })),
115895
+ rename: (deviceId, name, stampUserNamed) => manager.setName.mutate({
115896
+ deviceId,
115897
+ name,
115898
+ ...stampUserNamed ? {} : { stampUserNamed: false }
115899
+ }),
115900
+ logger: this.ctx.logger
115901
+ });
115902
+ } catch (err) {
115903
+ this.ctx.logger.warn("enpal: could not rename devices still carrying the old catalogue name", {
115904
+ tags: { deviceId: this.id },
115905
+ meta: { error: errMsg(err) }
115906
+ });
115907
+ }
115908
+ }
114353
115909
  async removeDevice() {
114354
115910
  this.snapshotUnsub?.();
114355
115911
  this.snapshotUnsub = null;