@enyo-energy/energy-app-sdk 1.27.0 → 1.29.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 +27 -2
- package/dist/cjs/implementations/energy-manager-settings/energy-manager-settings-validators.cjs +89 -3
- package/dist/cjs/implementations/energy-manager-settings/energy-manager-settings-validators.d.cts +15 -0
- package/dist/cjs/types/enyo-data-bus-value.cjs +90 -1
- package/dist/cjs/types/enyo-data-bus-value.d.cts +216 -2
- package/dist/cjs/types/enyo-electricity-tariff.d.cts +13 -0
- package/dist/cjs/types/enyo-energy-manager-settings.cjs +48 -2
- package/dist/cjs/types/enyo-energy-manager-settings.d.cts +158 -2
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/implementations/energy-manager-settings/energy-manager-settings-validators.d.ts +15 -0
- package/dist/implementations/energy-manager-settings/energy-manager-settings-validators.js +89 -3
- package/dist/types/enyo-data-bus-value.d.ts +216 -2
- package/dist/types/enyo-data-bus-value.js +90 -1
- package/dist/types/enyo-electricity-tariff.d.ts +13 -0
- package/dist/types/enyo-energy-manager-settings.d.ts +158 -2
- package/dist/types/enyo-energy-manager-settings.js +48 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1646,10 +1646,14 @@ A closed, SDK-defined set of user-facing controls the active energy manager hono
|
|
|
1646
1646
|
| `BatteryEvDischargeMode` | `fixed-wh` \| `soc-limit` \| `intelligent` \| `block-discharge` \| `unmanaged` |
|
|
1647
1647
|
| `BatteryEvDischargeFixedWh` | number, **Wh** (only under `fixed-wh`) |
|
|
1648
1648
|
| `BatteryEvDischargeSocLimitPercent` | number, **%** floor (only under `soc-limit`) |
|
|
1649
|
+
| `BatteryPvPriorityTargetSocPercent` | number \| `null`, **%** target the battery is charged to from PV before other surplus consumers (absent / `null` = disabled) |
|
|
1650
|
+
| `BatteryDischargeBlockAboveHomeConsumptionW` | number \| `null`, **W** home consumption above which the battery may not discharge (absent / `null` = disabled) |
|
|
1649
1651
|
| `HeatpumpControl` | boolean |
|
|
1650
1652
|
| `HeatingRodControl` | boolean |
|
|
1651
1653
|
| `HeatingRodMode` | `pv-surplus-only` \| `boost` |
|
|
1654
|
+
| `HeatingRodBatteryDischargeSocLimitPercent` | number \| `null`, **%** floor the battery may feed the heating rod down to (absent / `null` = disabled) |
|
|
1652
1655
|
| `ChargerControl` | boolean |
|
|
1656
|
+
| `GridOptimizedEvCharging` | boolean, off by default — cost-optimised charging on a static / time-of-use tariff avoids high grid load (by EPEX spot) |
|
|
1653
1657
|
| `DefaultChargeMode` | `EnyoChargeModeEnum` |
|
|
1654
1658
|
| `PriceLimitMode` | `ct-per-kwh` \| `cheapest-share` (absent = no ceiling) |
|
|
1655
1659
|
| `PriceLimitCtPerKwh` | number, **ct/kWh** (`7` = 7 ct/kWh; only under `ct-per-kwh`) |
|
|
@@ -1694,7 +1698,11 @@ Three things to get right:
|
|
|
1694
1698
|
|
|
1695
1699
|
- **Absent is not `false`.** `undefined` means unsupported or never chosen; `false` means the user switched it off. Check `supported` first, then the value — collapsing the two steers hardware someone deliberately disabled.
|
|
1696
1700
|
- **How the battery may feed a charging EV is one mode plus its parameter.** `BatteryEvDischargeMode` picks the strategy; `BatteryEvDischargeFixedWh` and `BatteryEvDischargeSocLimitPercent` are read only under their own mode. `block-discharge` (actively hold the battery back) and `unmanaged` (do not intervene either way) are **not** synonyms.
|
|
1697
|
-
- **
|
|
1701
|
+
- **Battery-first PV surplus is a charging target, not a floor.** `BatteryPvPriorityTargetSocPercent` (`40` = charge the battery to 40 % first) only holds back consumers that run *on surplus* — an EV on PV, a `pv-surplus-only` heating rod, heat pump surplus boosts, surplus automations. Grid-backed modes and explicit user requests are unaffected. It is independent of `BatteryEvDischargeSocLimitPercent`, which is a floor for discharging into the car. Disabled while absent or `null`; write `null` to switch it off again.
|
|
1702
|
+
- **The heating rod may draw from the battery only down to a floor, and only when asked.** `HeatingRodBatteryDischargeSocLimitPercent` (`80` = the rod may use the battery while it holds more than 80 %) is disabled while absent or `null`. It applies under both heating rod modes — under `pv-surplus-only` the battery above the floor counts as supply alongside PV, still never a grid import. It needs `batteryControl` to be enforced, and should not lie below `BatteryPvPriorityTargetSocPercent`; the validator warns on both. A consumer held back by it carries the command reason `BatteryPvPriority` (category `PvSurplus`) with `context.batteryPvPriority.targetSocPercent` and `socPercent`.
|
|
1703
|
+
- **The battery can be blocked from discharging under a heavy load.** `BatteryDischargeBlockAboveHomeConsumptionW` (`11000` = no discharge while the house draws more than 11 kW) keeps a sauna or an oven from emptying the battery in minutes. It is in **watts** — the validator warns below 100 W, which catches a kW value. Home consumption means the household plus every appliance the energy manager does **not** track — the same quantity as `homeConsumptionW` in the home consumption timeseries. Tracked appliances (wallbox, heat pump, heating rod, …) and the battery's own charging are excluded, so a charging car alone never trips the block. The block wins over the EV and heating rod discharge permissions; charging is unaffected. Disabled while absent or `null`. A battery held by it carries `HomeConsumptionHigh` with `context.batteryDischargeBlock.thresholdW` and `powerW` set to the current consumption.
|
|
1704
|
+
- **Grid-optimised EV charging is about the grid, not the bill.** `GridOptimizedEvCharging` adds no charge mode. It only affects `cost-optimized` sessions while the tariff is static or time-of-use (no effect under a dynamic one): the grid energy PV does not cover is placed in the hours with the lowest EPEX spot price before the deadline — the spot price as a proxy for grid load — instead of as late as possible or in the tariff's cheap windows. PV still comes first, the deadline still holds, and a session's price ceiling still applies against the customer's own tariff. On a time-of-use tariff a low-load hour can be a more expensive one; that is the trade the owner opts into.
|
|
1705
|
+
- **Fifteen settings are gated by another** (`heatingRodMode` needs `heatingRodControl`, `priceLimitCtPerKwh` only applies under `priceLimitMode: 'ct-per-kwh'`, …). The tree is exported as `ENERGY_MANAGER_SETTING_DEPENDENCIES`, with two helpers over it: `getEnergyManagerSettingDependency(setting)` returns the direct gate and the value it must hold (or `null` for an ungated root), and `isEnergyManagerSettingActive(setting, values)` walks the whole chain — the gates are now two deep, so a Wh budget is live only when the mode is `fixed-wh` *and* battery control is on.
|
|
1698
1706
|
- **`priceLimitCtPerKwh` is in cents**, unlike the SDK's machine-readable price fields (`electricityPricePerKwh`, EUR/kWh). Divide by 100 when comparing. `validateEnergyManagerSettingsState()` warns on values outside a plausible ct band, which catches the mix-up.
|
|
1699
1707
|
- **A price ceiling is one mode plus its parameter**, the same shape as the battery-to-EV one. `PriceLimitMode` picks how the ceiling is expressed — an absolute `ct-per-kwh` or a relative `cheapest-share` — and each value is read only under its own mode; the mode being absent means no ceiling at all. A relative ceiling ranks the price intervals *known* for the day and treats the cheapest `n` % as importable (the same rule as the `cheapest-share-of-day` automation trigger), so its effective threshold moves when tomorrow's prices publish — re-evaluate it rather than resolving it once.
|
|
1700
1708
|
|
|
@@ -1759,8 +1767,17 @@ An entry may carry an optional `gridFeeGrossPerKwh`: the gross grid fee **contai
|
|
|
1759
1767
|
`pricePerKwh`, in currency units per kWh. It is a breakdown only — `pricePerKwh` is always the
|
|
1760
1768
|
total, so never compute `pricePerKwh + gridFeeGrossPerKwh`.
|
|
1761
1769
|
|
|
1770
|
+
It may also carry an optional `spotPricePerKwh`: the net wholesale (e.g. EPEX day-ahead) price the
|
|
1771
|
+
interval's price is based on, as the provider reports it — in currency units per kWh, possibly negative.
|
|
1772
|
+
Informational too: never add it to `pricePerKwh`.
|
|
1773
|
+
|
|
1762
1774
|
```typescript
|
|
1763
|
-
entries.push({
|
|
1775
|
+
entries.push({
|
|
1776
|
+
timestampIso: '2026-05-23T10:00:00Z',
|
|
1777
|
+
pricePerKwh: 0.31,
|
|
1778
|
+
gridFeeGrossPerKwh: 0.09,
|
|
1779
|
+
spotPricePerKwh: 0.084,
|
|
1780
|
+
});
|
|
1764
1781
|
```
|
|
1765
1782
|
|
|
1766
1783
|
**Calling `setTariff` is the activation signal.** Return `AuthenticationRequired` or
|
|
@@ -4171,10 +4188,18 @@ New reason types came with this surface, so a skipped row can say *why* instead
|
|
|
4171
4188
|
|
|
4172
4189
|
**Battery vs. charging car.** While a car charges, the house battery is either held out of it or allowed to help, according to the owner's `batteryEvDischargeMode`. Two reason types (category `BatteryState`) say which: `BatteryReservedFromEv` for the hold (`Discharge 0`) and `BatterySupportsEvCharging` for the release (`mode=Auto`). Both carry `context.batteryToEv` (`EnyoDataBusCommandReasonBatteryToEvContext`) with the owner's `mode`, the hold `trigger` (`EnyoBatteryToEvHoldTriggerEnum`: `OwnerBlocked`, `SocLimitReached`, `AllowanceSpent`, `NoMeasurement`), `socLimitPercent`, `allowanceWh` and `remainingWh`. The energy manager re-checks a hold periodically by briefly releasing the battery; that re-check sets `batteryToEv.probe: true` and should not be listed as its own entry in a command history.
|
|
4173
4190
|
|
|
4191
|
+
**Battery first on PV surplus.** When a surplus consumer (EV on PV, heating rod, heat pump boost, surplus automation) is held back or reduced because the battery is charged first up to the owner's `batteryPvPriorityTargetSocPercent`, the energy manager states `BatteryPvPriority` (category `PvSurplus`) on that consumer: `context.batteryPvPriority.targetSocPercent` for the target, `socPercent` for the pack's current SoC and `powerW` for the surplus the battery is taking — e.g. "The battery charges to 40 % first (now 28 %)."
|
|
4192
|
+
|
|
4193
|
+
**Grid-optimised charging.** A cost-optimised session placed in, or waiting for, a low-load hour because of `gridOptimizedEvCharging` carries `GridLoadOptimized` (category `Schedule`) with `context.placement` (chosen window and deadline) and `context.gridLoad` (`epexPricePerKwh`, `currency`). The EPEX price is a grid-load proxy, not what the customer pays, so the text must not promise a saving — e.g. "Charging from 02:00, when the grid is less busy — ready by 07:00."
|
|
4194
|
+
|
|
4195
|
+
**Battery vs. heating rod.** When the owner set `heatingRodBatteryDischargeSocLimitPercent`, two reason types (category `BatteryState`) say whether the battery feeds the rod: `BatterySupportsHeatingRod` while it may (with `powerW` for its contribution), `BatteryReservedFromHeatingRod` once it reaches the floor. Both carry `context.batteryToHeatingRod.socLimitPercent` and `socPercent` — e.g. "Heating rod no longer runs from the battery — it is at your 80 % limit."
|
|
4196
|
+
|
|
4174
4197
|
**Short-cycling protection.** When an appliance is kept running (or kept off) to honour its minimum on/off time, state `DeviceProtection` with `context.switching` (`EnyoDataBusCommandReasonSwitchingContext`): `hold` (`EnyoSwitchingProtectionHoldEnum.KeptOn` / `KeptOff`), `minOnSeconds`, `minOffSeconds` and `untilIso`. The text can then say "Kept running for another 4 minutes to protect the compressor" instead of a generic "Protecting the device".
|
|
4175
4198
|
|
|
4176
4199
|
A hold that keeps an appliance **off** after a stop has its own type, `RestartDelay` (category `DeviceProtection`): set `context.switching` with `hold: KeptOff`, `minOffSeconds` and `untilIso` (when it may restart), and `powerW` to the power waiting for it — e.g. "Short pause after switching off — charging resumes at 13:15 so the car isn't switched on and off too often."
|
|
4177
4200
|
|
|
4201
|
+
**Fluctuating PV surplus.** When the surplus jumps around (clouds passing) and the energy manager holds an appliance's power steady instead of following every dip, state `PvSurplusFluctuating` (category `PvSurplus`) with `powerW` set to the power being held — e.g. "Charging power held steady while clouds pass." / "Ladeleistung wird gehalten, solange Wolken durchziehen." It is a smoothing decision, told apart from `PvSurplusAvailable` / `PvSurplusUnavailable`.
|
|
4202
|
+
|
|
4178
4203
|
## Dynamic Grid Fees & Tariff Bonuses
|
|
4179
4204
|
|
|
4180
4205
|
An electricity price is rarely one number. It is the energy price, plus the grid operator's network
|
package/dist/cjs/implementations/energy-manager-settings/energy-manager-settings-validators.cjs
CHANGED
|
@@ -67,6 +67,15 @@ const PRICE_LIMIT_MAX_CT = 200;
|
|
|
67
67
|
* warned about for duplicating mode `block-discharge` rather than for being a unit slip.
|
|
68
68
|
*/
|
|
69
69
|
const DISCHARGE_BUDGET_MIN_PLAUSIBLE_WH = 100;
|
|
70
|
+
/**
|
|
71
|
+
* Below this, a
|
|
72
|
+
* {@link EnergyManagerSettingValues.batteryDischargeBlockAboveHomeConsumptionW}
|
|
73
|
+
* threshold is almost certainly kW entered into a W field.
|
|
74
|
+
*
|
|
75
|
+
* `11` would block the battery above 11 W — that is, whenever the house draws
|
|
76
|
+
* anything — where 11 kW was plainly meant. `0` is caught by the same check.
|
|
77
|
+
*/
|
|
78
|
+
const HOME_CONSUMPTION_BLOCK_MIN_PLAUSIBLE_W = 100;
|
|
70
79
|
/** Every {@link EnergyManagerBatteryEvDischargeModeEnum} value. */
|
|
71
80
|
const EV_DISCHARGE_MODES = new Set(Object.values(enyo_energy_manager_settings_js_1.EnergyManagerBatteryEvDischargeModeEnum));
|
|
72
81
|
/** Value enums checked per field, for the enum-valued settings. */
|
|
@@ -139,6 +148,21 @@ function gateValue(values, setting) {
|
|
|
139
148
|
* duplicates mode `block-discharge`, the second catches a kWh value in a Wh field).
|
|
140
149
|
* - `batteryEvDischargeSocLimitPercent` is a finite number in 0…100 (**error**),
|
|
141
150
|
* and not `100` (**warning** — a floor nothing can discharge past).
|
|
151
|
+
* - `batteryPvPriorityTargetSocPercent`, unless `null` (disabled), is a finite
|
|
152
|
+
* number in 0…100 (**error**), and neither `0` (**warning** — no priority at
|
|
153
|
+
* all, write `null`) nor `100` (**warning** — charging tapers near full, so
|
|
154
|
+
* surplus consumers can be held back for hours).
|
|
155
|
+
* - `heatingRodBatteryDischargeSocLimitPercent`, unless `null` (disabled), is a
|
|
156
|
+
* finite number in 0…100 (**error**), and not `100` (**warning** — a floor
|
|
157
|
+
* nothing can discharge past). Set while `batteryControl` is not `true`
|
|
158
|
+
* (**warning** — the floor cannot be enforced), or below
|
|
159
|
+
* `batteryPvPriorityTargetSocPercent` (**warning** — the rod drains what the
|
|
160
|
+
* PV priority refills).
|
|
161
|
+
* - `batteryDischargeBlockAboveHomeConsumptionW`, unless `null` (disabled), is
|
|
162
|
+
* a finite non-negative number (**error**), and not below 100 W (**warning**
|
|
163
|
+
* — catches a kW value in a W field; the battery would be blocked almost
|
|
164
|
+
* always).
|
|
165
|
+
* - `null` counts as unset for the "stored" checks below.
|
|
142
166
|
* - A parameter belonging to a mode other than the selected one is flagged by
|
|
143
167
|
* the gate check below, since {@link ENERGY_MANAGER_SETTING_DEPENDENCIES}
|
|
144
168
|
* gates each on its own mode.
|
|
@@ -184,7 +208,7 @@ function validateEnergyManagerSettingsState(state) {
|
|
|
184
208
|
const values = state.values ?? {};
|
|
185
209
|
// ── value formats ──────────────────────────────────────────────────────
|
|
186
210
|
for (const key of ['batteryControl', 'batteryChargeFromGrid', 'heatpumpControl',
|
|
187
|
-
'heatingRodControl', 'chargerControl']) {
|
|
211
|
+
'heatingRodControl', 'chargerControl', 'gridOptimizedEvCharging']) {
|
|
188
212
|
if (values[key] !== undefined && typeof values[key] !== 'boolean') {
|
|
189
213
|
errors.push(`\`${key}\` must be a boolean when set.`);
|
|
190
214
|
}
|
|
@@ -271,6 +295,67 @@ function validateEnergyManagerSettingsState(state) {
|
|
|
271
295
|
'states that intent directly.');
|
|
272
296
|
}
|
|
273
297
|
}
|
|
298
|
+
if (values.batteryPvPriorityTargetSocPercent !== undefined
|
|
299
|
+
&& values.batteryPvPriorityTargetSocPercent !== null) {
|
|
300
|
+
const target = values.batteryPvPriorityTargetSocPercent;
|
|
301
|
+
if (typeof target !== 'number' || !Number.isFinite(target)) {
|
|
302
|
+
errors.push('`batteryPvPriorityTargetSocPercent` must be a finite number when set.');
|
|
303
|
+
}
|
|
304
|
+
else if (target < 0 || target > 100) {
|
|
305
|
+
errors.push(`\`batteryPvPriorityTargetSocPercent\` is ${target}; it is a percentage and must lie ` +
|
|
306
|
+
'between 0 and 100.');
|
|
307
|
+
}
|
|
308
|
+
else if (target === 0) {
|
|
309
|
+
warnings.push('`batteryPvPriorityTargetSocPercent` is 0, which gives the battery no priority at all — ' +
|
|
310
|
+
'write `null` to say that outright.');
|
|
311
|
+
}
|
|
312
|
+
else if (target === 100) {
|
|
313
|
+
warnings.push('`batteryPvPriorityTargetSocPercent` is 100. Charging tapers as the battery nears full, ' +
|
|
314
|
+
'so PV-surplus consumers may be held back for hours waiting for the last percent.');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (values.batteryDischargeBlockAboveHomeConsumptionW !== undefined
|
|
318
|
+
&& values.batteryDischargeBlockAboveHomeConsumptionW !== null) {
|
|
319
|
+
const threshold = values.batteryDischargeBlockAboveHomeConsumptionW;
|
|
320
|
+
if (typeof threshold !== 'number' || !Number.isFinite(threshold)) {
|
|
321
|
+
errors.push('`batteryDischargeBlockAboveHomeConsumptionW` must be a finite number or null when set.');
|
|
322
|
+
}
|
|
323
|
+
else if (threshold < 0) {
|
|
324
|
+
errors.push(`\`batteryDischargeBlockAboveHomeConsumptionW\` is ${threshold}; a consumption threshold ` +
|
|
325
|
+
'cannot be negative.');
|
|
326
|
+
}
|
|
327
|
+
else if (threshold < HOME_CONSUMPTION_BLOCK_MIN_PLAUSIBLE_W) {
|
|
328
|
+
warnings.push(`\`batteryDischargeBlockAboveHomeConsumptionW\` is ${threshold} W, so the battery would be ` +
|
|
329
|
+
'blocked almost always — this field is in watts, not kilowatts.');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (values.heatingRodBatteryDischargeSocLimitPercent !== undefined
|
|
333
|
+
&& values.heatingRodBatteryDischargeSocLimitPercent !== null) {
|
|
334
|
+
const floor = values.heatingRodBatteryDischargeSocLimitPercent;
|
|
335
|
+
if (typeof floor !== 'number' || !Number.isFinite(floor)) {
|
|
336
|
+
errors.push('`heatingRodBatteryDischargeSocLimitPercent` must be a finite number or null when set.');
|
|
337
|
+
}
|
|
338
|
+
else if (floor < 0 || floor > 100) {
|
|
339
|
+
errors.push(`\`heatingRodBatteryDischargeSocLimitPercent\` is ${floor}; it is a percentage and must lie ` +
|
|
340
|
+
'between 0 and 100.');
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
if (floor === 100) {
|
|
344
|
+
warnings.push('`heatingRodBatteryDischargeSocLimitPercent` is 100, a floor the battery is already at, ' +
|
|
345
|
+
'so the rod can never draw from it — write `null` to disable it outright.');
|
|
346
|
+
}
|
|
347
|
+
if (values.batteryControl !== true) {
|
|
348
|
+
warnings.push('`heatingRodBatteryDischargeSocLimitPercent` is set, but `batteryControl` is not true — ' +
|
|
349
|
+
'the energy manager cannot hold the battery at the floor.');
|
|
350
|
+
}
|
|
351
|
+
const priority = values.batteryPvPriorityTargetSocPercent;
|
|
352
|
+
if (typeof priority === 'number' && Number.isFinite(priority) && floor < priority) {
|
|
353
|
+
warnings.push(`\`heatingRodBatteryDischargeSocLimitPercent\` (${floor} %) is below ` +
|
|
354
|
+
`\`batteryPvPriorityTargetSocPercent\` (${priority} %) — the rod drains the battery ` +
|
|
355
|
+
'into a range the PV priority then refills.');
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
274
359
|
if (values.costOptimizedTargetTime !== undefined) {
|
|
275
360
|
if (typeof values.costOptimizedTargetTime !== 'string'
|
|
276
361
|
|| !TIME_RE.test(values.costOptimizedTargetTime)) {
|
|
@@ -290,7 +375,7 @@ function validateEnergyManagerSettingsState(state) {
|
|
|
290
375
|
// ── stored but unsupported ─────────────────────────────────────────────
|
|
291
376
|
for (const setting of Object.values(enyo_energy_manager_settings_js_1.EnergyManagerSettingEnum)) {
|
|
292
377
|
const keys = enyo_energy_manager_settings_js_1.ENERGY_MANAGER_SETTING_VALUE_KEYS[setting];
|
|
293
|
-
const held = keys.filter((key) => values[key] !== undefined);
|
|
378
|
+
const held = keys.filter((key) => values[key] !== undefined && values[key] !== null);
|
|
294
379
|
if (held.length && !supported.has(setting)) {
|
|
295
380
|
warnings.push(`"${setting}" holds a value (${held.join(', ')}) but is not in \`supported\` — ` +
|
|
296
381
|
'the energy manager will ignore it.');
|
|
@@ -301,7 +386,8 @@ function validateEnergyManagerSettingsState(state) {
|
|
|
301
386
|
if (!dependency)
|
|
302
387
|
continue;
|
|
303
388
|
const key = setting;
|
|
304
|
-
const held = enyo_energy_manager_settings_js_1.ENERGY_MANAGER_SETTING_VALUE_KEYS[key]
|
|
389
|
+
const held = enyo_energy_manager_settings_js_1.ENERGY_MANAGER_SETTING_VALUE_KEYS[key]
|
|
390
|
+
.some((k) => values[k] !== undefined && values[k] !== null);
|
|
305
391
|
if (held && gateValue(values, dependency.requires) !== dependency.equals) {
|
|
306
392
|
warnings.push(`"${key}" holds a value, but it only takes effect while "${dependency.requires}" ` +
|
|
307
393
|
`is ${JSON.stringify(dependency.equals)} — it currently cannot.`);
|
package/dist/cjs/implementations/energy-manager-settings/energy-manager-settings-validators.d.cts
CHANGED
|
@@ -65,6 +65,21 @@ export interface EnergyManagerSettingsValidationResult {
|
|
|
65
65
|
* duplicates mode `block-discharge`, the second catches a kWh value in a Wh field).
|
|
66
66
|
* - `batteryEvDischargeSocLimitPercent` is a finite number in 0…100 (**error**),
|
|
67
67
|
* and not `100` (**warning** — a floor nothing can discharge past).
|
|
68
|
+
* - `batteryPvPriorityTargetSocPercent`, unless `null` (disabled), is a finite
|
|
69
|
+
* number in 0…100 (**error**), and neither `0` (**warning** — no priority at
|
|
70
|
+
* all, write `null`) nor `100` (**warning** — charging tapers near full, so
|
|
71
|
+
* surplus consumers can be held back for hours).
|
|
72
|
+
* - `heatingRodBatteryDischargeSocLimitPercent`, unless `null` (disabled), is a
|
|
73
|
+
* finite number in 0…100 (**error**), and not `100` (**warning** — a floor
|
|
74
|
+
* nothing can discharge past). Set while `batteryControl` is not `true`
|
|
75
|
+
* (**warning** — the floor cannot be enforced), or below
|
|
76
|
+
* `batteryPvPriorityTargetSocPercent` (**warning** — the rod drains what the
|
|
77
|
+
* PV priority refills).
|
|
78
|
+
* - `batteryDischargeBlockAboveHomeConsumptionW`, unless `null` (disabled), is
|
|
79
|
+
* a finite non-negative number (**error**), and not below 100 W (**warning**
|
|
80
|
+
* — catches a kW value in a W field; the battery would be blocked almost
|
|
81
|
+
* always).
|
|
82
|
+
* - `null` counts as unset for the "stored" checks below.
|
|
68
83
|
* - A parameter belonging to a mode other than the selected one is flagged by
|
|
69
84
|
* the gate check below, since {@link ENERGY_MANAGER_SETTING_DEPENDENCIES}
|
|
70
85
|
* gates each on its own mode.
|
|
@@ -38,6 +38,19 @@ var EnyoDataBusCommandReasonTypeEnum;
|
|
|
38
38
|
* saying "elsewhere".
|
|
39
39
|
*/
|
|
40
40
|
EnyoDataBusCommandReasonTypeEnum["PvSurplusAllocatedElsewhere"] = "pv-surplus-allocated-elsewhere";
|
|
41
|
+
/**
|
|
42
|
+
* The PV surplus is fluctuating (e.g. clouds passing) and the energy manager
|
|
43
|
+
* deliberately holds the appliance's power steady instead of following
|
|
44
|
+
* every dip and peak — "Ladeleistung wird gehalten, solange Wolken
|
|
45
|
+
* durchziehen." / "Charging power held steady while clouds pass."
|
|
46
|
+
*
|
|
47
|
+
* Says the command is a smoothing decision, not a reaction to the surplus
|
|
48
|
+
* being there or gone: told apart from {@link PvSurplusAvailable} and
|
|
49
|
+
* {@link PvSurplusUnavailable}. Set {@link EnyoDataBusCommandReason.powerW}
|
|
50
|
+
* to the power being held. Category
|
|
51
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.PvSurplus}.
|
|
52
|
+
*/
|
|
53
|
+
EnyoDataBusCommandReasonTypeEnum["PvSurplusFluctuating"] = "pv-surplus-fluctuating";
|
|
41
54
|
/** Command issued because battery capacity is available */
|
|
42
55
|
EnyoDataBusCommandReasonTypeEnum["BatteryCapacityAvailable"] = "battery-capacity-available";
|
|
43
56
|
/** Command issued because battery capacity is unavailable */
|
|
@@ -82,7 +95,19 @@ var EnyoDataBusCommandReasonTypeEnum;
|
|
|
82
95
|
* {@link RestartDelay}.
|
|
83
96
|
*/
|
|
84
97
|
EnyoDataBusCommandReasonTypeEnum["DeviceProtection"] = "device-protection";
|
|
85
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Command issued because home consumption is high.
|
|
100
|
+
*
|
|
101
|
+
* When the energy manager holds the battery because home consumption — the
|
|
102
|
+
* household plus untracked appliances, tracked appliances excluded — is
|
|
103
|
+
* above the owner's `batteryDischargeBlockAboveHomeConsumptionW`, set
|
|
104
|
+
* {@link EnyoDataBusCommandReasonContext.batteryDischargeBlock} with the
|
|
105
|
+
* threshold, {@link EnyoDataBusCommandReason.powerW} to the current home
|
|
106
|
+
* consumption and category
|
|
107
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.BatteryState} — so the text
|
|
108
|
+
* can say "Battery paused: home consumption is 13.2 kW, above your 11 kW
|
|
109
|
+
* limit."
|
|
110
|
+
*/
|
|
86
111
|
EnyoDataBusCommandReasonTypeEnum["HomeConsumptionHigh"] = "home-consumption-high";
|
|
87
112
|
/** Command issued to optimize self-consumption */
|
|
88
113
|
EnyoDataBusCommandReasonTypeEnum["SelfConsumptionOptimization"] = "self-consumption-optimization";
|
|
@@ -229,6 +254,70 @@ var EnyoDataBusCommandReasonTypeEnum;
|
|
|
229
254
|
* the appliance from short-cycling and is not about price, sun or contention.
|
|
230
255
|
*/
|
|
231
256
|
EnyoDataBusCommandReasonTypeEnum["RestartDelay"] = "restart-delay";
|
|
257
|
+
/**
|
|
258
|
+
* A PV-surplus consumer (EV on PV, heating rod, heat pump surplus boost,
|
|
259
|
+
* surplus automation) is held back or reduced because the house battery is
|
|
260
|
+
* charged first, up to the owner's `batteryPvPriorityTargetSocPercent`.
|
|
261
|
+
*
|
|
262
|
+
* Stated by the energy manager's surplus allocation on the consumer that
|
|
263
|
+
* lost out, never on the battery itself. Set
|
|
264
|
+
* {@link EnyoDataBusCommandReasonContext.batteryPvPriority} with the
|
|
265
|
+
* target, {@link EnyoDataBusCommandReason.socPercent} to the pack's current
|
|
266
|
+
* SoC, and {@link EnyoDataBusCommandReason.powerW} to the surplus the
|
|
267
|
+
* battery is taking — so the text can say "The battery charges to 40 %
|
|
268
|
+
* first (now 28 %)."
|
|
269
|
+
*
|
|
270
|
+
* Told apart from {@link PvSurplusAllocatedElsewhere}: the surplus did not
|
|
271
|
+
* go to another appliance by the planner's choice, it went to the battery
|
|
272
|
+
* because the owner asked for it. Category
|
|
273
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.PvSurplus} — it is about who
|
|
274
|
+
* got the sun.
|
|
275
|
+
*/
|
|
276
|
+
EnyoDataBusCommandReasonTypeEnum["BatteryPvPriority"] = "battery-pv-priority";
|
|
277
|
+
/**
|
|
278
|
+
* The house battery is allowed to feed the heating rod, down to the
|
|
279
|
+
* owner's `heatingRodBatteryDischargeSocLimitPercent`.
|
|
280
|
+
*
|
|
281
|
+
* Set {@link EnyoDataBusCommandReasonContext.batteryToHeatingRod} with the
|
|
282
|
+
* floor, {@link EnyoDataBusCommandReason.socPercent} to the pack's current
|
|
283
|
+
* SoC and {@link EnyoDataBusCommandReason.powerW} to the power the battery
|
|
284
|
+
* contributes. Category
|
|
285
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.BatteryState}.
|
|
286
|
+
*/
|
|
287
|
+
EnyoDataBusCommandReasonTypeEnum["BatterySupportsHeatingRod"] = "battery-supports-heating-rod";
|
|
288
|
+
/**
|
|
289
|
+
* The house battery is held back from the heating rod because it has
|
|
290
|
+
* reached the owner's floor (`heatingRodBatteryDischargeSocLimitPercent`).
|
|
291
|
+
* The rod runs on PV surplus only — or, under boost, from the grid.
|
|
292
|
+
*
|
|
293
|
+
* The counterpart of {@link BatterySupportsHeatingRod}. Set
|
|
294
|
+
* {@link EnyoDataBusCommandReasonContext.batteryToHeatingRod} and
|
|
295
|
+
* {@link EnyoDataBusCommandReason.socPercent}. Told apart from
|
|
296
|
+
* {@link BatterySoCLow}: the pack is not low, it is at the floor the owner
|
|
297
|
+
* chose for the rod. Category
|
|
298
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.BatteryState}.
|
|
299
|
+
*/
|
|
300
|
+
EnyoDataBusCommandReasonTypeEnum["BatteryReservedFromHeatingRod"] = "battery-reserved-from-heating-rod";
|
|
301
|
+
/**
|
|
302
|
+
* A cost-optimised charging session was placed in — or is waiting for — a
|
|
303
|
+
* low-load hour on the grid, because the owner switched on
|
|
304
|
+
* `gridOptimizedEvCharging`. Covers both the "charging now" and the
|
|
305
|
+
* "waiting" side of the decision.
|
|
306
|
+
*
|
|
307
|
+
* About relieving the grid, not about the customer's price: on a static or
|
|
308
|
+
* time-of-use tariff the EPEX spot price that ranked the slot is not what
|
|
309
|
+
* the customer pays, so the text must not promise a saving — which is why
|
|
310
|
+
* this is its own type rather than
|
|
311
|
+
* {@link ElectricityPriceBelowThreshold} or {@link WaitingForCheaperSlot}.
|
|
312
|
+
*
|
|
313
|
+
* Set {@link EnyoDataBusCommandReasonContext.placement} with the chosen
|
|
314
|
+
* window and the session's deadline, and
|
|
315
|
+
* {@link EnyoDataBusCommandReasonContext.gridLoad} with the spot price
|
|
316
|
+
* behind it — so the text can say "Charging from 02:00, when the grid is
|
|
317
|
+
* less busy — ready by 07:00." Category
|
|
318
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.Schedule}.
|
|
319
|
+
*/
|
|
320
|
+
EnyoDataBusCommandReasonTypeEnum["GridLoadOptimized"] = "grid-load-optimized";
|
|
232
321
|
})(EnyoDataBusCommandReasonTypeEnum || (exports.EnyoDataBusCommandReasonTypeEnum = EnyoDataBusCommandReasonTypeEnum = {}));
|
|
233
322
|
/**
|
|
234
323
|
* Coarse, machine-readable grouping of why a data bus command was issued.
|
|
@@ -54,6 +54,19 @@ export declare enum EnyoDataBusCommandReasonTypeEnum {
|
|
|
54
54
|
* saying "elsewhere".
|
|
55
55
|
*/
|
|
56
56
|
PvSurplusAllocatedElsewhere = "pv-surplus-allocated-elsewhere",
|
|
57
|
+
/**
|
|
58
|
+
* The PV surplus is fluctuating (e.g. clouds passing) and the energy manager
|
|
59
|
+
* deliberately holds the appliance's power steady instead of following
|
|
60
|
+
* every dip and peak — "Ladeleistung wird gehalten, solange Wolken
|
|
61
|
+
* durchziehen." / "Charging power held steady while clouds pass."
|
|
62
|
+
*
|
|
63
|
+
* Says the command is a smoothing decision, not a reaction to the surplus
|
|
64
|
+
* being there or gone: told apart from {@link PvSurplusAvailable} and
|
|
65
|
+
* {@link PvSurplusUnavailable}. Set {@link EnyoDataBusCommandReason.powerW}
|
|
66
|
+
* to the power being held. Category
|
|
67
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.PvSurplus}.
|
|
68
|
+
*/
|
|
69
|
+
PvSurplusFluctuating = "pv-surplus-fluctuating",
|
|
57
70
|
/** Command issued because battery capacity is available */
|
|
58
71
|
BatteryCapacityAvailable = "battery-capacity-available",
|
|
59
72
|
/** Command issued because battery capacity is unavailable */
|
|
@@ -98,7 +111,19 @@ export declare enum EnyoDataBusCommandReasonTypeEnum {
|
|
|
98
111
|
* {@link RestartDelay}.
|
|
99
112
|
*/
|
|
100
113
|
DeviceProtection = "device-protection",
|
|
101
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Command issued because home consumption is high.
|
|
116
|
+
*
|
|
117
|
+
* When the energy manager holds the battery because home consumption — the
|
|
118
|
+
* household plus untracked appliances, tracked appliances excluded — is
|
|
119
|
+
* above the owner's `batteryDischargeBlockAboveHomeConsumptionW`, set
|
|
120
|
+
* {@link EnyoDataBusCommandReasonContext.batteryDischargeBlock} with the
|
|
121
|
+
* threshold, {@link EnyoDataBusCommandReason.powerW} to the current home
|
|
122
|
+
* consumption and category
|
|
123
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.BatteryState} — so the text
|
|
124
|
+
* can say "Battery paused: home consumption is 13.2 kW, above your 11 kW
|
|
125
|
+
* limit."
|
|
126
|
+
*/
|
|
102
127
|
HomeConsumptionHigh = "home-consumption-high",
|
|
103
128
|
/** Command issued to optimize self-consumption */
|
|
104
129
|
SelfConsumptionOptimization = "self-consumption-optimization",
|
|
@@ -244,7 +269,71 @@ export declare enum EnyoDataBusCommandReasonTypeEnum {
|
|
|
244
269
|
* {@link EnyoDataBusCommandReasonCategoryEnum.DeviceProtection} — it protects
|
|
245
270
|
* the appliance from short-cycling and is not about price, sun or contention.
|
|
246
271
|
*/
|
|
247
|
-
RestartDelay = "restart-delay"
|
|
272
|
+
RestartDelay = "restart-delay",
|
|
273
|
+
/**
|
|
274
|
+
* A PV-surplus consumer (EV on PV, heating rod, heat pump surplus boost,
|
|
275
|
+
* surplus automation) is held back or reduced because the house battery is
|
|
276
|
+
* charged first, up to the owner's `batteryPvPriorityTargetSocPercent`.
|
|
277
|
+
*
|
|
278
|
+
* Stated by the energy manager's surplus allocation on the consumer that
|
|
279
|
+
* lost out, never on the battery itself. Set
|
|
280
|
+
* {@link EnyoDataBusCommandReasonContext.batteryPvPriority} with the
|
|
281
|
+
* target, {@link EnyoDataBusCommandReason.socPercent} to the pack's current
|
|
282
|
+
* SoC, and {@link EnyoDataBusCommandReason.powerW} to the surplus the
|
|
283
|
+
* battery is taking — so the text can say "The battery charges to 40 %
|
|
284
|
+
* first (now 28 %)."
|
|
285
|
+
*
|
|
286
|
+
* Told apart from {@link PvSurplusAllocatedElsewhere}: the surplus did not
|
|
287
|
+
* go to another appliance by the planner's choice, it went to the battery
|
|
288
|
+
* because the owner asked for it. Category
|
|
289
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.PvSurplus} — it is about who
|
|
290
|
+
* got the sun.
|
|
291
|
+
*/
|
|
292
|
+
BatteryPvPriority = "battery-pv-priority",
|
|
293
|
+
/**
|
|
294
|
+
* The house battery is allowed to feed the heating rod, down to the
|
|
295
|
+
* owner's `heatingRodBatteryDischargeSocLimitPercent`.
|
|
296
|
+
*
|
|
297
|
+
* Set {@link EnyoDataBusCommandReasonContext.batteryToHeatingRod} with the
|
|
298
|
+
* floor, {@link EnyoDataBusCommandReason.socPercent} to the pack's current
|
|
299
|
+
* SoC and {@link EnyoDataBusCommandReason.powerW} to the power the battery
|
|
300
|
+
* contributes. Category
|
|
301
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.BatteryState}.
|
|
302
|
+
*/
|
|
303
|
+
BatterySupportsHeatingRod = "battery-supports-heating-rod",
|
|
304
|
+
/**
|
|
305
|
+
* The house battery is held back from the heating rod because it has
|
|
306
|
+
* reached the owner's floor (`heatingRodBatteryDischargeSocLimitPercent`).
|
|
307
|
+
* The rod runs on PV surplus only — or, under boost, from the grid.
|
|
308
|
+
*
|
|
309
|
+
* The counterpart of {@link BatterySupportsHeatingRod}. Set
|
|
310
|
+
* {@link EnyoDataBusCommandReasonContext.batteryToHeatingRod} and
|
|
311
|
+
* {@link EnyoDataBusCommandReason.socPercent}. Told apart from
|
|
312
|
+
* {@link BatterySoCLow}: the pack is not low, it is at the floor the owner
|
|
313
|
+
* chose for the rod. Category
|
|
314
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.BatteryState}.
|
|
315
|
+
*/
|
|
316
|
+
BatteryReservedFromHeatingRod = "battery-reserved-from-heating-rod",
|
|
317
|
+
/**
|
|
318
|
+
* A cost-optimised charging session was placed in — or is waiting for — a
|
|
319
|
+
* low-load hour on the grid, because the owner switched on
|
|
320
|
+
* `gridOptimizedEvCharging`. Covers both the "charging now" and the
|
|
321
|
+
* "waiting" side of the decision.
|
|
322
|
+
*
|
|
323
|
+
* About relieving the grid, not about the customer's price: on a static or
|
|
324
|
+
* time-of-use tariff the EPEX spot price that ranked the slot is not what
|
|
325
|
+
* the customer pays, so the text must not promise a saving — which is why
|
|
326
|
+
* this is its own type rather than
|
|
327
|
+
* {@link ElectricityPriceBelowThreshold} or {@link WaitingForCheaperSlot}.
|
|
328
|
+
*
|
|
329
|
+
* Set {@link EnyoDataBusCommandReasonContext.placement} with the chosen
|
|
330
|
+
* window and the session's deadline, and
|
|
331
|
+
* {@link EnyoDataBusCommandReasonContext.gridLoad} with the spot price
|
|
332
|
+
* behind it — so the text can say "Charging from 02:00, when the grid is
|
|
333
|
+
* less busy — ready by 07:00." Category
|
|
334
|
+
* {@link EnyoDataBusCommandReasonCategoryEnum.Schedule}.
|
|
335
|
+
*/
|
|
336
|
+
GridLoadOptimized = "grid-load-optimized"
|
|
248
337
|
}
|
|
249
338
|
/**
|
|
250
339
|
* Coarse, machine-readable grouping of why a data bus command was issued.
|
|
@@ -538,6 +627,131 @@ export interface EnyoDataBusCommandReasonContext {
|
|
|
538
627
|
* protection is against short-cycling (kept on).
|
|
539
628
|
*/
|
|
540
629
|
switching?: EnyoDataBusCommandReasonSwitchingContext;
|
|
630
|
+
/**
|
|
631
|
+
* The owner's battery-first target (`batteryPvPriorityTargetSocPercent`).
|
|
632
|
+
* Set on {@link EnyoDataBusCommandReasonTypeEnum.BatteryPvPriority}.
|
|
633
|
+
*/
|
|
634
|
+
batteryPvPriority?: EnyoDataBusCommandReasonBatteryPvPriorityContext;
|
|
635
|
+
/**
|
|
636
|
+
* The owner's battery-to-heating-rod floor
|
|
637
|
+
* (`heatingRodBatteryDischargeSocLimitPercent`). Set on
|
|
638
|
+
* {@link EnyoDataBusCommandReasonTypeEnum.BatterySupportsHeatingRod} and
|
|
639
|
+
* {@link EnyoDataBusCommandReasonTypeEnum.BatteryReservedFromHeatingRod}.
|
|
640
|
+
*/
|
|
641
|
+
batteryToHeatingRod?: EnyoDataBusCommandReasonBatteryToHeatingRodContext;
|
|
642
|
+
/**
|
|
643
|
+
* The owner's home-consumption threshold above which the battery may not
|
|
644
|
+
* discharge (`batteryDischargeBlockAboveHomeConsumptionW`). Set on
|
|
645
|
+
* {@link EnyoDataBusCommandReasonTypeEnum.HomeConsumptionHigh} when that
|
|
646
|
+
* threshold is why the battery is held.
|
|
647
|
+
*/
|
|
648
|
+
batteryDischargeBlock?: EnyoDataBusCommandReasonBatteryDischargeBlockContext;
|
|
649
|
+
/**
|
|
650
|
+
* The EPEX spot price that stood in for grid load. Set on
|
|
651
|
+
* {@link EnyoDataBusCommandReasonTypeEnum.GridLoadOptimized}.
|
|
652
|
+
*/
|
|
653
|
+
gridLoad?: EnyoDataBusCommandReasonGridLoadContext;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* The market signal behind a
|
|
657
|
+
* {@link EnyoDataBusCommandReasonTypeEnum.GridLoadOptimized} decision.
|
|
658
|
+
*
|
|
659
|
+
* The EPEX spot price is used as a proxy for how heavily the grid is loaded —
|
|
660
|
+
* it is **not** what the customer pays, which is why it lives here and not in
|
|
661
|
+
* {@link EnyoDataBusCommandReason.electricityPricePerKwh}.
|
|
662
|
+
*
|
|
663
|
+
* @example
|
|
664
|
+
* ```typescript
|
|
665
|
+
* // Waiting for the low-load hours from 02:00 to finish by 07:00:
|
|
666
|
+
* const reason: EnyoDataBusCommandReason = {
|
|
667
|
+
* type: EnyoDataBusCommandReasonTypeEnum.GridLoadOptimized,
|
|
668
|
+
* category: EnyoDataBusCommandReasonCategoryEnum.Schedule,
|
|
669
|
+
* context: {
|
|
670
|
+
* placement: {
|
|
671
|
+
* startIso: '2026-10-13T02:00:00+02:00',
|
|
672
|
+
* endIso: '2026-10-13T04:30:00+02:00',
|
|
673
|
+
* deadlineIso: '2026-10-13T07:00:00+02:00',
|
|
674
|
+
* },
|
|
675
|
+
* gridLoad: {epexPricePerKwh: 0.042, currency: EnyoCurrencyEnum.EUR},
|
|
676
|
+
* },
|
|
677
|
+
* };
|
|
678
|
+
* ```
|
|
679
|
+
*/
|
|
680
|
+
export interface EnyoDataBusCommandReasonGridLoadContext {
|
|
681
|
+
/** EPEX spot price per kWh of the chosen slot, in {@link currency} — a grid-load proxy. */
|
|
682
|
+
epexPricePerKwh: number;
|
|
683
|
+
/** Currency of {@link epexPricePerKwh}. */
|
|
684
|
+
currency: EnyoCurrencyEnum;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* The owner's home-consumption threshold
|
|
688
|
+
* (`batteryDischargeBlockAboveHomeConsumptionW`) the battery is held under —
|
|
689
|
+
* the structured backing of
|
|
690
|
+
* {@link EnyoDataBusCommandReasonTypeEnum.HomeConsumptionHigh} when it blocks
|
|
691
|
+
* the battery. The current home consumption travels in
|
|
692
|
+
* {@link EnyoDataBusCommandReason.powerW}, measured like
|
|
693
|
+
* {@link HomeConsumptionTimeseriesEntry.homeConsumptionW}: household plus
|
|
694
|
+
* untracked appliances, tracked appliances excluded.
|
|
695
|
+
*
|
|
696
|
+
* @example
|
|
697
|
+
* ```typescript
|
|
698
|
+
* // Home consumption is 13.2 kW, above the owner's 11 kW threshold — hold the battery:
|
|
699
|
+
* const reason: EnyoDataBusCommandReason = {
|
|
700
|
+
* type: EnyoDataBusCommandReasonTypeEnum.HomeConsumptionHigh,
|
|
701
|
+
* category: EnyoDataBusCommandReasonCategoryEnum.BatteryState,
|
|
702
|
+
* powerW: 13200,
|
|
703
|
+
* context: {batteryDischargeBlock: {thresholdW: 11000}},
|
|
704
|
+
* };
|
|
705
|
+
* ```
|
|
706
|
+
*/
|
|
707
|
+
export interface EnyoDataBusCommandReasonBatteryDischargeBlockContext {
|
|
708
|
+
/** The home consumption, in W, above which the battery may not discharge. */
|
|
709
|
+
thresholdW: number;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* The owner's battery-to-heating-rod floor
|
|
713
|
+
* (`heatingRodBatteryDischargeSocLimitPercent`) a decision was taken under —
|
|
714
|
+
* the structured backing of
|
|
715
|
+
* {@link EnyoDataBusCommandReasonTypeEnum.BatterySupportsHeatingRod} and
|
|
716
|
+
* {@link EnyoDataBusCommandReasonTypeEnum.BatteryReservedFromHeatingRod}. The
|
|
717
|
+
* pack's current SoC travels in {@link EnyoDataBusCommandReason.socPercent}.
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
* ```typescript
|
|
721
|
+
* // The battery reaches the owner's 80 % floor — the rod stops drawing from it:
|
|
722
|
+
* const reason: EnyoDataBusCommandReason = {
|
|
723
|
+
* type: EnyoDataBusCommandReasonTypeEnum.BatteryReservedFromHeatingRod,
|
|
724
|
+
* category: EnyoDataBusCommandReasonCategoryEnum.BatteryState,
|
|
725
|
+
* socPercent: 80,
|
|
726
|
+
* context: {batteryToHeatingRod: {socLimitPercent: 80}},
|
|
727
|
+
* };
|
|
728
|
+
* ```
|
|
729
|
+
*/
|
|
730
|
+
export interface EnyoDataBusCommandReasonBatteryToHeatingRodContext {
|
|
731
|
+
/** The SoC floor, in percent, the battery may feed the rod down to. */
|
|
732
|
+
socLimitPercent: number;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* The owner's battery-first setting (`batteryPvPriorityTargetSocPercent`) a
|
|
736
|
+
* PV-surplus consumer was held back under — the structured backing of
|
|
737
|
+
* {@link EnyoDataBusCommandReasonTypeEnum.BatteryPvPriority}. The pack's
|
|
738
|
+
* current SoC travels in {@link EnyoDataBusCommandReason.socPercent}.
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```typescript
|
|
742
|
+
* // The wallbox gets no PV surplus while the battery charges to 40 %:
|
|
743
|
+
* const reason: EnyoDataBusCommandReason = {
|
|
744
|
+
* type: EnyoDataBusCommandReasonTypeEnum.BatteryPvPriority,
|
|
745
|
+
* category: EnyoDataBusCommandReasonCategoryEnum.PvSurplus,
|
|
746
|
+
* socPercent: 28,
|
|
747
|
+
* powerW: 3200,
|
|
748
|
+
* context: {batteryPvPriority: {targetSocPercent: 40}},
|
|
749
|
+
* };
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
752
|
+
export interface EnyoDataBusCommandReasonBatteryPvPriorityContext {
|
|
753
|
+
/** The SoC, in percent, the battery is charged to before surplus is shared. */
|
|
754
|
+
targetSocPercent: number;
|
|
541
755
|
}
|
|
542
756
|
/**
|
|
543
757
|
* Which part of the owner's battery-to-EV setting
|
|
@@ -189,6 +189,19 @@ export interface EnyoTariffPriceEntry {
|
|
|
189
189
|
* series declares {@link EnyoPriceComponentEnum.GridFee} in its `includes`.
|
|
190
190
|
*/
|
|
191
191
|
gridFeeGrossPerKwh?: number;
|
|
192
|
+
/**
|
|
193
|
+
* Optional wholesale spot price per kWh this interval's price is based on,
|
|
194
|
+
* as the provider reports it — typically the EPEX day-ahead price for a
|
|
195
|
+
* dynamic tariff. In the tariff's currency (not cent), net: without grid
|
|
196
|
+
* fee, taxes, levies or supplier margin. May be negative.
|
|
197
|
+
*
|
|
198
|
+
* Informational only, like {@link gridFeeGrossPerKwh}: it shows what the
|
|
199
|
+
* price was derived from and is **never added** to {@link pricePerKwh},
|
|
200
|
+
* which is always the total price. Set it when the provider hands it out;
|
|
201
|
+
* omit it otherwise — consumers needing a spot price without it can read
|
|
202
|
+
* `useEpexSpotPrices()`.
|
|
203
|
+
*/
|
|
204
|
+
spotPricePerKwh?: number;
|
|
192
205
|
}
|
|
193
206
|
/**
|
|
194
207
|
* Prices for one direction over a requested range.
|