@enyo-energy/energy-app-sdk 1.23.0 → 1.25.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 (38) hide show
  1. package/README.md +109 -2
  2. package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.cjs +9 -3
  3. package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.d.cts +9 -3
  4. package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.cjs +7 -3
  5. package/dist/cjs/integrations/integration-energy-app.cjs +45 -0
  6. package/dist/cjs/integrations/integration-energy-app.d.cts +35 -1
  7. package/dist/cjs/packages/energy-app-grid-connection-point.d.cts +6 -1
  8. package/dist/cjs/types/enyo-charger-appliance.cjs +6 -0
  9. package/dist/cjs/types/enyo-charger-appliance.d.cts +7 -1
  10. package/dist/cjs/types/enyo-data-bus-value.cjs +2 -0
  11. package/dist/cjs/types/enyo-data-bus-value.d.cts +161 -2
  12. package/dist/cjs/types/enyo-eebus.d.cts +13 -0
  13. package/dist/cjs/types/enyo-electricity-tariff.d.cts +9 -0
  14. package/dist/cjs/types/enyo-grid-connection-point.cjs +8 -0
  15. package/dist/cjs/types/enyo-grid-connection-point.d.cts +21 -0
  16. package/dist/cjs/types/enyo-onboarding-v2-eebus-device-select.d.cts +13 -1
  17. package/dist/cjs/types/enyo-onboarding-v2.d.cts +25 -0
  18. package/dist/cjs/version.cjs +1 -1
  19. package/dist/cjs/version.d.cts +1 -1
  20. package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.d.ts +9 -3
  21. package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.js +9 -3
  22. package/dist/implementations/onboarding-v2/onboarding-v2-validators.js +7 -3
  23. package/dist/integrations/integration-energy-app.d.ts +35 -1
  24. package/dist/integrations/integration-energy-app.js +45 -0
  25. package/dist/packages/energy-app-grid-connection-point.d.ts +6 -1
  26. package/dist/types/enyo-charger-appliance.d.ts +7 -1
  27. package/dist/types/enyo-charger-appliance.js +6 -0
  28. package/dist/types/enyo-data-bus-value.d.ts +161 -2
  29. package/dist/types/enyo-data-bus-value.js +2 -0
  30. package/dist/types/enyo-eebus.d.ts +13 -0
  31. package/dist/types/enyo-electricity-tariff.d.ts +9 -0
  32. package/dist/types/enyo-grid-connection-point.d.ts +21 -0
  33. package/dist/types/enyo-grid-connection-point.js +7 -1
  34. package/dist/types/enyo-onboarding-v2-eebus-device-select.d.ts +13 -1
  35. package/dist/types/enyo-onboarding-v2.d.ts +25 -0
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +1 -1
package/README.md CHANGED
@@ -184,7 +184,7 @@ The SDK exposes several layered building blocks. Pick the one that matches the k
184
184
  | List known WiFi SSIDs in range | [`useWifi()`](#usewifi-energyappwifi) |
185
185
  | Query historical timeseries (PV, battery, meter, …) | [`useTimeseries()`](#usetimeseries-energyapptimeseries) |
186
186
  | Read site location (zip or coordinates) | [`useLocation()`](#uselocation-energyapplocation) |
187
- | Read grid connection point (fuse, phases, max power) | [`useGridConnectionPoint()`](#usegridconnectionpoint-energyappgridconnectionpoint) |
187
+ | Read grid connection point (fuse, phases, max power, charger limit) | [`useGridConnectionPoint()`](#usegridconnectionpoint-energyappgridconnectionpoint) |
188
188
  | Retrieve secrets from the developer org secret store | [`useSecretManager()`](#usesecretmanager-energyappsecretmanager) |
189
189
  | Submit energy-manager diagnostics | [`useDiagnostics()`](#usediagnostics-energyappdiagnostics) |
190
190
  | Register a weather / PV / dynamic-price forecast provider | [`useWeatherForecasting()`](#useweatherforecasting-energyappweatherforecasting) / [`usePvForecasting()`](#usepvforecasting-energyapppvforecasting) / [`useDynamicPriceForecast()`](#usedynamicpriceforecast-energyappdynamicpriceforecast) |
@@ -1592,13 +1592,18 @@ if (full) console.log(`lat=${full.latitude} lon=${full.longitude}`);
1592
1592
 
1593
1593
  #### `useGridConnectionPoint(): EnergyAppGridConnectionPoint`
1594
1594
 
1595
- Read the site's grid connection details — main fuse rating, number of phases, and the maximum allowed grid power. Use this to size dispatch envelopes and avoid violating the contractual cap.
1595
+ 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.
1596
+
1597
+ `chargerLimitW` is optional: when it is not configured, load balancing falls back to `DEFAULT_CHARGER_LIMIT_W` (11 kW).
1596
1598
 
1597
1599
  ```typescript
1600
+ import {DEFAULT_CHARGER_LIMIT_W} from "@enyo-energy/energy-app-sdk";
1601
+
1598
1602
  const gcp = energyApp.useGridConnectionPoint();
1599
1603
  const point = await gcp.getGridConnectionPoint();
1600
1604
  if (point) {
1601
1605
  console.log(`Fuse ${point.fuseAmpere}A across ${point.numberOfPhases} phases`);
1606
+ console.log(`Charger limit ${point.chargerLimitW ?? DEFAULT_CHARGER_LIMIT_W} W`);
1602
1607
  }
1603
1608
  ```
1604
1609
 
@@ -1725,6 +1730,14 @@ await tariffs.publishPrices(EnyoTariffDirectionEnum.Consumption, {
1725
1730
  });
1726
1731
  ```
1727
1732
 
1733
+ An entry may carry an optional `gridFeeGrossPerKwh`: the gross grid fee **contained in**
1734
+ `pricePerKwh`, in currency units per kWh. It is a breakdown only — `pricePerKwh` is always the
1735
+ total, so never compute `pricePerKwh + gridFeeGrossPerKwh`.
1736
+
1737
+ ```typescript
1738
+ entries.push({ timestampIso: '2026-05-23T10:00:00Z', pricePerKwh: 0.31, gridFeeGrossPerKwh: 0.09 });
1739
+ ```
1740
+
1728
1741
  **Calling `setTariff` is the activation signal.** Return `AuthenticationRequired` or
1729
1742
  `OnboardingRequired` from the handler to have the host send the user somewhere, and carry the
1730
1743
  `authenticationUrl` / `onboardingGuideId` that makes it actionable; when that flow later completes,
@@ -4453,6 +4466,100 @@ Notes:
4453
4466
  grant can be read against the announcement it answers. It is advisory — the
4454
4467
  command's `powerW` remains the only limit.
4455
4468
 
4469
+ #### Announcing Power Flexibility
4470
+
4471
+ `ApplianceFlexibilityAnnouncementV2` is the **power-based** sibling of the
4472
+ announcement above. Where V1 says "4 kWh by 14:00", V2 says "I can draw
4473
+ 800–2300 W, you may start me any time between 10:00 and 14:00, and I will then
4474
+ run for up to 90 minutes".
4475
+
4476
+ Which one to send is decided by the demand, not by preference:
4477
+
4478
+ | | Send V1 | Send V2 |
4479
+ | --- | --- | --- |
4480
+ | The appliance owes a known amount of energy by a deadline | ✅ | |
4481
+ | The appliance can absorb power, and the energy falls out of how long it runs | | ✅ |
4482
+ | Example | A charging session that needs 22 kWh by 07:00 | A heat pump that will take surplus into its tank |
4483
+
4484
+ V2 deliberately has **no `kWh` field**. An energy figure would be read as
4485
+ authoritative the moment it existed, and the message would collapse back into V1
4486
+ with extra fields. An appliance that knows its energy sends V1.
4487
+
4488
+ **The window is a trigger window, not a run window.**
4489
+ `availableFromIsoTimestamp` and `availableUntilIsoTimestamp` bound when the run
4490
+ may be *started*. `durationMinutes` says how long it then runs — a run started
4491
+ one minute before the window closes may still be drawing power long afterwards.
4492
+
4493
+ ```typescript
4494
+ import {
4495
+ EnergyApp,
4496
+ EnyoFlexibilityTargetEnum,
4497
+ } from '@enyo-energy/energy-app-sdk';
4498
+
4499
+ const energyApp = new EnergyApp();
4500
+ const dataBus = energyApp.useDataBus();
4501
+
4502
+ dataBus.sendMessage([{
4503
+ type: 'message',
4504
+ message: 'ApplianceFlexibilityAnnouncementV2',
4505
+ applianceId: 'heatpump-1',
4506
+ data: {
4507
+ flexibility: {
4508
+ // Modulating compressor: 800 W to 2300 W in 100 W steps.
4509
+ power: {minWatt: 800, maxWatt: 2300, stepWatt: 100},
4510
+ // May be started any time this morning ...
4511
+ availableFromIsoTimestamp: '2025-10-01T10:00:00Z',
4512
+ availableUntilIsoTimestamp: '2025-10-01T14:00:00Z',
4513
+ // ... and then runs for up to 90 minutes, 20 at the very least.
4514
+ durationMinutes: 90,
4515
+ minDurationMinutes: 20,
4516
+ // What the power would go into. Required here, unlike in V1.
4517
+ targets: [
4518
+ {target: EnyoFlexibilityTargetEnum.DomesticHotWater, powerW: 1500},
4519
+ {target: EnyoFlexibilityTargetEnum.BufferTank, powerW: 800},
4520
+ ],
4521
+ },
4522
+ },
4523
+ }]);
4524
+ ```
4525
+
4526
+ Integrations built on the SDK's integration base classes can publish the same
4527
+ message without assembling the envelope by hand:
4528
+
4529
+ ```typescript
4530
+ class MyHeatpump extends HeatpumpIntegrationEnergyApp {
4531
+ private announce(): void {
4532
+ this.publishFlexibilityAnnouncement('heatpump-1', {
4533
+ power: {minWatt: 800, maxWatt: 2300, stepWatt: 100},
4534
+ availableUntilIsoTimestamp: '2025-10-01T14:00:00Z',
4535
+ durationMinutes: 90,
4536
+ targets: [
4537
+ {target: EnyoFlexibilityTargetEnum.DomesticHotWater, powerW: 1500},
4538
+ ],
4539
+ });
4540
+ }
4541
+ }
4542
+ ```
4543
+
4544
+ Notes:
4545
+
4546
+ - `power` is a band (`minWatt` / `maxWatt` / `stepWatt`), not a single figure,
4547
+ because a modulating appliance is *placed*, not switched. A fixed-power
4548
+ appliance states the same value for both bounds and a `stepWatt` equal to the
4549
+ band's width.
4550
+ - `targets` is **required** here. V1 has no need of it — `kWh` is authoritative
4551
+ there and the breakdown is decoration. V2 has no energy figure, so the targets
4552
+ are the substance of the announcement.
4553
+ - `durationMinutes` omitted means the appliance sets no limit of its own and is
4554
+ expected to stop itself when it is satisfied. `minDurationMinutes` is the
4555
+ shortest run still worth starting — below it, a consumer filling a short gap
4556
+ should leave the appliance alone rather than cycle it.
4557
+ - `context.progress` is worth setting precisely because there is no energy
4558
+ figure: without it a consumer cannot tell a run that has nearly finished from
4559
+ one that has barely started.
4560
+ - V1 is unchanged and remains fully supported. V2 is an additive sibling, not a
4561
+ migration.
4562
+
4456
4563
  #### Explaining Why a Command Was Issued
4457
4564
 
4458
4565
  Every data bus command can carry an `EnyoDataBusCommandReason`. Its `type`
@@ -306,7 +306,11 @@ exports.onboardingV2Block = {
306
306
  * Filter it. `deviceTypes` is what turns this from "here are the six EEBUS
307
307
  * devices in the house" into "here is your heat pump", and with one match it
308
308
  * skips the screen entirely instead of asking a question with one possible
309
- * answer.
309
+ * answer. `vendors` narrows it further, by the `brand` a peer announces —
310
+ * use it where the device type cannot separate the candidates (two EEBUS
311
+ * heat pumps in one house), or where the guide is written for one
312
+ * manufacturer and must not offer a competitor's device. The two filters are
313
+ * conjunctive: a peer must satisfy both to be offered.
310
314
  *
311
315
  * The picker is drawn from what mDNS discovery found, so the guide must have
312
316
  * scanned — keep {@link EnyoOnboardingV2Guide.requiresNetworkScan} at its
@@ -327,8 +331,9 @@ exports.onboardingV2Block = {
327
331
  * paired peer into appliances.
328
332
  *
329
333
  * @param id - Stable block id, unique within the guide.
330
- * @param options - Screen wording, optional `deviceTypes` filter, skip
331
- * behaviour, and the `paired` / `not-found` / `failure` routing handles.
334
+ * @param options - Screen wording, optional `deviceTypes` and `vendors`
335
+ * filters, skip behaviour, and the `paired` / `not-found` / `failure`
336
+ * routing handles.
332
337
  * @returns The EEBUS device-select block.
333
338
  *
334
339
  * @example
@@ -336,6 +341,7 @@ exports.onboardingV2Block = {
336
341
  * onboardingV2Block.eebusDeviceSelect('pair', {
337
342
  * headline: t('Wärmepumpe auswählen', 'Select the heat pump'),
338
343
  * deviceTypes: [EnyoEebusDeviceTypeEnum.HeatPumpAppliance],
344
+ * vendors: ['Vaillant'],
339
345
  * outcomes: [
340
346
  * {id: 'ok', value: EnyoOnboardingV2EebusPairOutcome.Paired, label: t('Gekoppelt', 'Paired')},
341
347
  * {id: 'none', value: EnyoOnboardingV2EebusPairOutcome.NotFound, label: t('Nichts gefunden', 'Nothing found')},
@@ -254,7 +254,11 @@ export declare const onboardingV2Block: {
254
254
  * Filter it. `deviceTypes` is what turns this from "here are the six EEBUS
255
255
  * devices in the house" into "here is your heat pump", and with one match it
256
256
  * skips the screen entirely instead of asking a question with one possible
257
- * answer.
257
+ * answer. `vendors` narrows it further, by the `brand` a peer announces —
258
+ * use it where the device type cannot separate the candidates (two EEBUS
259
+ * heat pumps in one house), or where the guide is written for one
260
+ * manufacturer and must not offer a competitor's device. The two filters are
261
+ * conjunctive: a peer must satisfy both to be offered.
258
262
  *
259
263
  * The picker is drawn from what mDNS discovery found, so the guide must have
260
264
  * scanned — keep {@link EnyoOnboardingV2Guide.requiresNetworkScan} at its
@@ -275,8 +279,9 @@ export declare const onboardingV2Block: {
275
279
  * paired peer into appliances.
276
280
  *
277
281
  * @param id - Stable block id, unique within the guide.
278
- * @param options - Screen wording, optional `deviceTypes` filter, skip
279
- * behaviour, and the `paired` / `not-found` / `failure` routing handles.
282
+ * @param options - Screen wording, optional `deviceTypes` and `vendors`
283
+ * filters, skip behaviour, and the `paired` / `not-found` / `failure`
284
+ * routing handles.
280
285
  * @returns The EEBUS device-select block.
281
286
  *
282
287
  * @example
@@ -284,6 +289,7 @@ export declare const onboardingV2Block: {
284
289
  * onboardingV2Block.eebusDeviceSelect('pair', {
285
290
  * headline: t('Wärmepumpe auswählen', 'Select the heat pump'),
286
291
  * deviceTypes: [EnyoEebusDeviceTypeEnum.HeatPumpAppliance],
292
+ * vendors: ['Vaillant'],
287
293
  * outcomes: [
288
294
  * {id: 'ok', value: EnyoOnboardingV2EebusPairOutcome.Paired, label: t('Gekoppelt', 'Paired')},
289
295
  * {id: 'none', value: EnyoOnboardingV2EebusPairOutcome.NotFound, label: t('Nichts gefunden', 'Nothing found')},
@@ -614,9 +614,9 @@ function isPickerBlock(block) {
614
614
  * it would either be skipped along with it or offer a way past the pick. Both
615
615
  * are errors rather than warnings, because there is no reading of the step
616
616
  * that behaves sensibly.
617
- * - **An empty filter is not a filter.** `detectedAt: []` / `deviceTypes: []`
618
- * match nothing, so the picker can only ever reach its `not-found` branch.
619
- * Omitting the property is how "no filter" is expressed.
617
+ * - **An empty filter is not a filter.** `detectedAt: []` / `deviceTypes: []` /
618
+ * `vendors: []` match nothing, so the picker can only ever reach its
619
+ * `not-found` branch. Omitting the property is how "no filter" is expressed.
620
620
  * - **A picker that renders should say something.** With neither a headline of
621
621
  * its own nor a step title, the installer gets a bare list — a warning, since
622
622
  * the host has a default caption.
@@ -655,6 +655,10 @@ function validatePickerBlocks(step, at, errors, warnings) {
655
655
  errors.push(`${at}: ${label(block)} has an empty \`deviceTypes\` filter, which matches no peer — ` +
656
656
  'omit the property to offer every discovered peer.');
657
657
  }
658
+ if (block.vendors && block.vendors.length === 0) {
659
+ errors.push(`${at}: ${label(block)} has an empty \`vendors\` filter, which matches no peer — ` +
660
+ 'omit the property to offer peers of every vendor.');
661
+ }
658
662
  }
659
663
  if (!block.headline?.length && !step.title?.length) {
660
664
  warnings.push(`${at}: ${label(block)} has no headline and sits on a step with no title — ` +
@@ -176,6 +176,51 @@ class IntegrationEnergyApp extends energy_app_js_1.EnergyApp {
176
176
  };
177
177
  this.useDataBus().sendMessage([msg]);
178
178
  }
179
+ /**
180
+ * Publishes an `ApplianceFlexibilityAnnouncementV2` — the power-based
181
+ * flexibility announcement: a draw the appliance offers inside a trigger
182
+ * window, with no fixed energy attached.
183
+ *
184
+ * Use this when the appliance can absorb power but owes no particular
185
+ * amount of energy — a heat pump that will take surplus into its tank, an
186
+ * air conditioner that can pre-cool. When the appliance instead owes a
187
+ * known `kWh` by a deadline, send an `ApplianceFlexibilityAnnouncementV1`
188
+ * rather than this; see
189
+ * {@link EnyoDataBusApplianceFlexibilityAnnouncementV2} for the full
190
+ * distinction.
191
+ *
192
+ * Publish again whenever the offer changes — the announcement describes the
193
+ * appliance right now, and a consumer keeps the last one it saw.
194
+ *
195
+ * @param applianceId - The appliance making the offer.
196
+ * @param flexibility - The power band, the window it may be triggered in,
197
+ * optionally how long it may then run, and what the power is for. See
198
+ * {@link EnyoDataBusApplianceFlexibilityAnnouncementV2.data.flexibility}.
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * this.publishFlexibilityAnnouncement('heatpump-1', {
203
+ * power: {minWatt: 800, maxWatt: 2300, stepWatt: 100},
204
+ * availableUntilIsoTimestamp: '2025-10-01T14:00:00Z',
205
+ * durationMinutes: 90,
206
+ * targets: [
207
+ * {target: EnyoFlexibilityTargetEnum.DomesticHotWater, powerW: 1500},
208
+ * ],
209
+ * });
210
+ * ```
211
+ */
212
+ publishFlexibilityAnnouncement(applianceId, flexibility) {
213
+ const msg = {
214
+ id: this.generateMessageId(),
215
+ type: 'message',
216
+ message: enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.ApplianceFlexibilityAnnouncementV2,
217
+ source: this.source,
218
+ applianceId,
219
+ timestampIso: new Date().toISOString(),
220
+ data: { flexibility }
221
+ };
222
+ this.useDataBus().sendMessage([msg]);
223
+ }
179
224
  /**
180
225
  * Resolves the list of appliance IDs this integration is responsible for.
181
226
  *
@@ -1,5 +1,5 @@
1
1
  import { EnergyApp } from "../energy-app.cjs";
2
- import { EnyoDataBusGridOperatorPowerLimitationExecutedV1, EnyoDataBusGridOperatorPowerLimitationV1, EnyoDataBusMessage, EnyoDataBusMessageEnum } from "../types/enyo-data-bus-value.cjs";
2
+ import { EnyoDataBusApplianceFlexibilityAnnouncementV2, EnyoDataBusGridOperatorPowerLimitationExecutedV1, EnyoDataBusGridOperatorPowerLimitationV1, EnyoDataBusMessage, EnyoDataBusMessageEnum } from "../types/enyo-data-bus-value.cjs";
3
3
  import { EnyoApplianceTypeEnum } from "../types/enyo-appliance.cjs";
4
4
  import { EnyoSourceEnum } from "../types/enyo-source.enum.cjs";
5
5
  import { DataBusCommandHandler } from "../implementations/data-bus/data-bus-command-handler.cjs";
@@ -132,6 +132,40 @@ export declare abstract class IntegrationEnergyApp extends EnergyApp {
132
132
  * (seconds), and optional command correlation id / reason.
133
133
  */
134
134
  publishGridOperatorPowerLimitationExecuted(applianceId: string, data: EnyoDataBusGridOperatorPowerLimitationExecutedV1['data']): void;
135
+ /**
136
+ * Publishes an `ApplianceFlexibilityAnnouncementV2` — the power-based
137
+ * flexibility announcement: a draw the appliance offers inside a trigger
138
+ * window, with no fixed energy attached.
139
+ *
140
+ * Use this when the appliance can absorb power but owes no particular
141
+ * amount of energy — a heat pump that will take surplus into its tank, an
142
+ * air conditioner that can pre-cool. When the appliance instead owes a
143
+ * known `kWh` by a deadline, send an `ApplianceFlexibilityAnnouncementV1`
144
+ * rather than this; see
145
+ * {@link EnyoDataBusApplianceFlexibilityAnnouncementV2} for the full
146
+ * distinction.
147
+ *
148
+ * Publish again whenever the offer changes — the announcement describes the
149
+ * appliance right now, and a consumer keeps the last one it saw.
150
+ *
151
+ * @param applianceId - The appliance making the offer.
152
+ * @param flexibility - The power band, the window it may be triggered in,
153
+ * optionally how long it may then run, and what the power is for. See
154
+ * {@link EnyoDataBusApplianceFlexibilityAnnouncementV2.data.flexibility}.
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * this.publishFlexibilityAnnouncement('heatpump-1', {
159
+ * power: {minWatt: 800, maxWatt: 2300, stepWatt: 100},
160
+ * availableUntilIsoTimestamp: '2025-10-01T14:00:00Z',
161
+ * durationMinutes: 90,
162
+ * targets: [
163
+ * {target: EnyoFlexibilityTargetEnum.DomesticHotWater, powerW: 1500},
164
+ * ],
165
+ * });
166
+ * ```
167
+ */
168
+ publishFlexibilityAnnouncement(applianceId: string, flexibility: EnyoDataBusApplianceFlexibilityAnnouncementV2['data']['flexibility']): void;
135
169
  /**
136
170
  * Resolves the list of appliance IDs this integration is responsible for.
137
171
  *
@@ -4,7 +4,8 @@ import { EnyoGridConnectionPoint } from "../types/enyo-grid-connection-point.cjs
4
4
  *
5
5
  * The grid connection point describes the physical interface between the
6
6
  * local electrical installation and the public grid, including the main
7
- * fuse rating, the number of phases, and the maximum allowed grid power.
7
+ * fuse rating, the number of phases, the maximum allowed grid power, and the
8
+ * total power that load balancing may allocate to EV chargers.
8
9
  * Energy apps consume this information to size grid-import/export budgets,
9
10
  * enforce per-phase current limits, and respect contractual or regulatory
10
11
  * power caps.
@@ -21,12 +22,16 @@ export interface EnergyAppGridConnectionPoint {
21
22
  *
22
23
  * @example
23
24
  * ```typescript
25
+ * import {DEFAULT_CHARGER_LIMIT_W} from "@enyo-energy/energy-app-sdk";
26
+ *
24
27
  * const gridConnectionPoint = energyApp.useGridConnectionPoint();
25
28
  * const details = await gridConnectionPoint.getGridConnectionPoint();
26
29
  * if (details) {
27
30
  * console.log(`Fuse: ${details.fuseAmpere} A`);
28
31
  * console.log(`Phases: ${details.numberOfPhases}`);
29
32
  * console.log(`Power limit: ${details.powerLimitW} W`);
33
+ * const chargerLimitW = details.chargerLimitW ?? DEFAULT_CHARGER_LIMIT_W;
34
+ * console.log(`Charger limit: ${chargerLimitW} W`);
30
35
  * }
31
36
  * ```
32
37
  */
@@ -69,4 +69,10 @@ var EnyoChargerApplianceAvailableFeaturesEnum;
69
69
  EnyoChargerApplianceAvailableFeaturesEnum["PvSurplusMode"] = "PvSurplusMode";
70
70
  /** If the charger supports switching between three-phase and one-phase charging */
71
71
  EnyoChargerApplianceAvailableFeaturesEnum["ThreeToOnePhaseSwitch"] = "ThreeToOnePhaseSwitch";
72
+ /**
73
+ * If the charger requires the vehicle to be disconnected before a new charge can be started.
74
+ * Once the charger is in {@link EnyoChargerApplianceStatusEnum.Finishing}, no new charge can be
75
+ * started remotely; the customer has to unplug the vehicle and plug it in again.
76
+ */
77
+ EnyoChargerApplianceAvailableFeaturesEnum["DisconnectToRestartCharge"] = "DisconnectToRestartCharge";
72
78
  })(EnyoChargerApplianceAvailableFeaturesEnum || (exports.EnyoChargerApplianceAvailableFeaturesEnum = EnyoChargerApplianceAvailableFeaturesEnum = {}));
@@ -82,7 +82,13 @@ export declare enum EnyoChargerApplianceAvailableFeaturesEnum {
82
82
  /** If the Charger supprots a pv surplus mode */
83
83
  PvSurplusMode = "PvSurplusMode",
84
84
  /** If the charger supports switching between three-phase and one-phase charging */
85
- ThreeToOnePhaseSwitch = "ThreeToOnePhaseSwitch"
85
+ ThreeToOnePhaseSwitch = "ThreeToOnePhaseSwitch",
86
+ /**
87
+ * If the charger requires the vehicle to be disconnected before a new charge can be started.
88
+ * Once the charger is in {@link EnyoChargerApplianceStatusEnum.Finishing}, no new charge can be
89
+ * started remotely; the customer has to unplug the vehicle and plug it in again.
90
+ */
91
+ DisconnectToRestartCharge = "DisconnectToRestartCharge"
86
92
  }
87
93
  /**
88
94
  * Phase configurations a charger can operate in.
@@ -425,6 +425,8 @@ var EnyoDataBusMessageEnum;
425
425
  EnyoDataBusMessageEnum["MeterValuesUpdateV1"] = "MeterValuesUpdateV1";
426
426
  EnyoDataBusMessageEnum["BatteryValuesUpdateV1"] = "BatteryValuesUpdateV1";
427
427
  EnyoDataBusMessageEnum["ApplianceFlexibilityAnnouncementV1"] = "ApplianceFlexibilityAnnouncementV1";
428
+ /** Power-based flexibility: a draw the appliance offers inside a trigger window, with no fixed energy. */
429
+ EnyoDataBusMessageEnum["ApplianceFlexibilityAnnouncementV2"] = "ApplianceFlexibilityAnnouncementV2";
428
430
  EnyoDataBusMessageEnum["ApplianceStateUpdateV1"] = "ApplianceStateUpdateV1";
429
431
  EnyoDataBusMessageEnum["HeatpumpValuesUpdateV1"] = "HeatpumpValuesUpdateV1";
430
432
  EnyoDataBusMessageEnum["HeatingRodValuesUpdateV1"] = "HeatingRodValuesUpdateV1";
@@ -9,7 +9,7 @@ import type { EnyoCalibrationRequestOriginEnum, EnyoCalibrationRun } from "./eny
9
9
  import { EnyoEnergyPrices } from "./enyo-energy-prices.cjs";
10
10
  import { EnyoCurrencyEnum } from "./enyo-currency.cjs";
11
11
  import { EnyoHeatpumpApplianceModeEnum } from "./enyo-heatpump-appliance.cjs";
12
- import type { EnyoFlexibilityTargetEnum, EnyoFlexibilityTargetPower } from "./enyo-flexibility-announcement.cjs";
12
+ import type { EnyoFlexibilityHeatpumpTargetTypeEnum, EnyoFlexibilityPowerBand, EnyoFlexibilityProgress, EnyoFlexibilityTargetEnum, EnyoFlexibilityTargetPower } from "./enyo-flexibility-announcement.cjs";
13
13
  import { EnyoSmartPlugApplianceStateEnum } from "./enyo-smart-plug-appliance.cjs";
14
14
  import { EnyoAirConditioningApplianceModeEnum, EnyoAirConditioningOptimizationModeEnum } from "./enyo-air-conditioning-appliance.cjs";
15
15
  import { EnergyAppPackageCategory } from "../energy-app-package-definition.cjs";
@@ -696,6 +696,8 @@ export declare enum EnyoDataBusMessageEnum {
696
696
  MeterValuesUpdateV1 = "MeterValuesUpdateV1",
697
697
  BatteryValuesUpdateV1 = "BatteryValuesUpdateV1",
698
698
  ApplianceFlexibilityAnnouncementV1 = "ApplianceFlexibilityAnnouncementV1",
699
+ /** Power-based flexibility: a draw the appliance offers inside a trigger window, with no fixed energy. */
700
+ ApplianceFlexibilityAnnouncementV2 = "ApplianceFlexibilityAnnouncementV2",
699
701
  ApplianceStateUpdateV1 = "ApplianceStateUpdateV1",
700
702
  HeatpumpValuesUpdateV1 = "HeatpumpValuesUpdateV1",
701
703
  HeatingRodValuesUpdateV1 = "HeatingRodValuesUpdateV1",
@@ -1073,6 +1075,149 @@ export interface EnyoDataBusApplianceFlexibilityAnnouncementV1 extends EnyoDataB
1073
1075
  };
1074
1076
  };
1075
1077
  }
1078
+ /**
1079
+ * An appliance announces a *power* it can absorb inside a window, with no fixed
1080
+ * energy attached.
1081
+ *
1082
+ * The power-based counterpart to
1083
+ * {@link EnyoDataBusApplianceFlexibilityAnnouncementV1}. V1 answers "how much
1084
+ * energy do I owe, and by when" — the right shape for a charging session with a
1085
+ * target. This one answers "how much can I draw, when may you start me, and how
1086
+ * long may I run" — the right shape for a heat pump that can take surplus into a
1087
+ * tank without ever owing a particular number of kWh.
1088
+ *
1089
+ * Which to send is decided by the demand, not by preference:
1090
+ *
1091
+ * - **Energy is fixed, timing is not** → V1. The appliance owes `kWh` by a
1092
+ * deadline and the decision maker schedules it.
1093
+ * - **Power is offered, energy falls out of it** → V2. There is no amount the
1094
+ * appliance is owed; running it longer simply uses more.
1095
+ *
1096
+ * Deliberately no `kWh` field: an energy figure here would be read as
1097
+ * authoritative the moment it exists, and the announcement would collapse back
1098
+ * into V1 with extra fields. An appliance that knows its energy should send V1.
1099
+ *
1100
+ * **The window is a trigger window, not a run window.**
1101
+ * {@link data.flexibility.availableFromIsoTimestamp} and
1102
+ * {@link data.flexibility.availableUntilIsoTimestamp} bound when the run may be
1103
+ * *started*; {@link data.flexibility.durationMinutes} says how long it then
1104
+ * runs, and a run started at the end of the window may finish well past it.
1105
+ *
1106
+ * @example
1107
+ * ```typescript
1108
+ * dataBus.sendMessage([{
1109
+ * type: 'message',
1110
+ * message: EnyoDataBusMessageEnum.ApplianceFlexibilityAnnouncementV2,
1111
+ * applianceId: 'heatpump-1',
1112
+ * data: {
1113
+ * flexibility: {
1114
+ * // Modulating compressor: 800 W to 2300 W in 100 W steps.
1115
+ * power: {minWatt: 800, maxWatt: 2300, stepWatt: 100},
1116
+ * // May be started any time this morning ...
1117
+ * availableFromIsoTimestamp: '2025-10-01T10:00:00Z',
1118
+ * availableUntilIsoTimestamp: '2025-10-01T14:00:00Z',
1119
+ * // ... and then runs for up to 90 minutes, 20 at the very least.
1120
+ * durationMinutes: 90,
1121
+ * minDurationMinutes: 20,
1122
+ * // What the power would go into.
1123
+ * targets: [
1124
+ * {target: EnyoFlexibilityTargetEnum.DomesticHotWater, powerW: 1500},
1125
+ * {target: EnyoFlexibilityTargetEnum.BufferTank, powerW: 800},
1126
+ * ],
1127
+ * },
1128
+ * },
1129
+ * }]);
1130
+ * ```
1131
+ */
1132
+ export interface EnyoDataBusApplianceFlexibilityAnnouncementV2 extends EnyoDataBusMessage {
1133
+ type: 'message';
1134
+ message: EnyoDataBusMessageEnum.ApplianceFlexibilityAnnouncementV2;
1135
+ /** ID of the appliance */
1136
+ applianceId: string;
1137
+ data: {
1138
+ flexibility: {
1139
+ /**
1140
+ * Power envelope the appliance offers, in Watts — `minWatt`,
1141
+ * `maxWatt` and the `stepWatt` granularity between them.
1142
+ *
1143
+ * A band rather than a single figure because a modulating appliance
1144
+ * is placed, not switched: a decision maker with surplus to spend
1145
+ * needs to know it may run the compressor at 1200 W rather than
1146
+ * choosing between 2300 W and nothing. A fixed-power appliance
1147
+ * states the same value for both bounds and a `stepWatt` equal to
1148
+ * the band's width.
1149
+ */
1150
+ power: EnyoFlexibilityPowerBand;
1151
+ /**
1152
+ * Earliest the run may be triggered, ISO 8601. Omitted means it may
1153
+ * be triggered immediately.
1154
+ */
1155
+ availableFromIsoTimestamp?: string;
1156
+ /**
1157
+ * Latest the run may be triggered, ISO 8601.
1158
+ *
1159
+ * A trigger deadline, not a completion deadline — a run started one
1160
+ * minute before this may still be drawing power long afterwards.
1161
+ * See {@link durationMinutes} for how long.
1162
+ */
1163
+ availableUntilIsoTimestamp: string;
1164
+ /**
1165
+ * How long the run may last once triggered, in minutes.
1166
+ *
1167
+ * Omitted means the appliance sets no limit of its own and the
1168
+ * decision maker may run it for as long as the window and its own
1169
+ * budget allow — the appliance is expected to stop itself when the
1170
+ * tank, the room or the battery is satisfied.
1171
+ */
1172
+ durationMinutes?: number;
1173
+ /**
1174
+ * Shortest contiguous run still worth starting, in minutes.
1175
+ *
1176
+ * Below this the run costs more than it returns — a compressor that
1177
+ * spends its first ten minutes getting up to temperature gains
1178
+ * nothing from a five-minute slot, and a decision maker filling a
1179
+ * short gap should leave it alone rather than cycle it.
1180
+ */
1181
+ minDurationMinutes?: number;
1182
+ /**
1183
+ * What the offered power would go into, and how much of it each
1184
+ * target would draw — e.g. a heat pump splitting between the
1185
+ * domestic hot water tank and the heating buffer tank.
1186
+ *
1187
+ * Required here, unlike on
1188
+ * {@link EnyoDataBusApplianceFlexibilityAnnouncementV1} where it is
1189
+ * optional context beside an authoritative `kWh`: this message has
1190
+ * no energy figure, so the targets are the substance of the
1191
+ * announcement rather than a decoration on it. A given target
1192
+ * SHOULD appear at most once, and the entries' `powerW` SHOULD sit
1193
+ * inside {@link power}.
1194
+ */
1195
+ targets: EnyoFlexibilityTargetPower[];
1196
+ /**
1197
+ * Optional extra context for the consumer of this announcement.
1198
+ * Nested so further context keys can be added without changing the
1199
+ * message shape again.
1200
+ */
1201
+ context?: {
1202
+ /**
1203
+ * For heat pumps: what the flexibility is aimed at. Narrower
1204
+ * than {@link targets}, and shared with the category-level
1205
+ * announcement so both surfaces speak one vocabulary.
1206
+ */
1207
+ heatpumpTargetType?: EnyoFlexibilityHeatpumpTargetTypeEnum;
1208
+ /**
1209
+ * What the run is driving toward and where it stands now — a
1210
+ * tank at 38 °C heading for 50 °C.
1211
+ *
1212
+ * Worth setting precisely because this message carries no
1213
+ * energy figure: without it a consumer cannot tell a run that
1214
+ * has nearly finished from one that has barely started.
1215
+ */
1216
+ progress?: EnyoFlexibilityProgress;
1217
+ };
1218
+ };
1219
+ };
1220
+ }
1076
1221
  /**
1077
1222
  * Message sent when an appliance's connectivity state and/or health status
1078
1223
  * changes. At least one of `state` or `status` should be set; both may be
@@ -3213,7 +3358,7 @@ export interface EnyoDataBusVehicleSocUpdateV1 extends EnyoDataBusMessage {
3213
3358
  * energyApp.useDataBus().sendMessage([{
3214
3359
  * type: 'message',
3215
3360
  * message: 'RequestVehicleSocEstimateV1',
3216
- * data: {requestId, vehicleId, maxAgeMs: 15 * 60 * 1000},
3361
+ * data: {requestId, vehicleId, maxAgeMs: 15 * 60 * 1000, targetSocPercent: 80},
3217
3362
  * }]);
3218
3363
  * ```
3219
3364
  */
@@ -3244,6 +3389,20 @@ export interface EnyoDataBusRequestVehicleSocEstimateV1 extends EnyoDataBusMessa
3244
3389
  * values — find the right one without guessing.
3245
3390
  */
3246
3391
  applianceId?: string;
3392
+ /**
3393
+ * State of charge the caller is planning towards, in percent (0-100) —
3394
+ * the session's target as in
3395
+ * {@link EnyoDataBusStartChargeV1.data.targetSocPercent}, not
3396
+ * a limit on the answer.
3397
+ *
3398
+ * Told to the responder because what the estimate is *for* changes how
3399
+ * much trouble is worth taking to get it: a stored reading already at
3400
+ * or above the target answers the question without waking the car,
3401
+ * while one far below it justifies a fresh fetch. Omitted means the
3402
+ * responder decides on its own; the reading returned is the same
3403
+ * either way.
3404
+ */
3405
+ targetSocPercent?: number;
3247
3406
  };
3248
3407
  }
3249
3408
  /**
@@ -80,6 +80,19 @@ export interface EebusDiscoveredDevice {
80
80
  ski: string;
81
81
  /** Human-readable device name advertised during discovery */
82
82
  deviceName?: string;
83
+ /**
84
+ * The vendor the peer announced in its SHIP record (the `brand` TXT key),
85
+ * verbatim and untranslated — e.g. `Vaillant`, `KEBA`, `Viessmann`.
86
+ *
87
+ * The peer's own claim, made before anything is paired, which is what makes
88
+ * it usable as a discovery filter next to {@link deviceType}: see
89
+ * {@link EnyoOnboardingV2EebusDeviceSelectBlock.vendors}. Spelling and case
90
+ * are whatever the firmware ships, so compare case-insensitively rather
91
+ * than with `===`.
92
+ *
93
+ * Absent when the peer advertises no `brand`.
94
+ */
95
+ brand?: string;
83
96
  /** IP address or hostname of the device */
84
97
  host: string;
85
98
  /** Port number for the SHIP connection */
@@ -180,6 +180,15 @@ export interface EnyoTariffPriceEntry {
180
180
  timestampIso: string;
181
181
  /** Price per kWh for this interval, in the tariff's currency. */
182
182
  pricePerKwh: number;
183
+ /**
184
+ * Optional gross grid fee per kWh contained in {@link pricePerKwh}, in the
185
+ * tariff's currency (not cent).
186
+ *
187
+ * Informational only: it breaks down the price, it is **never added** to it.
188
+ * {@link pricePerKwh} is always the total price. Only meaningful when the
189
+ * series declares {@link EnyoPriceComponentEnum.GridFee} in its `includes`.
190
+ */
191
+ gridFeeGrossPerKwh?: number;
183
192
  }
184
193
  /**
185
194
  * Prices for one direction over a requested range.
@@ -1,2 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CHARGER_LIMIT_W = void 0;
4
+ /**
5
+ * Default total charger power limit, in watts (W), applied by load balancing
6
+ * when {@link EnyoGridConnectionPoint.chargerLimitW} is not configured.
7
+ *
8
+ * 11 kW corresponds to the common three-phase 16 A wallbox rating.
9
+ */
10
+ exports.DEFAULT_CHARGER_LIMIT_W = 11000;