@enyo-energy/energy-app-sdk 1.42.0 → 1.44.0

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.
Files changed (68) hide show
  1. package/README.md +76 -1
  2. package/dist/cjs/energy-app-permission.type.cjs +2 -0
  3. package/dist/cjs/energy-app-permission.type.d.cts +3 -1
  4. package/dist/cjs/energy-app.cjs +26 -0
  5. package/dist/cjs/energy-app.d.cts +24 -0
  6. package/dist/cjs/enyo-energy-app-sdk.d.cts +6 -0
  7. package/dist/cjs/implementations/appliances/appliance-manager.cjs +56 -0
  8. package/dist/cjs/implementations/appliances/appliance-manager.d.cts +34 -1
  9. package/dist/cjs/implementations/heatpump/heatpump-control-validators.cjs +61 -0
  10. package/dist/cjs/implementations/heatpump/heatpump-control-validators.d.cts +38 -1
  11. package/dist/cjs/implementations/heatpump/heatpump-curve-optimization-rooms.cjs +96 -0
  12. package/dist/cjs/implementations/heatpump/heatpump-curve-optimization-rooms.d.cts +65 -0
  13. package/dist/cjs/implementations/heatpump/heatpump-heating-curve-offset.cjs +119 -0
  14. package/dist/cjs/implementations/heatpump/heatpump-heating-curve-offset.d.cts +77 -0
  15. package/dist/cjs/implementations/heatpump/heatpump-metadata-validators.cjs +52 -0
  16. package/dist/cjs/implementations/heatpump/heatpump-metadata-validators.d.cts +3 -0
  17. package/dist/cjs/index.cjs +6 -0
  18. package/dist/cjs/index.d.cts +6 -0
  19. package/dist/cjs/integrations/heatpump-integration-energy-app.cjs +89 -0
  20. package/dist/cjs/integrations/heatpump-integration-energy-app.d.cts +75 -1
  21. package/dist/cjs/packages/energy-app-room-temperatures.cjs +2 -0
  22. package/dist/cjs/packages/energy-app-room-temperatures.d.cts +51 -0
  23. package/dist/cjs/packages/energy-app-room.cjs +2 -0
  24. package/dist/cjs/packages/energy-app-room.d.cts +59 -0
  25. package/dist/cjs/types/enyo-data-bus-value.cjs +2 -0
  26. package/dist/cjs/types/enyo-data-bus-value.d.cts +60 -0
  27. package/dist/cjs/types/enyo-heatpump-appliance.cjs +28 -1
  28. package/dist/cjs/types/enyo-heatpump-appliance.d.cts +124 -1
  29. package/dist/cjs/types/enyo-room-temperature.cjs +2 -0
  30. package/dist/cjs/types/enyo-room-temperature.d.cts +102 -0
  31. package/dist/cjs/types/enyo-room.cjs +37 -0
  32. package/dist/cjs/types/enyo-room.d.cts +60 -0
  33. package/dist/cjs/version.cjs +1 -1
  34. package/dist/cjs/version.d.cts +1 -1
  35. package/dist/energy-app-permission.type.d.ts +3 -1
  36. package/dist/energy-app-permission.type.js +2 -0
  37. package/dist/energy-app.d.ts +24 -0
  38. package/dist/energy-app.js +26 -0
  39. package/dist/enyo-energy-app-sdk.d.ts +6 -0
  40. package/dist/implementations/appliances/appliance-manager.d.ts +34 -1
  41. package/dist/implementations/appliances/appliance-manager.js +56 -0
  42. package/dist/implementations/heatpump/heatpump-control-validators.d.ts +38 -1
  43. package/dist/implementations/heatpump/heatpump-control-validators.js +60 -0
  44. package/dist/implementations/heatpump/heatpump-curve-optimization-rooms.d.ts +65 -0
  45. package/dist/implementations/heatpump/heatpump-curve-optimization-rooms.js +89 -0
  46. package/dist/implementations/heatpump/heatpump-heating-curve-offset.d.ts +77 -0
  47. package/dist/implementations/heatpump/heatpump-heating-curve-offset.js +113 -0
  48. package/dist/implementations/heatpump/heatpump-metadata-validators.d.ts +3 -0
  49. package/dist/implementations/heatpump/heatpump-metadata-validators.js +52 -0
  50. package/dist/index.d.ts +6 -0
  51. package/dist/index.js +6 -0
  52. package/dist/integrations/heatpump-integration-energy-app.d.ts +75 -1
  53. package/dist/integrations/heatpump-integration-energy-app.js +90 -1
  54. package/dist/packages/energy-app-room-temperatures.d.ts +51 -0
  55. package/dist/packages/energy-app-room-temperatures.js +1 -0
  56. package/dist/packages/energy-app-room.d.ts +59 -0
  57. package/dist/packages/energy-app-room.js +1 -0
  58. package/dist/types/enyo-data-bus-value.d.ts +60 -0
  59. package/dist/types/enyo-data-bus-value.js +2 -0
  60. package/dist/types/enyo-heatpump-appliance.d.ts +124 -1
  61. package/dist/types/enyo-heatpump-appliance.js +27 -0
  62. package/dist/types/enyo-room-temperature.d.ts +102 -0
  63. package/dist/types/enyo-room-temperature.js +1 -0
  64. package/dist/types/enyo-room.d.ts +60 -0
  65. package/dist/types/enyo-room.js +34 -0
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/package.json +1 -1
package/README.md CHANGED
@@ -409,6 +409,8 @@ Energy Apps use a granular permissions system to control access to system resour
409
409
  - **`GridFeeRegister`** / **`GridFeeUse`**: Publish / resolve dynamic grid fees (time-variable network charges)
410
410
  - **`CommandLog`**: Record and read back the commands apps issued to appliances
411
411
  - **`Subscriptions`**: Read the subscriptions bought for the app on this device
412
+ - **`Room`**: Read the rooms of the home the customer set up in the enyo app
413
+ - **`RoomTemperatures`**: Read the current and past temperatures measured in those rooms
412
414
 
413
415
  #### Site & Identity Permissions
414
416
 
@@ -1768,6 +1770,37 @@ const full = await location.getLocation(); // requires LocationC
1768
1770
  if (full) console.log(`lat=${full.latitude} lon=${full.longitude}`);
1769
1771
  ```
1770
1772
 
1773
+ #### `useRoom(): EnergyAppRoom`
1774
+
1775
+ Read the rooms of the home, as the customer set them up in the enyo app: name, the appliances in each, the temperature the customer wants there (`targetTemperatureC`, optional — use `HEATPUMP_CURVE_OPTIMIZATION_DEFAULT_TARGET_C` = 21 °C until set) and, for the heating curve optimisation, the room's `warmth` (`EnyoRoomWarmthEnum`) and `thermostat` (`EnyoRoomThermostatEnum`). Read-only.
1776
+
1777
+ ```typescript
1778
+ const rooms = energyApp.useRoom();
1779
+
1780
+ applyRooms(await rooms.getRooms());
1781
+ const circuitRooms = await rooms.getRoomsById(circuit.curveOptimization?.roomIds ?? []);
1782
+ rooms.listenForRoomsChanged((all) => applyRooms(all));
1783
+ ```
1784
+
1785
+ Requires the `Room` permission.
1786
+
1787
+ #### `useRoomTemperatures(): EnergyAppRoomTemperatures`
1788
+
1789
+ Read the temperatures measured in the rooms, per sensor — a room can have several. `getCurrentRoomTemperatures(roomIds)` returns the latest reading of each sensor, `getRoomTemperatureHistory({roomIds, startDateIso, endDateIso, resolution})` the past ones in `'1m'` or `'15m'` buckets (average, min, max). There is no listener: follow the sensors named in the current values (`applianceId`, `sensorId`) via `TemperatureSensorValuesUpdateV1` on the data bus.
1790
+
1791
+ ```typescript
1792
+ const roomTemperatures = energyApp.useRoomTemperatures();
1793
+
1794
+ const current = await roomTemperatures.getCurrentRoomTemperatures(['living', 'bath']);
1795
+ const history = await roomTemperatures.getRoomTemperatureHistory({
1796
+ roomIds: ['living', 'bath'],
1797
+ startDateIso: '2026-10-27T00:00:00Z',
1798
+ endDateIso: '2026-10-28T00:00:00Z',
1799
+ });
1800
+ ```
1801
+
1802
+ Requires the `RoomTemperatures` permission.
1803
+
1771
1804
  #### `useGridConnectionPoint(): EnergyAppGridConnectionPoint`
1772
1805
 
1773
1806
  Read the site's grid connection details — main fuse rating, number of phases, the maximum allowed grid power, and the total power that load balancing may allocate to EV chargers. Use this to size dispatch envelopes and avoid violating the contractual cap.
@@ -3666,12 +3699,13 @@ Drives a heatpump. Manages building / DHW overheating commands and grid-power-av
3666
3699
 
3667
3700
  > **Preferred: control heatpumps with the V3 messages.** `HeatpumpControlV3` (power, power sources, which zones to heat now, until when), `HeatpumpControlCancelV3` (cancel a session) and `HeatpumpControlStatusV3` (progress) are the preferred way to control a heatpump — for energy managers sending commands and for integrations receiving them. Use them for all new work. `HeatpumpOverheatingV1`, `SetHeatpumpAvailablePowerV2` and `HeatpumpAvailablePowerAnnouncementV1` are deprecated and only remain for heatpumps that do not declare `ControlV3`. See [HeatpumpControlV3](#heatpumpcontrolv3). Likewise, a heatpump offers its flexibility with `HeatpumpFlexibilityAnnouncementV3` rather than the generic `ApplianceFlexibilityAnnouncementV1` / `V2` — see [HeatpumpFlexibilityAnnouncementV3](#heatpumpflexibilityannouncementv3).
3668
3701
 
3669
- - **Subscribed commands:** `HeatpumpControlV3`, `HeatpumpControlCancelV3`, `HeatpumpProgramSwitchV3`, `HeatpumpOverheatingV1` (deprecated), `HeatpumpAvailablePowerAnnouncementV1` (deprecated), `SetHeatpumpAvailablePowerV2` (deprecated), `SetHeatpumpRoomTemperatureV1`, `GridOperatorPowerLimitationV1` (broadcast)
3702
+ - **Subscribed commands:** `HeatpumpControlV3`, `HeatpumpControlCancelV3`, `HeatpumpProgramSwitchV3`, `SetHeatpumpHeatingCurveOffsetV1`, `HeatpumpOverheatingV1` (deprecated), `HeatpumpAvailablePowerAnnouncementV1` (deprecated), `SetHeatpumpAvailablePowerV2` (deprecated), `SetHeatpumpRoomTemperatureV1`, `GridOperatorPowerLimitationV1` (broadcast)
3670
3703
  - **Implement:** `handleHeatpumpOverheating`, `handleHeatpumpAvailablePowerAnnouncement`, `handleSetHeatpumpAvailablePower`, `handleGridOperatorPowerLimitation`
3671
3704
  - **Optional overrides:**
3672
3705
  - `handleSetHeatpumpRoomTemperature` — write a measured room temperature into a heating circuit. Answers `NotSupported` by default; override it when the heatpump declares `RoomTemperatureInput`.
3673
3706
  - `handleHeatpumpControl` — support `HeatpumpControlV3` (see below). Answers `NotSupported` by default; override it and declare `ControlV3`.
3674
3707
  - `handleHeatpumpProgramSwitch(request)` — switch a zone to comfort or eco, or back to its time program (see [HeatpumpProgramSwitchV3](#heatpumpprogramswitchv3)). Answers `NotSupported` by default; override it and declare `ProgramSwitch`.
3708
+ - `handleHeatpumpHeatingCurveOffset(message)` — shift a heating circuit's curve permanently, relative to its initial curve (see [SetHeatpumpHeatingCurveOffsetV1](#setheatpumpheatingcurveoffsetv1)). Answers `NotSupported` by default; override it and declare `HeatingCurveOffset`.
3675
3709
  - `handleHeatpumpControlCancel` — withdraw a cancelled session on the device. By default `Accepted` for heatpumps declaring `ControlV3`, `NotSupported` otherwise.
3676
3710
  - `onHeatpumpControlEnded(applianceId, cause)` — return the heatpump to its own behaviour when a session ends; `cause` is `HeatpumpControlEndCauseEnum.Expired`, `Ended` or `Cancelled`. Does nothing by default.
3677
3711
  - `resolveHeatpumpMetadata(applianceId)` — the metadata actions are validated against. Defaults to the stored appliance's `heatpump` metadata.
@@ -3732,6 +3766,44 @@ Switches one heating circuit or DHW zone to its **comfort** or **eco** setpoint
3732
3766
  - **Owner's consent for price-driven eco.** An energy manager switches zones to eco for price reasons only while the general setting `heatpumpEcoModeInExpensiveTimes` (and `heatpumpControl`) is `true` — see [General settings](#general-settings).
3733
3767
  - **Validation** (run by the base class before the handler, or `validateHeatpumpProgramSwitch(data, metadata)` directly): target `Room` / `Dhw`, a known mode, a valid `untilIso` for comfort / eco (`Rejected`); `ProgramSwitch` declared and the mode listed for the zone (`NotSupported`); a known zone index (`Rejected`).
3734
3768
 
3769
+ #### SetHeatpumpHeatingCurveOffsetV1
3770
+
3771
+ Shifts the heating curve of one heating circuit in parallel — **permanently**, until the next message. The energy manager uses it to lower the curve as far as the measured room temperature allows (less flow temperature, roughly 2.5 % less heating electricity per K).
3772
+
3773
+ ```typescript
3774
+ {
3775
+ heatingCircuitIndex: 0,
3776
+ offsetK: -1.5, // K of flow temperature, relative to the initial curve; 0 restores it
3777
+ }
3778
+ ```
3779
+
3780
+ - **Relative to the initial curve, never to the current one.** Before the first offset other than `0`, the integration stores the curve set on the device as `heatingCircuits[].initialHeatingCurve` (`ApplianceManager.rememberHeatpumpInitialHeatingCurve` keeps an already stored one). The same message may therefore arrive twice.
3781
+ - **`offsetK: 0` restores** the initial curve exactly, including vendor parameters, then clears it (`ApplianceManager.clearHeatpumpInitialHeatingCurve`, which also sets `activeHeatingCurveOffsetK` to `0`).
3782
+ - **No expiry.** If the hub is offline nobody could reset it anyway; the stored initial curve is the safety net.
3783
+ - **Vendor units.** Convert to whatever the device shifts in (K of room temperature, steps, …) and report the effective shift in K of flow temperature as `heatingCircuits[].activeHeatingCurveOffsetK`, read back from the device. The energy manager waits for it before the next change.
3784
+ - **Changed by hand.** If the curve read from the device differs from `initialHeatingCurve` shifted by `activeHeatingCurveOffsetK` by more than 0.5 K at any point (`isHeatingCurveManuallyChanged(initial, offsetK, read)`), store the read curve as the new `initialHeatingCurve`, set `activeHeatingCurveOffsetK` to `0` and log it in a plain sentence.
3785
+ - **Alongside `HeatpumpControlV3` and `HeatpumpProgramSwitchV3`.** Boost and eco act on top; the offset is the new base.
3786
+ - **Validation** (run by the base class before the handler via `checkHeatingCurveOffset`, or `validateHeatpumpHeatingCurveOffset(data, metadata)` directly): a valid index and finite `offsetK` (`Rejected`); `HeatingCurveOffset` declared (`NotSupported`); a known circuit and — unless `offsetK` is `0` — within `heatingCurveOffsetRangeK` (`Rejected`).
3787
+ - **Customer settings.** Whether to optimise at all, and which rooms of the home the circuit heats (`roomIds`, whether or not a room has a sensor), live in `heatingCircuits[].curveOptimization`. The rooms themselves come from `useRoom().getRoomsById(roomIds)`, their measured temperatures from `useRoomTemperatures()`; `curveOptimizationRooms(curveOptimization, rooms)` picks the ones the curve is judged by (the warmest rooms behind a manual thermostat, and every room with a self-regulating thermostat or none). A room at its target means the curve is just right when its thermostat is open, but only a room falling below its target shows the curve went too far behind a self-regulating one. Rooms without a sensor are left out; the room furthest below its target counts, and the curve is lowered by at most `HEATPUMP_CURVE_OPTIMIZATION_MAX_LOWERING_K` (3 K). Integrations only read them.
3788
+
3789
+ ```typescript
3790
+ protected async handleHeatpumpHeatingCurveOffset(message: EnyoDataBusSetHeatpumpHeatingCurveOffsetV1) {
3791
+ const {heatingCircuitIndex: index, offsetK} = message.data;
3792
+ if (offsetK !== 0) {
3793
+ await this.appliances.rememberHeatpumpInitialHeatingCurve(message.applianceId, index, await this.readCurve(index));
3794
+ }
3795
+ await this.device.writeCurveShift(index, offsetK);
3796
+ if (offsetK === 0) {
3797
+ await this.appliances.clearHeatpumpInitialHeatingCurve(message.applianceId, index);
3798
+ } else {
3799
+ await this.appliances.updateHeatpumpHeatingCircuit(message.applianceId, index, {
3800
+ activeHeatingCurveOffsetK: await this.readEffectiveShiftK(index),
3801
+ });
3802
+ }
3803
+ return {answer: EnyoCommandAcknowledgeAnswerEnum.Accepted};
3804
+ }
3805
+ ```
3806
+
3735
3807
  #### HeatpumpFlexibilityAnnouncementV3
3736
3808
 
3737
3809
  The heatpump's offer, in the vocabulary of `HeatpumpControlV3`: per zone (`target` + `index`), how far it can be raised right now (`maxDeltaK` above `setpointC`, room in Kelvin of room temperature) and the smallest raise that actually starts it (`minDeltaK`, e.g. the remaining hysteresis), with which heat source at what power (`heatSources`: compressor and heating rod as separate entries, each with a `power` band and an optional `estimatedElectricalKwh` for the full raise), and when (`earliestStartIso` / `latestStartIso`, `minDurationMinutes`). An optional `required: {deltaK, byIso}` turns a "may" into a "must, by then" — e.g. hot water dropping below comfort at 18:00. Declare `FlexibilityAnnouncementV3` on the heatpump; for such heatpumps it supersedes the generic `ApplianceFlexibilityAnnouncementV1` / `V2`.
@@ -3767,6 +3839,9 @@ Beyond tanks and circuits, the heatpump metadata (`EnyoHeatpumpApplianceMetadata
3767
3839
  | `ProgramSwitch` | `heatingCircuits[].supportedProgramModes` / `programModeTemperaturesC` / `activeProgramMode` and the same on `domesticHotWater[]` — which zones can be switched to comfort / eco with `HeatpumpProgramSwitchV3`, at which setpoints, and the mode they run in now |
3768
3840
  | `RoomTemperature` | `heatingCircuits[].roomTemperatureC` in `HeatpumpTemperaturesUpdateV1`; where it is measured goes in `heatingCircuits[].roomTemperatureSource` (`None` / `Internal` / `External`) and, for an enyo sensor, `roomTemperatureSensor` |
3769
3841
  | `RoomTemperatureInput` | The heatpump accepts measured room temperatures via `SetHeatpumpRoomTemperatureV1` |
3842
+ | `HeatingCurveOffset` | The heatpump's app shifts a circuit's curve via `SetHeatpumpHeatingCurveOffsetV1`. `heatingCircuits[].heatingCurveOffsetRangeK` (allowed range, must contain 0), `activeHeatingCurveOffsetK` (effective shift read back) and `initialHeatingCurve` (the curve before the first shift) |
3843
+ | — | `heatingCircuits[].curveOptimization` — the customer's choice for the room-temperature based curve optimisation: `enabled` (off by default) and `roomIds` (the rooms the circuit heats, see `useRoom()`). Customer-owned; integrations only read it |
3844
+ | — | `building.recommendedHeatingCurve` — the curve the building actually needs, learned from the days the room held its target |
3770
3845
  | `PvSurplusSignal` | The heatpump takes `availablePowerW` of `HeatpumpControlV3` as a PV surplus signal (e.g. Lambda register 102). Supersedes the deprecated `AvailablePowerAnnouncement` |
3771
3846
  | `ControlV3` | The heatpump's app handles `HeatpumpControlV3` / `HeatpumpControlCancelV3` and reports `HeatpumpControlStatusV3`. The energy manager sends V3 only to heatpumps declaring it. Needs at least one overheating feature to be actionable |
3772
3847
  | `FlexibilityAnnouncementV3` | The heatpump publishes `HeatpumpFlexibilityAnnouncementV3` offers; the energy manager plans from them instead of deriving the headroom from metadata. Needs at least one overheating feature to be actionable |
@@ -69,4 +69,6 @@ var EnergyAppPermissionTypeEnum;
69
69
  EnergyAppPermissionTypeEnum["VehicleIntegration"] = "VehicleIntegration";
70
70
  EnergyAppPermissionTypeEnum["Webhook"] = "Webhook";
71
71
  EnergyAppPermissionTypeEnum["Subscriptions"] = "Subscriptions";
72
+ EnergyAppPermissionTypeEnum["Room"] = "Room";
73
+ EnergyAppPermissionTypeEnum["RoomTemperatures"] = "RoomTemperatures";
72
74
  })(EnergyAppPermissionTypeEnum || (exports.EnergyAppPermissionTypeEnum = EnergyAppPermissionTypeEnum = {}));
@@ -64,7 +64,9 @@ export declare enum EnergyAppPermissionTypeEnum {
64
64
  Calibration = "Calibration",
65
65
  VehicleIntegration = "VehicleIntegration",
66
66
  Webhook = "Webhook",
67
- Subscriptions = "Subscriptions"
67
+ Subscriptions = "Subscriptions",
68
+ Room = "Room",
69
+ RoomTemperatures = "RoomTemperatures"
68
70
  }
69
71
  /**
70
72
  * String union of every permission name, derived from
@@ -552,6 +552,32 @@ class EnergyApp {
552
552
  useSubscriptions() {
553
553
  return this.energyAppSdk.useSubscriptions();
554
554
  }
555
+ /**
556
+ * Gets the Room API for reading the rooms of the home: their name, the
557
+ * appliances in them, the temperature the customer wants there and, for
558
+ * the heating curve optimisation, their warmth and thermostat. Read the
559
+ * rooms with `getRooms()` or `getRoomsById()` and react to changes with
560
+ * `listenForRoomsChanged()`.
561
+ * @returns The Room API instance
562
+ * @throws {EnergyAppPermissionNotGrantedError} If the `Room` permission is
563
+ * not granted.
564
+ */
565
+ useRoom() {
566
+ return this.energyAppSdk.useRoom();
567
+ }
568
+ /**
569
+ * Gets the Room Temperatures API for reading the temperatures measured in
570
+ * the rooms of the home, per sensor: the current values with
571
+ * `getCurrentRoomTemperatures()` and the past ones with
572
+ * `getRoomTemperatureHistory()`. Follow changes via the
573
+ * `TemperatureSensorValuesUpdateV1` messages on the data bus.
574
+ * @returns The Room Temperatures API instance
575
+ * @throws {EnergyAppPermissionNotGrantedError} If the `RoomTemperatures`
576
+ * permission is not granted.
577
+ */
578
+ useRoomTemperatures() {
579
+ return this.energyAppSdk.useRoomTemperatures();
580
+ }
555
581
  /**
556
582
  * Gets the current SDK version.
557
583
  * @returns The semantic version string of the SDK
@@ -53,6 +53,8 @@ import { EnergyAppCommandLog } from "./packages/energy-app-command-log.cjs";
53
53
  import { EnergyAppCascade } from "./packages/energy-app-cascade.cjs";
54
54
  import { EnergyAppWebhook } from "./packages/energy-app-webhook.cjs";
55
55
  import { EnergyAppSubscriptions } from "./packages/energy-app-subscriptions.cjs";
56
+ import { EnergyAppRoom } from "./packages/energy-app-room.cjs";
57
+ import { EnergyAppRoomTemperatures } from "./packages/energy-app-room-temperatures.cjs";
56
58
  import { UseFetchOptions } from "./types/enyo-fetch.cjs";
57
59
  /**
58
60
  * Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
@@ -469,6 +471,28 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
469
471
  * permission is not granted.
470
472
  */
471
473
  useSubscriptions(): EnergyAppSubscriptions;
474
+ /**
475
+ * Gets the Room API for reading the rooms of the home: their name, the
476
+ * appliances in them, the temperature the customer wants there and, for
477
+ * the heating curve optimisation, their warmth and thermostat. Read the
478
+ * rooms with `getRooms()` or `getRoomsById()` and react to changes with
479
+ * `listenForRoomsChanged()`.
480
+ * @returns The Room API instance
481
+ * @throws {EnergyAppPermissionNotGrantedError} If the `Room` permission is
482
+ * not granted.
483
+ */
484
+ useRoom(): EnergyAppRoom;
485
+ /**
486
+ * Gets the Room Temperatures API for reading the temperatures measured in
487
+ * the rooms of the home, per sensor: the current values with
488
+ * `getCurrentRoomTemperatures()` and the past ones with
489
+ * `getRoomTemperatureHistory()`. Follow changes via the
490
+ * `TemperatureSensorValuesUpdateV1` messages on the data bus.
491
+ * @returns The Room Temperatures API instance
492
+ * @throws {EnergyAppPermissionNotGrantedError} If the `RoomTemperatures`
493
+ * permission is not granted.
494
+ */
495
+ useRoomTemperatures(): EnergyAppRoomTemperatures;
472
496
  /**
473
497
  * Gets the current SDK version.
474
498
  * @returns The semantic version string of the SDK
@@ -52,6 +52,8 @@ import { EnergyAppCommandLog } from "./packages/energy-app-command-log.cjs";
52
52
  import { EnergyAppCascade } from "./packages/energy-app-cascade.cjs";
53
53
  import { EnergyAppWebhook } from "./packages/energy-app-webhook.cjs";
54
54
  import { EnergyAppSubscriptions } from "./packages/energy-app-subscriptions.cjs";
55
+ import { EnergyAppRoom } from "./packages/energy-app-room.cjs";
56
+ import { EnergyAppRoomTemperatures } from "./packages/energy-app-room-temperatures.cjs";
55
57
  import { UseFetchOptions } from "./types/enyo-fetch.cjs";
56
58
  export declare enum EnergyAppStateEnum {
57
59
  Launching = "launching",
@@ -198,4 +200,8 @@ export interface EnyoEnergyAppSdk {
198
200
  useWebhook: () => EnergyAppWebhook;
199
201
  /** Get the Subscriptions API for reading the subscriptions bought for this app on this device and listening for new or changed subscriptions */
200
202
  useSubscriptions: () => EnergyAppSubscriptions;
203
+ /** Get the Room API for reading the rooms of the home the customer set up in the enyo app */
204
+ useRoom: () => EnergyAppRoom;
205
+ /** Get the Room Temperatures API for reading the current and past temperatures measured in the rooms of the home */
206
+ useRoomTemperatures: () => EnergyAppRoomTemperatures;
201
207
  }
@@ -618,6 +618,62 @@ class ApplianceManager {
618
618
  await this.energyApp.useAppliances().save(this.mergeApplianceData(appliance, { heatpump: { domesticHotWater: zones } }), applianceId);
619
619
  await this.primeCacheFromSdk(applianceId);
620
620
  }
621
+ /**
622
+ * Remembers the heating curve a circuit had before the first
623
+ * `SetHeatpumpHeatingCurveOffsetV1` changed it, as the circuit's
624
+ * `initialHeatingCurve` — the curve `offsetK: 0` restores. Call it before
625
+ * writing the first offset other than `0`.
626
+ *
627
+ * Keeps an already stored initial curve: every later offset is relative to
628
+ * the curve before the *first* change, so it must not be overwritten with
629
+ * an already shifted one. Works like {@link updateHeatpumpHeatingCircuit}
630
+ * otherwise.
631
+ *
632
+ * Does nothing when the appliance does not exist.
633
+ *
634
+ * @param applianceId The ID of the heatpump appliance
635
+ * @param index The `index` of the heating circuit
636
+ * @param curve The curve currently set on the device
637
+ * @returns `true` when the curve was stored, `false` when an initial curve
638
+ * was already stored or the appliance does not exist
639
+ * @throws {ApplianceManagerDisposedError} when called after {@link dispose}
640
+ */
641
+ async rememberHeatpumpInitialHeatingCurve(applianceId, index, curve) {
642
+ this.throwIfDisposed();
643
+ const appliance = await this.energyApp.useAppliances().getById(applianceId);
644
+ if (!appliance)
645
+ return false;
646
+ const circuit = appliance.heatpump?.heatingCircuits?.find(c => c.index === index);
647
+ if (circuit?.initialHeatingCurve)
648
+ return false;
649
+ await this.updateHeatpumpHeatingCircuit(applianceId, index, { initialHeatingCurve: curve });
650
+ return true;
651
+ }
652
+ /**
653
+ * Forgets a circuit's `initialHeatingCurve` after the integration restored
654
+ * it on the device (`offsetK: 0`), and sets `activeHeatingCurveOffsetK` to
655
+ * `0`. Other circuits and fields are left untouched.
656
+ *
657
+ * Does nothing when the appliance or the circuit does not exist.
658
+ *
659
+ * @param applianceId The ID of the heatpump appliance
660
+ * @param index The `index` of the heating circuit
661
+ * @throws {ApplianceManagerDisposedError} when called after {@link dispose}
662
+ */
663
+ async clearHeatpumpInitialHeatingCurve(applianceId, index) {
664
+ this.throwIfDisposed();
665
+ const appliance = await this.energyApp.useAppliances().getById(applianceId);
666
+ if (!appliance)
667
+ return;
668
+ const circuits = [...(appliance.heatpump?.heatingCircuits ?? [])];
669
+ const position = circuits.findIndex(c => c.index === index);
670
+ if (position < 0)
671
+ return;
672
+ const { initialHeatingCurve: _removed, ...rest } = circuits[position];
673
+ circuits[position] = { ...rest, activeHeatingCurveOffsetK: 0 };
674
+ await this.energyApp.useAppliances().save(this.mergeApplianceData(appliance, { heatpump: { heatingCircuits: circuits } }), applianceId);
675
+ await this.primeCacheFromSdk(applianceId);
676
+ }
621
677
  /**
622
678
  * Refetches an appliance from the SDK and reflects the result in the
623
679
  * cache. Used by mutating methods to keep cache reads consistent with
@@ -3,7 +3,7 @@ import type { EnyoNetworkDevice } from "../../types/enyo-network-device.cjs";
3
3
  import { EnyoAppliance, EnyoApplianceAvailableFeaturesEnum, EnyoApplianceConnectionType, EnyoApplianceGroup, EnyoApplianceMetadata, EnyoApplianceName, EnyoApplianceStateEnum, EnyoApplianceTopology, EnyoApplianceTypeEnum } from "../../types/enyo-appliance.cjs";
4
4
  import type { EnyoApplianceCreatedFilter } from "../../packages/energy-app-appliance.cjs";
5
5
  import type { EnyoChargerApplianceMetadata } from "../../types/enyo-charger-appliance.cjs";
6
- import type { EnyoHeatpumpApplianceDomesticHotWater, EnyoHeatpumpApplianceHeatingCircuit, EnyoHeatpumpApplianceMetadata } from "../../types/enyo-heatpump-appliance.cjs";
6
+ import type { EnyoHeatpumpApplianceDomesticHotWater, EnyoHeatpumpApplianceHeatingCircuit, EnyoHeatpumpApplianceHeatingCurve, EnyoHeatpumpApplianceMetadata } from "../../types/enyo-heatpump-appliance.cjs";
7
7
  import type { EnyoBatteryApplianceMetadata } from "../../types/enyo-battery-appliance.cjs";
8
8
  import type { EnyoInverterApplianceMetadata } from "../../types/enyo-inverter-appliance.cjs";
9
9
  import type { EnyoMeterAppliance } from "../../types/enyo-meter-appliance.cjs";
@@ -401,6 +401,39 @@ export declare class ApplianceManager {
401
401
  * @throws {Error} when the zone does not exist and `patch` lacks `targetTemperatureC`
402
402
  */
403
403
  updateHeatpumpDomesticHotWater(applianceId: string, index: number, patch: Partial<Omit<EnyoHeatpumpApplianceDomesticHotWater, 'index'>>): Promise<void>;
404
+ /**
405
+ * Remembers the heating curve a circuit had before the first
406
+ * `SetHeatpumpHeatingCurveOffsetV1` changed it, as the circuit's
407
+ * `initialHeatingCurve` — the curve `offsetK: 0` restores. Call it before
408
+ * writing the first offset other than `0`.
409
+ *
410
+ * Keeps an already stored initial curve: every later offset is relative to
411
+ * the curve before the *first* change, so it must not be overwritten with
412
+ * an already shifted one. Works like {@link updateHeatpumpHeatingCircuit}
413
+ * otherwise.
414
+ *
415
+ * Does nothing when the appliance does not exist.
416
+ *
417
+ * @param applianceId The ID of the heatpump appliance
418
+ * @param index The `index` of the heating circuit
419
+ * @param curve The curve currently set on the device
420
+ * @returns `true` when the curve was stored, `false` when an initial curve
421
+ * was already stored or the appliance does not exist
422
+ * @throws {ApplianceManagerDisposedError} when called after {@link dispose}
423
+ */
424
+ rememberHeatpumpInitialHeatingCurve(applianceId: string, index: number, curve: EnyoHeatpumpApplianceHeatingCurve): Promise<boolean>;
425
+ /**
426
+ * Forgets a circuit's `initialHeatingCurve` after the integration restored
427
+ * it on the device (`offsetK: 0`), and sets `activeHeatingCurveOffsetK` to
428
+ * `0`. Other circuits and fields are left untouched.
429
+ *
430
+ * Does nothing when the appliance or the circuit does not exist.
431
+ *
432
+ * @param applianceId The ID of the heatpump appliance
433
+ * @param index The `index` of the heating circuit
434
+ * @throws {ApplianceManagerDisposedError} when called after {@link dispose}
435
+ */
436
+ clearHeatpumpInitialHeatingCurve(applianceId: string, index: number): Promise<void>;
404
437
  /**
405
438
  * Refetches an appliance from the SDK and reflects the result in the
406
439
  * cache. Used by mutating methods to keep cache reads consistent with
@@ -5,6 +5,7 @@ exports.isPvOnlyPowerSources = isPvOnlyPowerSources;
5
5
  exports.validateHeatpumpControlAction = validateHeatpumpControlAction;
6
6
  exports.computeHeatpumpSetpointRaise = computeHeatpumpSetpointRaise;
7
7
  exports.validateHeatpumpProgramSwitch = validateHeatpumpProgramSwitch;
8
+ exports.validateHeatpumpHeatingCurveOffset = validateHeatpumpHeatingCurveOffset;
8
9
  const enyo_data_bus_value_js_1 = require("../../types/enyo-data-bus-value.cjs");
9
10
  const enyo_heatpump_appliance_js_1 = require("../../types/enyo-heatpump-appliance.cjs");
10
11
  /**
@@ -284,3 +285,63 @@ function validateHeatpumpProgramSwitch(data, metadata, nowMs = Date.now()) {
284
285
  }
285
286
  return { answer: enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted };
286
287
  }
288
+ /**
289
+ * Validates a `SetHeatpumpHeatingCurveOffsetV1` against the heatpump's
290
+ * metadata and returns the answer it deserves.
291
+ *
292
+ * Used by `HeatpumpIntegrationEnergyApp` before it calls
293
+ * `handleHeatpumpHeatingCurveOffset`; an integration handling the message
294
+ * itself can call it too.
295
+ *
296
+ * Rules, in order — the first that fails decides:
297
+ * 1. `heatingCircuitIndex` is an integer ≥ 0 and `offsetK` a finite number
298
+ * (**Rejected**).
299
+ * 2. The heatpump does not declare `HeatingCurveOffset` (**NotSupported**).
300
+ * 3. No heating circuit has the message's index (**Rejected**).
301
+ * 4. `offsetK` is not `0` and the circuit has no `heatingCurveOffsetRangeK`
302
+ * or `offsetK` lies outside it (**Rejected**). `0` — restoring the initial
303
+ * curve — is always allowed.
304
+ *
305
+ * Without metadata only rule 1 can be checked.
306
+ *
307
+ * @param data - The message's data.
308
+ * @param metadata - The heatpump's metadata, if known.
309
+ * @returns The answer: `Accepted` when every rule holds.
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * const result = validateHeatpumpHeatingCurveOffset(message.data, appliance.heatpump);
314
+ * if (result.answer !== EnyoCommandAcknowledgeAnswerEnum.Accepted) console.warn(result.rejectionReason);
315
+ * ```
316
+ */
317
+ function validateHeatpumpHeatingCurveOffset(data, metadata) {
318
+ const rejected = (rejectionReason) => ({ answer: enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, rejectionReason });
319
+ if (!Number.isInteger(data.heatingCircuitIndex) || data.heatingCircuitIndex < 0) {
320
+ return rejected(`heatingCircuitIndex must be an integer ≥ 0; got ${data.heatingCircuitIndex}.`);
321
+ }
322
+ if (typeof data.offsetK !== 'number' || !Number.isFinite(data.offsetK)) {
323
+ return rejected(`offsetK must be a finite number; got ${data.offsetK}.`);
324
+ }
325
+ if (!metadata)
326
+ return { answer: enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted };
327
+ if (!(metadata.availableFeatures ?? []).includes(enyo_heatpump_appliance_js_1.EnyoHeatpumpApplianceAvailableFeaturesEnum.HeatingCurveOffset)) {
328
+ return {
329
+ answer: enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.NotSupported,
330
+ rejectionReason: 'The heatpump does not support heating curve offsets (no HeatingCurveOffset).',
331
+ };
332
+ }
333
+ const circuit = (metadata.heatingCircuits ?? []).find((c) => c.index === data.heatingCircuitIndex);
334
+ if (!circuit) {
335
+ return rejected(`No heating circuit with index ${data.heatingCircuitIndex}.`);
336
+ }
337
+ if (data.offsetK === 0)
338
+ return { answer: enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted };
339
+ const range = circuit.heatingCurveOffsetRangeK;
340
+ if (!range) {
341
+ return rejected(`Heating circuit ${circuit.index} declares no heatingCurveOffsetRangeK; only offsetK 0 is accepted.`);
342
+ }
343
+ if (data.offsetK < range.minK || data.offsetK > range.maxK) {
344
+ return rejected(`offsetK ${data.offsetK} K is outside the range ${range.minK}…${range.maxK} K of heating circuit ${circuit.index}.`);
345
+ }
346
+ return { answer: enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Accepted };
347
+ }
@@ -1,4 +1,4 @@
1
- import { EnyoCommandAcknowledgeActionResult, EnyoCommandAcknowledgeAnswerEnum, EnyoDataBusHeatpumpFlexibilityAnnouncementV3, EnyoDataBusHeatpumpProgramSwitchV3, EnyoHeatpumpControlAction, EnyoHeatpumpControlTargetEnum, EnyoPowerSourceShare } from '../../types/enyo-data-bus-value.cjs';
1
+ import { EnyoCommandAcknowledgeActionResult, EnyoCommandAcknowledgeAnswerEnum, EnyoDataBusHeatpumpFlexibilityAnnouncementV3, EnyoDataBusHeatpumpProgramSwitchV3, EnyoDataBusSetHeatpumpHeatingCurveOffsetV1, EnyoHeatpumpControlAction, EnyoHeatpumpControlTargetEnum, EnyoPowerSourceShare } from '../../types/enyo-data-bus-value.cjs';
2
2
  import { EnyoHeatpumpApplianceAvailableFeaturesEnum, EnyoHeatpumpApplianceMetadata } from '../../types/enyo-heatpump-appliance.cjs';
3
3
  /**
4
4
  * The overheating feature each `HeatpumpControlV3` target needs — and that a
@@ -175,3 +175,40 @@ export interface HeatpumpProgramSwitchValidationResult {
175
175
  * ```
176
176
  */
177
177
  export declare function validateHeatpumpProgramSwitch(data: EnyoDataBusHeatpumpProgramSwitchV3['data'], metadata: EnyoHeatpumpApplianceMetadata | undefined, nowMs?: number): HeatpumpProgramSwitchValidationResult;
178
+ /** The answer {@link validateHeatpumpHeatingCurveOffset} gives a `SetHeatpumpHeatingCurveOffsetV1`. */
179
+ export interface HeatpumpHeatingCurveOffsetValidationResult {
180
+ /** `Accepted` when every rule holds. */
181
+ answer: EnyoCommandAcknowledgeAnswerEnum;
182
+ /** Why the offset was rejected or is not supported. */
183
+ rejectionReason?: string;
184
+ }
185
+ /**
186
+ * Validates a `SetHeatpumpHeatingCurveOffsetV1` against the heatpump's
187
+ * metadata and returns the answer it deserves.
188
+ *
189
+ * Used by `HeatpumpIntegrationEnergyApp` before it calls
190
+ * `handleHeatpumpHeatingCurveOffset`; an integration handling the message
191
+ * itself can call it too.
192
+ *
193
+ * Rules, in order — the first that fails decides:
194
+ * 1. `heatingCircuitIndex` is an integer ≥ 0 and `offsetK` a finite number
195
+ * (**Rejected**).
196
+ * 2. The heatpump does not declare `HeatingCurveOffset` (**NotSupported**).
197
+ * 3. No heating circuit has the message's index (**Rejected**).
198
+ * 4. `offsetK` is not `0` and the circuit has no `heatingCurveOffsetRangeK`
199
+ * or `offsetK` lies outside it (**Rejected**). `0` — restoring the initial
200
+ * curve — is always allowed.
201
+ *
202
+ * Without metadata only rule 1 can be checked.
203
+ *
204
+ * @param data - The message's data.
205
+ * @param metadata - The heatpump's metadata, if known.
206
+ * @returns The answer: `Accepted` when every rule holds.
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * const result = validateHeatpumpHeatingCurveOffset(message.data, appliance.heatpump);
211
+ * if (result.answer !== EnyoCommandAcknowledgeAnswerEnum.Accepted) console.warn(result.rejectionReason);
212
+ * ```
213
+ */
214
+ export declare function validateHeatpumpHeatingCurveOffset(data: EnyoDataBusSetHeatpumpHeatingCurveOffsetV1['data'], metadata: EnyoHeatpumpApplianceMetadata | undefined): HeatpumpHeatingCurveOffsetValidationResult;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * Which rooms of a heating circuit the room-temperature based heating curve
4
+ * optimisation is judged by. Shared by the energy manager, which lowers the
5
+ * curve, and the host's app, which tells the customer how to set each room's
6
+ * thermostat — both must agree on it.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.curveWarmthRank = curveWarmthRank;
10
+ exports.isManualRoomThermostat = isManualRoomThermostat;
11
+ exports.curveTopManualRank = curveTopManualRank;
12
+ exports.curveJudgedRoomIds = curveJudgedRoomIds;
13
+ exports.curveOptimizationRooms = curveOptimizationRooms;
14
+ const enyo_room_js_1 = require("../../types/enyo-room.cjs");
15
+ const WARMTH_RANK = {
16
+ [enyo_room_js_1.EnyoRoomWarmthEnum.Cool]: 0,
17
+ [enyo_room_js_1.EnyoRoomWarmthEnum.Normal]: 1,
18
+ [enyo_room_js_1.EnyoRoomWarmthEnum.Warm]: 2,
19
+ };
20
+ const MANUAL_THERMOSTATS = new Set([
21
+ enyo_room_js_1.EnyoRoomThermostatEnum.Steps5,
22
+ enyo_room_js_1.EnyoRoomThermostatEnum.Steps6,
23
+ enyo_room_js_1.EnyoRoomThermostatEnum.CelsiusDial,
24
+ ]);
25
+ /**
26
+ * How much warmth a room asks for compared with the other rooms of its
27
+ * circuit: 0 for `cool`, 1 for `normal` (also when absent), 2 for `warm`.
28
+ *
29
+ * @param traits - The room's warmth, if known.
30
+ * @returns The rank.
31
+ */
32
+ function curveWarmthRank(traits) {
33
+ return WARMTH_RANK[traits?.warmth ?? enyo_room_js_1.EnyoRoomWarmthEnum.Normal];
34
+ }
35
+ /**
36
+ * Whether a thermostat is set by hand: a dial with steps or a dial in °C.
37
+ * Absent counts as the common dial with steps 1–5.
38
+ *
39
+ * @param thermostat - The room's thermostat.
40
+ * @returns `true` for a manual thermostat.
41
+ */
42
+ function isManualRoomThermostat(thermostat) {
43
+ return thermostat === undefined || MANUAL_THERMOSTATS.has(thermostat);
44
+ }
45
+ /**
46
+ * How much warmth the most demanding room behind a manual thermostat asks for.
47
+ *
48
+ * @param roomIds - The rooms of the circuit.
49
+ * @param traitsOf - A room's warmth and thermostat.
50
+ * @returns The highest rank, or `undefined` when no room has a manual thermostat.
51
+ */
52
+ function curveTopManualRank(roomIds, traitsOf) {
53
+ const ranks = roomIds
54
+ .filter((roomId) => isManualRoomThermostat(traitsOf(roomId)?.thermostat))
55
+ .map((roomId) => curveWarmthRank(traitsOf(roomId)));
56
+ return ranks.length > 0 ? Math.max(...ranks) : undefined;
57
+ }
58
+ /**
59
+ * The rooms of a heating circuit the curve is judged by. One flow temperature
60
+ * heats every room of the circuit, so the curve has to serve the warmest wish:
61
+ * of the rooms with a manual thermostat, those asking for the most warmth keep
62
+ * it fully open and show what the curve does. Every other one is held back by
63
+ * its thermostat, reads the thermostat rather than the curve, and would mislead
64
+ * the controller. A room with no thermostat follows the curve, and one with a
65
+ * self-regulating thermostat holds its own target — both are always judged.
66
+ *
67
+ * @param roomIds - The rooms of the circuit.
68
+ * @param traitsOf - A room's warmth and thermostat.
69
+ * @returns The judged room IDs, in the order given.
70
+ */
71
+ function curveJudgedRoomIds(roomIds, traitsOf) {
72
+ const top = curveTopManualRank(roomIds, traitsOf);
73
+ return roomIds.filter((roomId) => {
74
+ const traits = traitsOf(roomId);
75
+ return !isManualRoomThermostat(traits?.thermostat) || curveWarmthRank(traits) === top;
76
+ });
77
+ }
78
+ /**
79
+ * The rooms a heating circuit's curve optimisation is judged by, resolved
80
+ * against the rooms of the home ({@link EnergyAppRoom.getRooms} or
81
+ * {@link EnergyAppRoom.getRoomsById}). A room that no longer exists is
82
+ * dropped. Whether a judged room can be measured is up to
83
+ * {@link EnergyAppRoomTemperatures}.
84
+ *
85
+ * @param settings - The circuit's {@link EnyoHeatpumpApplianceHeatingCircuit.curveOptimization}.
86
+ * @param rooms - Every room of the home.
87
+ * @returns The judged rooms, in the order of `settings.roomIds`.
88
+ */
89
+ function curveOptimizationRooms(settings, rooms) {
90
+ const live = (settings?.roomIds ?? []).flatMap((roomId) => {
91
+ const room = rooms.find((candidate) => candidate.id === roomId);
92
+ return room ? [room] : [];
93
+ });
94
+ const judged = curveJudgedRoomIds(live.map((room) => room.id), (roomId) => live.find((room) => room.id === roomId));
95
+ return live.filter((room) => judged.includes(room.id));
96
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Which rooms of a heating circuit the room-temperature based heating curve
3
+ * optimisation is judged by. Shared by the energy manager, which lowers the
4
+ * curve, and the host's app, which tells the customer how to set each room's
5
+ * thermostat — both must agree on it.
6
+ */
7
+ import type { EnyoHeatpumpHeatingCurveOptimizationSettings } from '../../types/enyo-heatpump-appliance.cjs';
8
+ import { type EnyoRoom, EnyoRoomThermostatEnum, EnyoRoomWarmthEnum } from '../../types/enyo-room.cjs';
9
+ /** What the curve rules need to know about a room. */
10
+ export interface CurveRoomTraits {
11
+ /** Absent: {@link EnyoRoomWarmthEnum.Normal}. */
12
+ warmth?: EnyoRoomWarmthEnum;
13
+ /** Absent: {@link EnyoRoomThermostatEnum.Steps5}. */
14
+ thermostat?: EnyoRoomThermostatEnum;
15
+ }
16
+ /**
17
+ * How much warmth a room asks for compared with the other rooms of its
18
+ * circuit: 0 for `cool`, 1 for `normal` (also when absent), 2 for `warm`.
19
+ *
20
+ * @param traits - The room's warmth, if known.
21
+ * @returns The rank.
22
+ */
23
+ export declare function curveWarmthRank(traits: CurveRoomTraits | undefined): number;
24
+ /**
25
+ * Whether a thermostat is set by hand: a dial with steps or a dial in °C.
26
+ * Absent counts as the common dial with steps 1–5.
27
+ *
28
+ * @param thermostat - The room's thermostat.
29
+ * @returns `true` for a manual thermostat.
30
+ */
31
+ export declare function isManualRoomThermostat(thermostat: EnyoRoomThermostatEnum | undefined): boolean;
32
+ /**
33
+ * How much warmth the most demanding room behind a manual thermostat asks for.
34
+ *
35
+ * @param roomIds - The rooms of the circuit.
36
+ * @param traitsOf - A room's warmth and thermostat.
37
+ * @returns The highest rank, or `undefined` when no room has a manual thermostat.
38
+ */
39
+ export declare function curveTopManualRank(roomIds: readonly string[], traitsOf: (roomId: string) => CurveRoomTraits | undefined): number | undefined;
40
+ /**
41
+ * The rooms of a heating circuit the curve is judged by. One flow temperature
42
+ * heats every room of the circuit, so the curve has to serve the warmest wish:
43
+ * of the rooms with a manual thermostat, those asking for the most warmth keep
44
+ * it fully open and show what the curve does. Every other one is held back by
45
+ * its thermostat, reads the thermostat rather than the curve, and would mislead
46
+ * the controller. A room with no thermostat follows the curve, and one with a
47
+ * self-regulating thermostat holds its own target — both are always judged.
48
+ *
49
+ * @param roomIds - The rooms of the circuit.
50
+ * @param traitsOf - A room's warmth and thermostat.
51
+ * @returns The judged room IDs, in the order given.
52
+ */
53
+ export declare function curveJudgedRoomIds(roomIds: readonly string[], traitsOf: (roomId: string) => CurveRoomTraits | undefined): string[];
54
+ /**
55
+ * The rooms a heating circuit's curve optimisation is judged by, resolved
56
+ * against the rooms of the home ({@link EnergyAppRoom.getRooms} or
57
+ * {@link EnergyAppRoom.getRoomsById}). A room that no longer exists is
58
+ * dropped. Whether a judged room can be measured is up to
59
+ * {@link EnergyAppRoomTemperatures}.
60
+ *
61
+ * @param settings - The circuit's {@link EnyoHeatpumpApplianceHeatingCircuit.curveOptimization}.
62
+ * @param rooms - Every room of the home.
63
+ * @returns The judged rooms, in the order of `settings.roomIds`.
64
+ */
65
+ export declare function curveOptimizationRooms(settings: EnyoHeatpumpHeatingCurveOptimizationSettings | undefined, rooms: readonly EnyoRoom[]): EnyoRoom[];