@enyo-energy/energy-app-sdk 1.23.0 → 1.24.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 CHANGED
@@ -4453,6 +4453,100 @@ Notes:
4453
4453
  grant can be read against the announcement it answers. It is advisory — the
4454
4454
  command's `powerW` remains the only limit.
4455
4455
 
4456
+ #### Announcing Power Flexibility
4457
+
4458
+ `ApplianceFlexibilityAnnouncementV2` is the **power-based** sibling of the
4459
+ announcement above. Where V1 says "4 kWh by 14:00", V2 says "I can draw
4460
+ 800–2300 W, you may start me any time between 10:00 and 14:00, and I will then
4461
+ run for up to 90 minutes".
4462
+
4463
+ Which one to send is decided by the demand, not by preference:
4464
+
4465
+ | | Send V1 | Send V2 |
4466
+ | --- | --- | --- |
4467
+ | The appliance owes a known amount of energy by a deadline | ✅ | |
4468
+ | The appliance can absorb power, and the energy falls out of how long it runs | | ✅ |
4469
+ | Example | A charging session that needs 22 kWh by 07:00 | A heat pump that will take surplus into its tank |
4470
+
4471
+ V2 deliberately has **no `kWh` field**. An energy figure would be read as
4472
+ authoritative the moment it existed, and the message would collapse back into V1
4473
+ with extra fields. An appliance that knows its energy sends V1.
4474
+
4475
+ **The window is a trigger window, not a run window.**
4476
+ `availableFromIsoTimestamp` and `availableUntilIsoTimestamp` bound when the run
4477
+ may be *started*. `durationMinutes` says how long it then runs — a run started
4478
+ one minute before the window closes may still be drawing power long afterwards.
4479
+
4480
+ ```typescript
4481
+ import {
4482
+ EnergyApp,
4483
+ EnyoFlexibilityTargetEnum,
4484
+ } from '@enyo-energy/energy-app-sdk';
4485
+
4486
+ const energyApp = new EnergyApp();
4487
+ const dataBus = energyApp.useDataBus();
4488
+
4489
+ dataBus.sendMessage([{
4490
+ type: 'message',
4491
+ message: 'ApplianceFlexibilityAnnouncementV2',
4492
+ applianceId: 'heatpump-1',
4493
+ data: {
4494
+ flexibility: {
4495
+ // Modulating compressor: 800 W to 2300 W in 100 W steps.
4496
+ power: {minWatt: 800, maxWatt: 2300, stepWatt: 100},
4497
+ // May be started any time this morning ...
4498
+ availableFromIsoTimestamp: '2025-10-01T10:00:00Z',
4499
+ availableUntilIsoTimestamp: '2025-10-01T14:00:00Z',
4500
+ // ... and then runs for up to 90 minutes, 20 at the very least.
4501
+ durationMinutes: 90,
4502
+ minDurationMinutes: 20,
4503
+ // What the power would go into. Required here, unlike in V1.
4504
+ targets: [
4505
+ {target: EnyoFlexibilityTargetEnum.DomesticHotWater, powerW: 1500},
4506
+ {target: EnyoFlexibilityTargetEnum.BufferTank, powerW: 800},
4507
+ ],
4508
+ },
4509
+ },
4510
+ }]);
4511
+ ```
4512
+
4513
+ Integrations built on the SDK's integration base classes can publish the same
4514
+ message without assembling the envelope by hand:
4515
+
4516
+ ```typescript
4517
+ class MyHeatpump extends HeatpumpIntegrationEnergyApp {
4518
+ private announce(): void {
4519
+ this.publishFlexibilityAnnouncement('heatpump-1', {
4520
+ power: {minWatt: 800, maxWatt: 2300, stepWatt: 100},
4521
+ availableUntilIsoTimestamp: '2025-10-01T14:00:00Z',
4522
+ durationMinutes: 90,
4523
+ targets: [
4524
+ {target: EnyoFlexibilityTargetEnum.DomesticHotWater, powerW: 1500},
4525
+ ],
4526
+ });
4527
+ }
4528
+ }
4529
+ ```
4530
+
4531
+ Notes:
4532
+
4533
+ - `power` is a band (`minWatt` / `maxWatt` / `stepWatt`), not a single figure,
4534
+ because a modulating appliance is *placed*, not switched. A fixed-power
4535
+ appliance states the same value for both bounds and a `stepWatt` equal to the
4536
+ band's width.
4537
+ - `targets` is **required** here. V1 has no need of it — `kWh` is authoritative
4538
+ there and the breakdown is decoration. V2 has no energy figure, so the targets
4539
+ are the substance of the announcement.
4540
+ - `durationMinutes` omitted means the appliance sets no limit of its own and is
4541
+ expected to stop itself when it is satisfied. `minDurationMinutes` is the
4542
+ shortest run still worth starting — below it, a consumer filling a short gap
4543
+ should leave the appliance alone rather than cycle it.
4544
+ - `context.progress` is worth setting precisely because there is no energy
4545
+ figure: without it a consumer cannot tell a run that has nearly finished from
4546
+ one that has barely started.
4547
+ - V1 is unchanged and remains fully supported. V2 is an additive sibling, not a
4548
+ migration.
4549
+
4456
4550
  #### Explaining Why a Command Was Issued
4457
4551
 
4458
4552
  Every data bus command can carry an `EnyoDataBusCommandReason`. Its `type`
@@ -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
  *
@@ -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
  /**
@@ -9,7 +9,7 @@ exports.getSdkVersion = getSdkVersion;
9
9
  /**
10
10
  * Current version of the enyo Energy App SDK.
11
11
  */
12
- exports.SDK_VERSION = '1.23.0';
12
+ exports.SDK_VERSION = '1.24.0';
13
13
  /**
14
14
  * Gets the current SDK version.
15
15
  * @returns The semantic version string of the SDK
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "1.23.0";
8
+ export declare const SDK_VERSION = "1.24.0";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -1,5 +1,5 @@
1
1
  import { EnergyApp } from "../energy-app.js";
2
- import { EnyoDataBusGridOperatorPowerLimitationExecutedV1, EnyoDataBusGridOperatorPowerLimitationV1, EnyoDataBusMessage, EnyoDataBusMessageEnum } from "../types/enyo-data-bus-value.js";
2
+ import { EnyoDataBusApplianceFlexibilityAnnouncementV2, EnyoDataBusGridOperatorPowerLimitationExecutedV1, EnyoDataBusGridOperatorPowerLimitationV1, EnyoDataBusMessage, EnyoDataBusMessageEnum } from "../types/enyo-data-bus-value.js";
3
3
  import { EnyoApplianceTypeEnum } from "../types/enyo-appliance.js";
4
4
  import { EnyoSourceEnum } from "../types/enyo-source.enum.js";
5
5
  import { DataBusCommandHandler } from "../implementations/data-bus/data-bus-command-handler.js";
@@ -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
  *
@@ -173,6 +173,51 @@ export class IntegrationEnergyApp extends EnergyApp {
173
173
  };
174
174
  this.useDataBus().sendMessage([msg]);
175
175
  }
176
+ /**
177
+ * Publishes an `ApplianceFlexibilityAnnouncementV2` — the power-based
178
+ * flexibility announcement: a draw the appliance offers inside a trigger
179
+ * window, with no fixed energy attached.
180
+ *
181
+ * Use this when the appliance can absorb power but owes no particular
182
+ * amount of energy — a heat pump that will take surplus into its tank, an
183
+ * air conditioner that can pre-cool. When the appliance instead owes a
184
+ * known `kWh` by a deadline, send an `ApplianceFlexibilityAnnouncementV1`
185
+ * rather than this; see
186
+ * {@link EnyoDataBusApplianceFlexibilityAnnouncementV2} for the full
187
+ * distinction.
188
+ *
189
+ * Publish again whenever the offer changes — the announcement describes the
190
+ * appliance right now, and a consumer keeps the last one it saw.
191
+ *
192
+ * @param applianceId - The appliance making the offer.
193
+ * @param flexibility - The power band, the window it may be triggered in,
194
+ * optionally how long it may then run, and what the power is for. See
195
+ * {@link EnyoDataBusApplianceFlexibilityAnnouncementV2.data.flexibility}.
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * this.publishFlexibilityAnnouncement('heatpump-1', {
200
+ * power: {minWatt: 800, maxWatt: 2300, stepWatt: 100},
201
+ * availableUntilIsoTimestamp: '2025-10-01T14:00:00Z',
202
+ * durationMinutes: 90,
203
+ * targets: [
204
+ * {target: EnyoFlexibilityTargetEnum.DomesticHotWater, powerW: 1500},
205
+ * ],
206
+ * });
207
+ * ```
208
+ */
209
+ publishFlexibilityAnnouncement(applianceId, flexibility) {
210
+ const msg = {
211
+ id: this.generateMessageId(),
212
+ type: 'message',
213
+ message: EnyoDataBusMessageEnum.ApplianceFlexibilityAnnouncementV2,
214
+ source: this.source,
215
+ applianceId,
216
+ timestampIso: new Date().toISOString(),
217
+ data: { flexibility }
218
+ };
219
+ this.useDataBus().sendMessage([msg]);
220
+ }
176
221
  /**
177
222
  * Resolves the list of appliance IDs this integration is responsible for.
178
223
  *
@@ -9,7 +9,7 @@ import type { EnyoCalibrationRequestOriginEnum, EnyoCalibrationRun } from "./eny
9
9
  import { EnyoEnergyPrices } from "./enyo-energy-prices.js";
10
10
  import { EnyoCurrencyEnum } from "./enyo-currency.js";
11
11
  import { EnyoHeatpumpApplianceModeEnum } from "./enyo-heatpump-appliance.js";
12
- import type { EnyoFlexibilityTargetEnum, EnyoFlexibilityTargetPower } from "./enyo-flexibility-announcement.js";
12
+ import type { EnyoFlexibilityHeatpumpTargetTypeEnum, EnyoFlexibilityPowerBand, EnyoFlexibilityProgress, EnyoFlexibilityTargetEnum, EnyoFlexibilityTargetPower } from "./enyo-flexibility-announcement.js";
13
13
  import { EnyoSmartPlugApplianceStateEnum } from "./enyo-smart-plug-appliance.js";
14
14
  import { EnyoAirConditioningApplianceModeEnum, EnyoAirConditioningOptimizationModeEnum } from "./enyo-air-conditioning-appliance.js";
15
15
  import { EnergyAppPackageCategory } from "../energy-app-package-definition.js";
@@ -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
  /**
@@ -422,6 +422,8 @@ export var EnyoDataBusMessageEnum;
422
422
  EnyoDataBusMessageEnum["MeterValuesUpdateV1"] = "MeterValuesUpdateV1";
423
423
  EnyoDataBusMessageEnum["BatteryValuesUpdateV1"] = "BatteryValuesUpdateV1";
424
424
  EnyoDataBusMessageEnum["ApplianceFlexibilityAnnouncementV1"] = "ApplianceFlexibilityAnnouncementV1";
425
+ /** Power-based flexibility: a draw the appliance offers inside a trigger window, with no fixed energy. */
426
+ EnyoDataBusMessageEnum["ApplianceFlexibilityAnnouncementV2"] = "ApplianceFlexibilityAnnouncementV2";
425
427
  EnyoDataBusMessageEnum["ApplianceStateUpdateV1"] = "ApplianceStateUpdateV1";
426
428
  EnyoDataBusMessageEnum["HeatpumpValuesUpdateV1"] = "HeatpumpValuesUpdateV1";
427
429
  EnyoDataBusMessageEnum["HeatingRodValuesUpdateV1"] = "HeatingRodValuesUpdateV1";
package/dist/version.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "1.23.0";
8
+ export declare const SDK_VERSION = "1.24.0";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/dist/version.js CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export const SDK_VERSION = '1.23.0';
8
+ export const SDK_VERSION = '1.24.0';
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enyo-energy/energy-app-sdk",
3
- "version": "1.23.0",
3
+ "version": "1.24.0",
4
4
  "description": "enyo Energy App SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",