@enyo-energy/energy-app-sdk 1.26.0 → 1.27.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.
- package/README.md +162 -3
- package/dist/cjs/energy-app-model-feature.enum.cjs +2 -0
- package/dist/cjs/energy-app-model-feature.enum.d.cts +2 -0
- package/dist/cjs/energy-app.cjs +17 -0
- package/dist/cjs/energy-app.d.cts +16 -0
- package/dist/cjs/enyo-energy-app-sdk.d.cts +3 -0
- package/dist/cjs/implementations/appliances/appliance-manager.cjs +72 -0
- package/dist/cjs/implementations/appliances/appliance-manager.d.cts +37 -1
- package/dist/cjs/implementations/heatpump/heatpump-metadata-validators.cjs +329 -0
- package/dist/cjs/implementations/heatpump/heatpump-metadata-validators.d.cts +105 -0
- package/dist/cjs/index.cjs +3 -0
- package/dist/cjs/index.d.cts +3 -0
- package/dist/cjs/integrations/heatpump-integration-energy-app.cjs +29 -1
- package/dist/cjs/integrations/heatpump-integration-energy-app.d.cts +24 -2
- package/dist/cjs/packages/energy-app-cascade.cjs +2 -0
- package/dist/cjs/packages/energy-app-cascade.d.cts +374 -0
- package/dist/cjs/packages/energy-app-electricity-tariff.d.cts +41 -3
- package/dist/cjs/types/enyo-appliance.cjs +8 -0
- package/dist/cjs/types/enyo-appliance.d.cts +9 -1
- package/dist/cjs/types/enyo-data-bus-value.cjs +4 -0
- package/dist/cjs/types/enyo-data-bus-value.d.cts +184 -4
- package/dist/cjs/types/enyo-electricity-tariff.cjs +12 -1
- package/dist/cjs/types/enyo-electricity-tariff.d.cts +42 -0
- package/dist/cjs/types/enyo-heatpump-appliance.cjs +69 -1
- package/dist/cjs/types/enyo-heatpump-appliance.d.cts +246 -3
- package/dist/cjs/types/enyo-meter-cascade.cjs +49 -0
- package/dist/cjs/types/enyo-meter-cascade.d.cts +229 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/energy-app-model-feature.enum.d.ts +2 -0
- package/dist/energy-app-model-feature.enum.js +2 -0
- package/dist/energy-app.d.ts +16 -0
- package/dist/energy-app.js +17 -0
- package/dist/enyo-energy-app-sdk.d.ts +3 -0
- package/dist/implementations/appliances/appliance-manager.d.ts +37 -1
- package/dist/implementations/appliances/appliance-manager.js +72 -0
- package/dist/implementations/heatpump/heatpump-metadata-validators.d.ts +105 -0
- package/dist/implementations/heatpump/heatpump-metadata-validators.js +320 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/integrations/heatpump-integration-energy-app.d.ts +24 -2
- package/dist/integrations/heatpump-integration-energy-app.js +30 -2
- package/dist/packages/energy-app-cascade.d.ts +374 -0
- package/dist/packages/energy-app-cascade.js +1 -0
- package/dist/packages/energy-app-electricity-tariff.d.ts +41 -3
- package/dist/types/enyo-appliance.d.ts +9 -1
- package/dist/types/enyo-appliance.js +8 -0
- package/dist/types/enyo-data-bus-value.d.ts +184 -4
- package/dist/types/enyo-data-bus-value.js +4 -0
- package/dist/types/enyo-electricity-tariff.d.ts +42 -0
- package/dist/types/enyo-electricity-tariff.js +11 -0
- package/dist/types/enyo-heatpump-appliance.d.ts +246 -3
- package/dist/types/enyo-heatpump-appliance.js +68 -0
- package/dist/types/enyo-meter-cascade.d.ts +229 -0
- package/dist/types/enyo-meter-cascade.js +46 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,6 +191,7 @@ The SDK exposes several layered building blocks. Pick the one that matches the k
|
|
|
191
191
|
| Read EPEX SPOT wholesale prices (incl. negative-price windows) | [`useEpexSpotPrices()`](#useepexspotprices-energyappepexspotprice) |
|
|
192
192
|
| Manage electricity tariffs (default tariff, price per kWh) | [`useElectricityTariff()`](#useelectricitytariff-energyappelectricitytariff) |
|
|
193
193
|
| Publish or resolve time-variable grid fees (§14a HT/NT) | [`useGridFee()`](#usegridfee-energyappgridfee) |
|
|
194
|
+
| Price appliances behind a cascade meter (own tariff, prices and grid fee) | [`useCascade()`](#usecascade-energyappcascade) |
|
|
194
195
|
| Register a PV system (kWp, DC strings, orientation) | [`usePvSystem()`](#usepvsystem-energyapppvsystem) |
|
|
195
196
|
| Discover capabilities of the active energy manager | [`useEnergyManager()`](#useenergymanager-energyappenergymanager) |
|
|
196
197
|
| Serve your v2 onboarding guides when the host asks for them | [`useOnboardingV2()`](#useonboardingv2-energyapponboardingv2) |
|
|
@@ -1715,6 +1716,18 @@ const prices = await tariffs.getPrices(EnyoTariffDirectionEnum.Consumption, { fr
|
|
|
1715
1716
|
const needsGridFee = !prices?.includes.includes(EnyoPriceComponentEnum.GridFee);
|
|
1716
1717
|
```
|
|
1717
1718
|
|
|
1719
|
+
To price one device, ask for the prices that actually bill it. This works for every appliance: the
|
|
1720
|
+
site's consumption prices, or — for an appliance behind an active [meter cascade](#usecascade-energyappcascade)
|
|
1721
|
+
— the cascade's, filled per 15-minute slot. The prices are effective prices (grid fee and taxes
|
|
1722
|
+
included, see `includes`), so never add a grid fee on top:
|
|
1723
|
+
|
|
1724
|
+
```typescript
|
|
1725
|
+
const billed = await tariffs.getPricesForAppliance('heatpump-1', { fromIso, untilIso });
|
|
1726
|
+
// billed.billingMeter: 'primary' | 'cascade'
|
|
1727
|
+
// entry.fallback: cascade had no price for this slot, the site's was used
|
|
1728
|
+
// billed.inheritedFromSite: cascade has no tariff of its own yet
|
|
1729
|
+
```
|
|
1730
|
+
|
|
1718
1731
|
The app that integrates a provider owns the other side. It answers when the user picks it, sets the
|
|
1719
1732
|
tariff once it is actually usable, and pushes prices as they arrive:
|
|
1720
1733
|
|
|
@@ -1785,6 +1798,115 @@ const fees = await gridFee.getGridFeeValues({ fromIso, untilIso });
|
|
|
1785
1798
|
|
|
1786
1799
|
Publishers need `GridFeeRegister`; consumers need `GridFeeUse`.
|
|
1787
1800
|
|
|
1801
|
+
#### `useCascade(): EnergyAppCascade`
|
|
1802
|
+
|
|
1803
|
+
A **meter cascade** (Kaskadenschaltung) is a second billing meter (Z2) installed *behind* the primary
|
|
1804
|
+
meter (Z1) — typically for a heatpump or wallbox on a dedicated tariff with a reduced (§14a EnWG) grid
|
|
1805
|
+
fee. Appliances behind an **active** Z2 are billed on the cascade's tariff and grid fee; everything else
|
|
1806
|
+
on `useElectricityTariff()` and `useGridFee()`. The site has at most one cascade, and Z2 has a
|
|
1807
|
+
**consumption tariff only** — all feed-in leaves through Z1.
|
|
1808
|
+
|
|
1809
|
+
**Split rule.** PV and battery cover Z2 first; Z2 only pays the cascade tariff for what Z1 imports at the
|
|
1810
|
+
same moment. Z1 importing 2 kW, battery 1 kW, Z2 drawing 2 kW → Z2 grid share 1 kW, Z2 self-consumed
|
|
1811
|
+
1 kW, household grid share 1 kW.
|
|
1812
|
+
|
|
1813
|
+
Topology — is there a cascade, is it working, who is behind it:
|
|
1814
|
+
|
|
1815
|
+
```typescript
|
|
1816
|
+
const cascade = energyApp.useCascade();
|
|
1817
|
+
|
|
1818
|
+
const details = await cascade.getCascade(); // active, status, estimated, meterApplianceId?, applianceIds
|
|
1819
|
+
if (details?.active && details.status !== EnyoCascadeStatusEnum.Live) {
|
|
1820
|
+
// 'noCascadeSource' (nothing assigned yet) or 'noGridReading' (no Z1 reading)
|
|
1821
|
+
}
|
|
1822
|
+
cascade.onCascadeChanged(event => reassignPrices(event.cascade));
|
|
1823
|
+
```
|
|
1824
|
+
|
|
1825
|
+
With `estimated: true`, Z2 is the sum of its appliances and has no meter (`meterApplianceId` absent).
|
|
1826
|
+
A physical Z2 meter carries the topology feature `CascadeSubMeter` — skip it when looking for the grid
|
|
1827
|
+
meter.
|
|
1828
|
+
|
|
1829
|
+
Prices — to price a device, use `useElectricityTariff().getPricesForAppliance(applianceId, range)`. It
|
|
1830
|
+
works for every appliance and picks Z2 or Z1 itself, returning the **effective** price per 15-minute slot,
|
|
1831
|
+
grid fee and taxes included. `cascade.getPrices(range)` returns Z2's effective prices directly. Never add
|
|
1832
|
+
a grid fee on top:
|
|
1833
|
+
|
|
1834
|
+
```typescript
|
|
1835
|
+
const prices = await energyApp.useElectricityTariff().getPricesForAppliance('heatpump-1', { fromIso, untilIso });
|
|
1836
|
+
// prices.billingMeter: 'cascade' | 'primary'
|
|
1837
|
+
// entry.fallback: Z2 had no price for that slot, the site's was used
|
|
1838
|
+
// prices.inheritedFromSite: Z2 has no tariff of its own yet
|
|
1839
|
+
```
|
|
1840
|
+
|
|
1841
|
+
Until Z2 has its own tariff or grid fee, the hub prices it with the site's — and so do `getTariff`,
|
|
1842
|
+
`getPrices`, `getGridFee` and `getGridFeeValues`: they return the site's values with
|
|
1843
|
+
`inheritedFromSite: true` rather than `null`. The grid fee methods are informational (to show the fee
|
|
1844
|
+
separately).
|
|
1845
|
+
|
|
1846
|
+
What Z2 is actually drawing and costing — live via the `CascadeSplitUpdateV1` data bus message
|
|
1847
|
+
(`householdGridW`, `cascadeGridW`, `cascadeSelfConsumedW`, `subMeterPowerW`, `status`, `estimated` — the
|
|
1848
|
+
same values the cockpit shows), and as history:
|
|
1849
|
+
|
|
1850
|
+
```typescript
|
|
1851
|
+
energyApp.useDataBus().listenForMessages([EnyoDataBusMessageEnum.CascadeSplitUpdateV1], msg => {
|
|
1852
|
+
const { cascadeGridW, status } = (msg as EnyoDataBusCascadeSplitUpdateV1).data;
|
|
1853
|
+
});
|
|
1854
|
+
|
|
1855
|
+
const history = await cascade.getCascadeTimeseries({ fromIso, untilIso, resolution: '1d' });
|
|
1856
|
+
// resolution: '1m' (kept 31 days) | '15m' | '1d' | '1mo'
|
|
1857
|
+
// per bucket: householdGridKwh, cascadeGridKwh, cascadeSelfConsumedKwh,
|
|
1858
|
+
// householdCostCt, cascadeCostCt, cascadePriceCt, estimated
|
|
1859
|
+
```
|
|
1860
|
+
|
|
1861
|
+
History costs are in **ct**, priced when read with the tariffs then in force.
|
|
1862
|
+
|
|
1863
|
+
**The core owns the cascade tariff by default** — the user enters it in the hub. The cascade **grid fee
|
|
1864
|
+
comes with the tariff**: it is provided by the core, or by the app that owns the cascade tariff, and goes
|
|
1865
|
+
away with that app's ownership. An app that provides a
|
|
1866
|
+
**dynamic** tariff can offer itself for Z2 and take it over once the user selects it, then publish its
|
|
1867
|
+
prices:
|
|
1868
|
+
|
|
1869
|
+
```typescript
|
|
1870
|
+
// Registering the handler is what lists this app in the hub's cascade tariff selection.
|
|
1871
|
+
cascade.onTariffSelected(async () => {
|
|
1872
|
+
if (!await isAuthenticated()) {
|
|
1873
|
+
return {
|
|
1874
|
+
status: EnyoTariffActivationStatusEnum.AuthenticationRequired,
|
|
1875
|
+
authenticationUrl: buildOAuthUrl('cascade'),
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
await cascade.setTariff({ // takes over the slot from the core
|
|
1879
|
+
name: 'Tibber Wärmepumpe',
|
|
1880
|
+
vendorName: 'Tibber',
|
|
1881
|
+
currency: EnyoCurrencyEnum.EUR,
|
|
1882
|
+
pricing: { type: EnyoTariffPricingTypeEnum.Dynamic }, // only dynamic tariffs can be app-provided
|
|
1883
|
+
externalTariffId: contract.id,
|
|
1884
|
+
});
|
|
1885
|
+
return { status: EnyoTariffActivationStatusEnum.Success };
|
|
1886
|
+
});
|
|
1887
|
+
|
|
1888
|
+
await cascade.publishPrices({ includes: [], entries }); // only while this app owns Z2's tariff
|
|
1889
|
+
await cascade.registerGridFee(reducedGridFee); // optional: the Z2 grid fee comes with the tariff
|
|
1890
|
+
|
|
1891
|
+
cascade.onTariffChanged(event => {
|
|
1892
|
+
if (event.tariff?.externalTariffId !== contract.id) stopPublishing(); // lost ownership
|
|
1893
|
+
});
|
|
1894
|
+
```
|
|
1895
|
+
|
|
1896
|
+
| Group | Methods | Permission |
|
|
1897
|
+
|---|---|---|
|
|
1898
|
+
| Topology | `isActive`, `getCascade`, `getApplianceIds`, `isApplianceBehindCascade`, `onCascadeChanged` | none |
|
|
1899
|
+
| Prices & tariff (read) | `getTariff`, `getPrices`, `onTariffChanged` | none |
|
|
1900
|
+
| History | `getCascadeTimeseries` | `Timeseries` |
|
|
1901
|
+
| Grid fee | `getGridFee`, `getGridFeeValues`, `onGridFeeChanged` / `registerGridFee`, `removeGridFee` (cascade tariff owner only) | `GridFeeUse` / `GridFeeRegister` |
|
|
1902
|
+
| Dynamic tariff provider | `onTariffSelected`, `setTariff`, `publishPrices` | `ElectricityTariff` |
|
|
1903
|
+
| Data bus | `CascadeSplitUpdateV1`, `AggregatedStateUpdateV1.data.cascade` | `SubscribeDataBus` |
|
|
1904
|
+
|
|
1905
|
+
Without a cascade, topology getters resolve to `null` / `false` / `[]`, cascade price and grid fee
|
|
1906
|
+
getters to `null`, and writes reject; `useElectricityTariff().getPricesForAppliance` returns the site's
|
|
1907
|
+
prices. Z1 also counts
|
|
1908
|
+
everything that passes Z2, so never add the two meter readings together.
|
|
1909
|
+
|
|
1788
1910
|
#### `useWeatherForecasting(): EnergyAppWeatherForecasting`
|
|
1789
1911
|
|
|
1790
1912
|
Register a weather-forecast provider (e.g. wraps an external API) and / or consume forecasts by zip code or coordinates.
|
|
@@ -2971,11 +3093,48 @@ class MyHeatpumpApp extends HeatpumpIntegrationEnergyApp {
|
|
|
2971
3093
|
|
|
2972
3094
|
Drives a heatpump. Manages building / DHW overheating commands and grid-power-availability announcements.
|
|
2973
3095
|
|
|
2974
|
-
- **Subscribed commands:** `HeatpumpOverheatingV1`, `HeatpumpAvailablePowerAnnouncementV1`, `GridOperatorPowerLimitationV1` (broadcast)
|
|
2975
|
-
- **Implement:** `handleHeatpumpOverheating`, `handleHeatpumpAvailablePowerAnnouncement`, `handleGridOperatorPowerLimitation`
|
|
3096
|
+
- **Subscribed commands:** `HeatpumpOverheatingV1`, `HeatpumpAvailablePowerAnnouncementV1`, `SetHeatpumpAvailablePowerV2`, `SetHeatpumpRoomTemperatureV1`, `GridOperatorPowerLimitationV1` (broadcast)
|
|
3097
|
+
- **Implement:** `handleHeatpumpOverheating`, `handleHeatpumpAvailablePowerAnnouncement`, `handleSetHeatpumpAvailablePower`, `handleGridOperatorPowerLimitation`
|
|
3098
|
+
- **Optional override:** `handleSetHeatpumpRoomTemperature` — write a measured room temperature into a heating circuit. Answers `NotSupported` by default; override it when the heatpump declares `RoomTemperatureInput`.
|
|
2976
3099
|
- **Publish helpers:**
|
|
2977
3100
|
- `publishHeatpumpValuesUpdate(applianceId, values)` — operation mode, electrical and thermal power, energies.
|
|
2978
|
-
- `publishHeatpumpTemperatures(applianceId, temperatures)` — outdoor, flow
|
|
3101
|
+
- `publishHeatpumpTemperatures(applianceId, temperatures)` — outdoor, flow / target flow / return, DHW tanks, heating circuits (room, flow, target flow, return), buffer tank.
|
|
3102
|
+
|
|
3103
|
+
#### Advanced heatpump metadata
|
|
3104
|
+
|
|
3105
|
+
Beyond tanks and circuits, the heatpump metadata (`EnyoHeatpumpApplianceMetadata`) can carry what the device is configured to do. Declare each part with its feature flag in `availableFeatures` so consumers know to expect it:
|
|
3106
|
+
|
|
3107
|
+
| Feature flag | Where the data lives |
|
|
3108
|
+
|---|---|
|
|
3109
|
+
| `HeatingCurve` | `heatingCircuits[].heatingCurve` — outdoor → flow temperature `points` (vendor-neutral, linearly interpolated), plus raw `slope` / `parallelShiftK`, min / max flow and an optional cooling curve |
|
|
3110
|
+
| `TimeProgram` | `heatingCircuits[].timeProgram` and `domesticHotWater[].timeProgram` — weekly periods (`daysOfWeek` 0 = Sunday, local `HH:mm` times, may wrap past midnight) switching between `Comfort` / `Reduced` / `Off` / `Boost` |
|
|
3111
|
+
| `RoomTemperature` | `heatingCircuits[].roomTemperatureC` in `HeatpumpTemperaturesUpdateV1`; where it is measured goes in `heatingCircuits[].roomTemperatureSource` (`None` / `Internal` / `External`) and, for an enyo sensor, `roomTemperatureSensor` |
|
|
3112
|
+
| `RoomTemperatureInput` | The heatpump accepts measured room temperatures via `SetHeatpumpRoomTemperatureV1` |
|
|
3113
|
+
| `FlowTemperature` / `ReturnTemperature` | `heatpumpFlowTemperatureC`, `heatpumpTargetFlowTemperatureC`, `heatpumpReturnTemperatureC` and per-circuit `flowTemperatureC`, `targetFlowTemperatureC`, `returnTemperatureC` in `HeatpumpTemperaturesUpdateV1` |
|
|
3114
|
+
|
|
3115
|
+
`updateAppliance` replaces the whole `heatingCircuits` / `domesticHotWater` array, so use the `ApplianceManager` helpers to change a single entry:
|
|
3116
|
+
|
|
3117
|
+
```typescript
|
|
3118
|
+
await applianceManager.updateHeatpumpHeatingCircuit(applianceId, 0, {
|
|
3119
|
+
heatingCurve: {
|
|
3120
|
+
points: [
|
|
3121
|
+
{ outdoorTemperatureC: -15, flowTemperatureC: 48 },
|
|
3122
|
+
{ outdoorTemperatureC: 0, flowTemperatureC: 38 },
|
|
3123
|
+
{ outdoorTemperatureC: 15, flowTemperatureC: 27 },
|
|
3124
|
+
],
|
|
3125
|
+
slope: 0.8,
|
|
3126
|
+
parallelShiftK: 0,
|
|
3127
|
+
},
|
|
3128
|
+
timeProgram: {
|
|
3129
|
+
defaultLevel: EnyoHeatpumpTimeProgramLevelEnum.Reduced,
|
|
3130
|
+
levelTemperaturesC: { Comfort: 21, Reduced: 18 },
|
|
3131
|
+
periods: [{ daysOfWeek: [1, 2, 3, 4, 5], startTimeOfDay: '06:00', endTimeOfDay: '22:00', level: EnyoHeatpumpTimeProgramLevelEnum.Comfort }],
|
|
3132
|
+
},
|
|
3133
|
+
});
|
|
3134
|
+
await applianceManager.updateHeatpumpDomesticHotWater(applianceId, 0, { timeProgram: dhwProgram });
|
|
3135
|
+
```
|
|
3136
|
+
|
|
3137
|
+
Validate before publishing with `validateHeatpumpHeatingCurve`, `validateHeatpumpTimeProgram` or `validateHeatpumpAdvancedMetadata` (or their `assertValid*` counterparts). Each returns `{ ok, errors, warnings }`.
|
|
2979
3138
|
|
|
2980
3139
|
### WallboxIntegrationEnergyApp
|
|
2981
3140
|
|
|
@@ -41,6 +41,8 @@ var EnergyAppModelFeatureEnum;
|
|
|
41
41
|
EnergyAppModelFeatureEnum["HeatpumpPowerModulation"] = "heatpump-power-modulation";
|
|
42
42
|
/** The model supports the SG Ready interface for smart-grid signalling */
|
|
43
43
|
EnergyAppModelFeatureEnum["HeatpumpSgReady"] = "heatpump-sg-ready";
|
|
44
|
+
/** The heat pump accepts measured room temperatures from an external sensor as control input */
|
|
45
|
+
EnergyAppModelFeatureEnum["HeatpumpRoomTemperatureInput"] = "heatpump-room-temperature-input";
|
|
44
46
|
// Climate Control / Air Conditioning
|
|
45
47
|
/** The target temperature setpoint can be controlled */
|
|
46
48
|
EnergyAppModelFeatureEnum["ClimateTemperatureSetpoint"] = "climate-temperature-setpoint";
|
|
@@ -33,6 +33,8 @@ export declare enum EnergyAppModelFeatureEnum {
|
|
|
33
33
|
HeatpumpPowerModulation = "heatpump-power-modulation",
|
|
34
34
|
/** The model supports the SG Ready interface for smart-grid signalling */
|
|
35
35
|
HeatpumpSgReady = "heatpump-sg-ready",
|
|
36
|
+
/** The heat pump accepts measured room temperatures from an external sensor as control input */
|
|
37
|
+
HeatpumpRoomTemperatureInput = "heatpump-room-temperature-input",
|
|
36
38
|
/** The target temperature setpoint can be controlled */
|
|
37
39
|
ClimateTemperatureSetpoint = "climate-temperature-setpoint",
|
|
38
40
|
/** The operating mode (heat / cool / fan / off) can be controlled */
|
package/dist/cjs/energy-app.cjs
CHANGED
|
@@ -506,6 +506,23 @@ class EnergyApp {
|
|
|
506
506
|
useCommandLog() {
|
|
507
507
|
return this.energyAppSdk.useCommandLog();
|
|
508
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* Gets the Cascade API for the site's meter cascade: a second meter behind
|
|
511
|
+
* the primary meter (e.g. for a heatpump on a dedicated tariff) with its
|
|
512
|
+
* own electricity tariff, prices and grid fee.
|
|
513
|
+
*
|
|
514
|
+
* Use it to find out whether a cascade is active and which appliances are
|
|
515
|
+
* behind it, and to read or supply the cascade's tariff, prices and grid
|
|
516
|
+
* fee — appliances behind an active cascade are billed on those instead of
|
|
517
|
+
* on {@link useElectricityTariff} and {@link useGridFee}.
|
|
518
|
+
* @returns The Cascade API instance
|
|
519
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `ElectricityTariff`,
|
|
520
|
+
* `GridFeeRegister` or `GridFeeUse` permission required by the
|
|
521
|
+
* called method is not granted.
|
|
522
|
+
*/
|
|
523
|
+
useCascade() {
|
|
524
|
+
return this.energyAppSdk.useCascade();
|
|
525
|
+
}
|
|
509
526
|
/**
|
|
510
527
|
* Gets the current SDK version.
|
|
511
528
|
* @returns The semantic version string of the SDK
|
|
@@ -50,6 +50,7 @@ import { EnergyAppDeviceTest } from "./packages/energy-app-device-test.cjs";
|
|
|
50
50
|
import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.cjs";
|
|
51
51
|
import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.cjs";
|
|
52
52
|
import { EnergyAppCommandLog } from "./packages/energy-app-command-log.cjs";
|
|
53
|
+
import { EnergyAppCascade } from "./packages/energy-app-cascade.cjs";
|
|
53
54
|
import { UseFetchOptions } from "./types/enyo-fetch.cjs";
|
|
54
55
|
/**
|
|
55
56
|
* Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
|
|
@@ -426,6 +427,21 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
|
|
|
426
427
|
* permission is not granted.
|
|
427
428
|
*/
|
|
428
429
|
useCommandLog(): EnergyAppCommandLog;
|
|
430
|
+
/**
|
|
431
|
+
* Gets the Cascade API for the site's meter cascade: a second meter behind
|
|
432
|
+
* the primary meter (e.g. for a heatpump on a dedicated tariff) with its
|
|
433
|
+
* own electricity tariff, prices and grid fee.
|
|
434
|
+
*
|
|
435
|
+
* Use it to find out whether a cascade is active and which appliances are
|
|
436
|
+
* behind it, and to read or supply the cascade's tariff, prices and grid
|
|
437
|
+
* fee — appliances behind an active cascade are billed on those instead of
|
|
438
|
+
* on {@link useElectricityTariff} and {@link useGridFee}.
|
|
439
|
+
* @returns The Cascade API instance
|
|
440
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `ElectricityTariff`,
|
|
441
|
+
* `GridFeeRegister` or `GridFeeUse` permission required by the
|
|
442
|
+
* called method is not granted.
|
|
443
|
+
*/
|
|
444
|
+
useCascade(): EnergyAppCascade;
|
|
429
445
|
/**
|
|
430
446
|
* Gets the current SDK version.
|
|
431
447
|
* @returns The semantic version string of the SDK
|
|
@@ -49,6 +49,7 @@ import { EnergyAppDeviceTest } from "./packages/energy-app-device-test.cjs";
|
|
|
49
49
|
import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.cjs";
|
|
50
50
|
import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.cjs";
|
|
51
51
|
import { EnergyAppCommandLog } from "./packages/energy-app-command-log.cjs";
|
|
52
|
+
import { EnergyAppCascade } from "./packages/energy-app-cascade.cjs";
|
|
52
53
|
import { UseFetchOptions } from "./types/enyo-fetch.cjs";
|
|
53
54
|
export declare enum EnergyAppStateEnum {
|
|
54
55
|
Launching = "launching",
|
|
@@ -181,4 +182,6 @@ export interface EnyoEnergyAppSdk {
|
|
|
181
182
|
useGridFee: () => EnergyAppGridFee;
|
|
182
183
|
/** Get the Command Log API for recording which register, configuration key or message this app wrote to an appliance, and reading those commands back */
|
|
183
184
|
useCommandLog: () => EnergyAppCommandLog;
|
|
185
|
+
/** Get the Cascade API for the meter behind the primary meter: whether it is active, which appliances are behind it, and its own electricity tariff, prices and grid fee */
|
|
186
|
+
useCascade: () => EnergyAppCascade;
|
|
184
187
|
}
|
|
@@ -531,6 +531,78 @@ class ApplianceManager {
|
|
|
531
531
|
await this.energyApp.useAppliances().save(updated, applianceId);
|
|
532
532
|
await this.primeCacheFromSdk(applianceId);
|
|
533
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Patches a single heating circuit of a heatpump — e.g. to publish a changed
|
|
536
|
+
* heating curve or time program — without resending the whole
|
|
537
|
+
* `heatingCircuits` array.
|
|
538
|
+
*
|
|
539
|
+
* {@link updateAppliance} merges `heatpump` metadata only one level deep,
|
|
540
|
+
* so passing `heatingCircuits` there replaces every circuit. This helper
|
|
541
|
+
* reads the stored array, shallow-merges `patch` into the circuit with the
|
|
542
|
+
* given `index` (appending a new circuit when none exists), keeps the
|
|
543
|
+
* array sorted by index, and saves. Other circuits are left untouched.
|
|
544
|
+
*
|
|
545
|
+
* Does nothing when the appliance does not exist.
|
|
546
|
+
*
|
|
547
|
+
* @param applianceId The ID of the heatpump appliance
|
|
548
|
+
* @param index The `index` of the heating circuit to patch
|
|
549
|
+
* @param patch The circuit fields to set; fields not given keep their stored value
|
|
550
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
551
|
+
*/
|
|
552
|
+
async updateHeatpumpHeatingCircuit(applianceId, index, patch) {
|
|
553
|
+
this.throwIfDisposed();
|
|
554
|
+
const appliance = await this.energyApp.useAppliances().getById(applianceId);
|
|
555
|
+
if (!appliance)
|
|
556
|
+
return;
|
|
557
|
+
const circuits = [...(appliance.heatpump?.heatingCircuits ?? [])];
|
|
558
|
+
const position = circuits.findIndex(c => c.index === index);
|
|
559
|
+
if (position >= 0) {
|
|
560
|
+
circuits[position] = { ...circuits[position], ...patch, index };
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
circuits.push({ ...patch, index });
|
|
564
|
+
circuits.sort((a, b) => a.index - b.index);
|
|
565
|
+
}
|
|
566
|
+
await this.energyApp.useAppliances().save(this.mergeApplianceData(appliance, { heatpump: { heatingCircuits: circuits } }), applianceId);
|
|
567
|
+
await this.primeCacheFromSdk(applianceId);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Patches a single domestic-hot-water zone of a heatpump — e.g. to publish
|
|
571
|
+
* a changed time program — without resending the whole `domesticHotWater`
|
|
572
|
+
* array. Works like {@link updateHeatpumpHeatingCircuit}.
|
|
573
|
+
*
|
|
574
|
+
* Appending a zone that does not exist yet requires `targetTemperatureC`
|
|
575
|
+
* in `patch`, because a DHW zone cannot be stored without it.
|
|
576
|
+
*
|
|
577
|
+
* Does nothing when the appliance does not exist.
|
|
578
|
+
*
|
|
579
|
+
* @param applianceId The ID of the heatpump appliance
|
|
580
|
+
* @param index The `index` of the DHW zone to patch
|
|
581
|
+
* @param patch The zone fields to set; fields not given keep their stored value
|
|
582
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
583
|
+
* @throws {Error} when the zone does not exist and `patch` lacks `targetTemperatureC`
|
|
584
|
+
*/
|
|
585
|
+
async updateHeatpumpDomesticHotWater(applianceId, index, patch) {
|
|
586
|
+
this.throwIfDisposed();
|
|
587
|
+
const appliance = await this.energyApp.useAppliances().getById(applianceId);
|
|
588
|
+
if (!appliance)
|
|
589
|
+
return;
|
|
590
|
+
const zones = [...(appliance.heatpump?.domesticHotWater ?? [])];
|
|
591
|
+
const position = zones.findIndex(z => z.index === index);
|
|
592
|
+
if (position >= 0) {
|
|
593
|
+
zones[position] = { ...zones[position], ...patch, index };
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
const { targetTemperatureC } = patch;
|
|
597
|
+
if (targetTemperatureC === undefined) {
|
|
598
|
+
throw new Error(`Cannot add domestic hot water zone ${index} to appliance ${applianceId} without targetTemperatureC`);
|
|
599
|
+
}
|
|
600
|
+
zones.push({ ...patch, index, targetTemperatureC });
|
|
601
|
+
zones.sort((a, b) => a.index - b.index);
|
|
602
|
+
}
|
|
603
|
+
await this.energyApp.useAppliances().save(this.mergeApplianceData(appliance, { heatpump: { domesticHotWater: zones } }), applianceId);
|
|
604
|
+
await this.primeCacheFromSdk(applianceId);
|
|
605
|
+
}
|
|
534
606
|
/**
|
|
535
607
|
* Refetches an appliance from the SDK and reflects the result in the
|
|
536
608
|
* 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, 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 { EnyoHeatpumpApplianceMetadata } from "../../types/enyo-heatpump-appliance.cjs";
|
|
6
|
+
import type { EnyoHeatpumpApplianceDomesticHotWater, EnyoHeatpumpApplianceHeatingCircuit, 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";
|
|
@@ -329,6 +329,42 @@ export declare class ApplianceManager {
|
|
|
329
329
|
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
330
330
|
*/
|
|
331
331
|
updateAppliance(applianceId: string, attributes: Partial<PartialEnyoAppliance>): Promise<void>;
|
|
332
|
+
/**
|
|
333
|
+
* Patches a single heating circuit of a heatpump — e.g. to publish a changed
|
|
334
|
+
* heating curve or time program — without resending the whole
|
|
335
|
+
* `heatingCircuits` array.
|
|
336
|
+
*
|
|
337
|
+
* {@link updateAppliance} merges `heatpump` metadata only one level deep,
|
|
338
|
+
* so passing `heatingCircuits` there replaces every circuit. This helper
|
|
339
|
+
* reads the stored array, shallow-merges `patch` into the circuit with the
|
|
340
|
+
* given `index` (appending a new circuit when none exists), keeps the
|
|
341
|
+
* array sorted by index, and saves. Other circuits are left untouched.
|
|
342
|
+
*
|
|
343
|
+
* Does nothing when the appliance does not exist.
|
|
344
|
+
*
|
|
345
|
+
* @param applianceId The ID of the heatpump appliance
|
|
346
|
+
* @param index The `index` of the heating circuit to patch
|
|
347
|
+
* @param patch The circuit fields to set; fields not given keep their stored value
|
|
348
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
349
|
+
*/
|
|
350
|
+
updateHeatpumpHeatingCircuit(applianceId: string, index: number, patch: Partial<Omit<EnyoHeatpumpApplianceHeatingCircuit, 'index'>>): Promise<void>;
|
|
351
|
+
/**
|
|
352
|
+
* Patches a single domestic-hot-water zone of a heatpump — e.g. to publish
|
|
353
|
+
* a changed time program — without resending the whole `domesticHotWater`
|
|
354
|
+
* array. Works like {@link updateHeatpumpHeatingCircuit}.
|
|
355
|
+
*
|
|
356
|
+
* Appending a zone that does not exist yet requires `targetTemperatureC`
|
|
357
|
+
* in `patch`, because a DHW zone cannot be stored without it.
|
|
358
|
+
*
|
|
359
|
+
* Does nothing when the appliance does not exist.
|
|
360
|
+
*
|
|
361
|
+
* @param applianceId The ID of the heatpump appliance
|
|
362
|
+
* @param index The `index` of the DHW zone to patch
|
|
363
|
+
* @param patch The zone fields to set; fields not given keep their stored value
|
|
364
|
+
* @throws {ApplianceManagerDisposedError} when called after {@link dispose}
|
|
365
|
+
* @throws {Error} when the zone does not exist and `patch` lacks `targetTemperatureC`
|
|
366
|
+
*/
|
|
367
|
+
updateHeatpumpDomesticHotWater(applianceId: string, index: number, patch: Partial<Omit<EnyoHeatpumpApplianceDomesticHotWater, 'index'>>): Promise<void>;
|
|
332
368
|
/**
|
|
333
369
|
* Refetches an appliance from the SDK and reflects the result in the
|
|
334
370
|
* cache. Used by mutating methods to keep cache reads consistent with
|