@enyo-energy/energy-app-sdk 1.38.0 → 1.39.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
@@ -1290,32 +1290,150 @@ prices.onPriceChange((newPrice) => {
1290
1290
 
1291
1291
  #### `useNotification(): EnergyAppNotification`
1292
1292
 
1293
- Send notifications to users:
1293
+ Send notifications to users. Every category must be registered before it is used; the host uses the category for the
1294
+ user's on/off switch and for rate limits.
1294
1295
 
1295
1296
  ```typescript
1296
1297
  const notifications = energyApp.useNotification();
1297
1298
 
1298
- // Send info notification
1299
- await notifications.sendNotification({
1300
- type: 'info',
1301
- title: 'Energy Optimization',
1302
- message: 'Your system is running optimally',
1303
- timestamp: Date.now()
1299
+ notifications.registerNotificationCategory({
1300
+ category: 'connection-issue',
1301
+ name: [
1302
+ { language: 'en', name: 'Connection issue' },
1303
+ { language: 'de', name: 'Verbindungsproblem' }
1304
+ ]
1304
1305
  });
1305
1306
 
1306
- // Send warning
1307
- await notifications.sendNotification({
1308
- type: 'warning',
1309
- title: 'High Energy Consumption',
1310
- message: 'Consider reducing energy usage during peak hours'
1311
- });
1307
+ const notificationId = notifications.sendNotification(
1308
+ 'connection-issue',
1309
+ { applianceId, target: { type: 'appliance-settings', applianceId } },
1310
+ [
1311
+ { language: 'en', title: 'Inverter offline', body: 'Inverter communication lost - please check the connection.' },
1312
+ { language: 'de', title: 'Wechselrichter offline', body: 'Keine Verbindung zum Wechselrichter - bitte Verbindung prüfen.' }
1313
+ ]
1314
+ );
1315
+ ```
1316
+
1317
+ Categories:
1318
+
1319
+ | Category | Use when |
1320
+ |---|---|
1321
+ | `connection-issue` | An appliance, gateway or upstream service is unreachable. |
1322
+ | `onboarding-required` | The user has to complete (or revisit) an onboarding flow. |
1323
+ | `charging-issue` | The app asked a charger to charge, but it is not charging. See [Charging issues](#charging-issues). |
1324
+ | `battery-issue` | The app commanded a battery, but it does not follow the command. See [Battery issues](#battery-issues). |
1325
+ | `authentication-required` | A sign-in that used to work was lost. See [Lost sign-in](#lost-sign-in). |
1326
+
1327
+ Targets (`options.target`) decide where tapping the notification leads:
1328
+
1329
+ | Target | Opens |
1330
+ |---|---|
1331
+ | `{ type: 'onboarding', onboardingName }` | A registered onboarding guide. |
1332
+ | `{ type: 'energy-app-settings' }` | The package's own settings. |
1333
+ | `{ type: 'appliance-settings', applianceId }` | The settings screen of an appliance. |
1334
+ | `{ type: 'appliance', applianceId }` | The live view of an appliance (current state, power and session). |
1335
+ | `{ type: 'authentication', applianceId? }` | The package's pending sign-in, created with `requestAuthentication()`. |
1336
+ | `{ type: 'subscription-offer', planKey }` | The offer screen for a subscription plan. |
1312
1337
 
1313
- // Send critical alert
1314
- await notifications.sendNotification({
1315
- type: 'error',
1316
- title: 'System Fault',
1317
- message: 'Inverter communication lost - please check connection'
1338
+ `sendNotification` throws an `EnyoNotificationSendError` with `type: 'not-registered'` if the category was not
1339
+ registered, or `type: 'rate-limit'` if the host's rate limit for the category is exceeded.
1340
+
1341
+ ##### Charging issues
1342
+
1343
+ Only the app that decided a charger should charge knows that it should be charging, so the app sends
1344
+ `charging-issue` notifications. The host relies on these rules and does not re-check them. Send one only when
1345
+ **all** of the following hold:
1346
+
1347
+ - The app itself asked the charger to charge: it sent a start or resume, or it is giving the charger more than 0 W.
1348
+ - The charger has stayed non-charging for at least 5 minutes after that request (status is not `Charging`, or it is
1349
+ `Charging` but draws almost no power).
1350
+ - The charger is not `Suspended` with reason `EnyoChargerApplianceSuspendedReasonEnum.Ev`: the car is full or paused
1351
+ charging itself, which is not a fault.
1352
+ - No grid-operator limit (§14a / `GridOperatorPowerLimitationV1`) is currently pushing the charger to 0.
1353
+
1354
+ Send it **at most once per plug-in**: wait until the charger is `Available` again (car unplugged) before sending
1355
+ another one for that appliance. Always pass `applianceId` and the `appliance` target. The host drops more than one
1356
+ `charging-issue` per appliance per 30 minutes with a `rate-limit` error.
1357
+
1358
+ ```typescript
1359
+ const notification = energyApp.useNotification();
1360
+
1361
+ notification.registerNotificationCategory({
1362
+ category: 'charging-issue',
1363
+ name: [
1364
+ {language: 'de', name: 'Ladeprobleme'},
1365
+ {language: 'en', name: 'Charging issues'},
1366
+ ],
1367
+ description: [
1368
+ {language: 'de', name: 'Ladeprobleme', description: 'Wenn dein Auto nicht lädt, obwohl es laden sollte.'},
1369
+ {language: 'en', name: 'Charging issues', description: 'When your car is not charging although it should.'},
1370
+ ],
1318
1371
  });
1372
+
1373
+ notification.sendNotification(
1374
+ 'charging-issue',
1375
+ {applianceId, target: {type: 'appliance', applianceId}},
1376
+ [
1377
+ {language: 'de', title: 'Dein Auto lädt nicht', body: 'Die Wallbox sollte gerade laden, tut es aber nicht. Prüfe bitte Auto und Ladekabel.'},
1378
+ {language: 'en', title: 'Your car is not charging', body: 'The wallbox should be charging right now but isn\'t. Please check the car and the charging cable.'},
1379
+ ],
1380
+ );
1381
+ ```
1382
+
1383
+ ##### Battery issues
1384
+
1385
+ `battery-issue` follows the same idea for home batteries. Send one only when **all** of the following hold:
1386
+
1387
+ - The app itself commanded the battery (`SetStorageControlV2` with mode `Controlled`, or one of the other storage
1388
+ commands) and that command is still in effect. Never while the battery runs on `Auto`.
1389
+ - For at least 5 minutes the battery has not followed it, judged by `batteryPowerW` in `BatteryValuesUpdateV1`:
1390
+ not charging on `Charge`, not discharging on `Discharge`, or moving power noticeably on `Hold`.
1391
+ - The battery is not simply at its limit: no charge command at or above `maxSoC` (or 100 %), no discharge command
1392
+ at or below `minSoC`.
1393
+ - A discharge to the house is not just capped by low household consumption.
1394
+ - No grid-operator limit (§14a) is pushing the battery to 0, and `controlAllowed` is not `false`.
1395
+
1396
+ Send it **at most once per issue**: wait until the battery follows a command again, or the app releases it to
1397
+ `Auto`, before sending another one. Always pass `applianceId` and the `appliance` target. The host drops more than
1398
+ one `battery-issue` per appliance per 30 minutes with a `rate-limit` error.
1399
+
1400
+ ```typescript
1401
+ notification.sendNotification(
1402
+ 'battery-issue',
1403
+ {applianceId, target: {type: 'appliance', applianceId}},
1404
+ [
1405
+ {language: 'de', title: 'Dein Speicher reagiert nicht', body: 'Der Batteriespeicher folgt der Steuerung gerade nicht. Prüfe bitte, ob die externe Steuerung am Speicher aktiviert ist.'},
1406
+ {language: 'en', title: 'Your battery is not responding', body: 'The home battery is not following the control right now. Please check that external control is enabled on the battery.'},
1407
+ ],
1408
+ );
1409
+ ```
1410
+
1411
+ ##### Lost sign-in
1412
+
1413
+ `requestAuthentication()` already prompts the user, including for the first sign-in during onboarding.
1414
+ `authentication-required` is only for a sign-in that **used to work** and was lost. Send one only when:
1415
+
1416
+ - The package was `Authenticated` before, the vendor now rejects the credentials or token, and a refresh failed.
1417
+ - The failure is not transient. Timeouts or a vendor outage are a `connection-issue`.
1418
+ - The user did not sign out themselves.
1419
+
1420
+ Before sending, set the state with `setAuthenticationState()` and create a new request with
1421
+ `requestAuthentication()`, so the `authentication` target has a sign-in to open. Send it **at most once per lost
1422
+ session**, and not again until the package is `Authenticated` again. The host allows one per package (or per
1423
+ appliance, for an appliance sign-in) per 24 hours.
1424
+
1425
+ ```typescript
1426
+ await auth.setAuthenticationState({state: EnyoAuthenticationStateEnum.AuthenticationFailed});
1427
+ await auth.requestAuthentication({authenticationType: 'oauth', oauth: {}, oneTimeAuthentication: false});
1428
+
1429
+ notification.sendNotification(
1430
+ 'authentication-required',
1431
+ {target: {type: 'authentication'}},
1432
+ [
1433
+ {language: 'de', title: 'Bitte erneut anmelden', body: 'Deine Anmeldung ist abgelaufen. Melde dich erneut an, damit wir deine Geräte weiter steuern können.'},
1434
+ {language: 'en', title: 'Please sign in again', body: 'Your sign-in has expired. Sign in again so we can keep controlling your devices.'},
1435
+ ],
1436
+ );
1319
1437
  ```
1320
1438
 
1321
1439
  ### App Intelligence
@@ -11,6 +11,164 @@ import { EnyoNotificationCategory, EnyoNotificationCategoryRegistration, EnyoNot
11
11
  * The host may additionally enforce rate limits per category. When the limit
12
12
  * is exceeded, {@link sendNotification} fails with a `rate-limit` error and
13
13
  * the caller must back off before retrying.
14
+ *
15
+ * ## Sending a `charging-issue`
16
+ *
17
+ * The host relies on these rules and does not re-check them, so every app
18
+ * must send a `charging-issue` under the same conditions.
19
+ *
20
+ * Send it only when **all** of the following hold:
21
+ * - The app itself asked the charger to charge: it sent a start or resume,
22
+ * or it is giving the charger more than 0 W of available power.
23
+ * - The charger has stayed non-charging for at least 5 minutes after that
24
+ * request. Non-charging means its status is not
25
+ * {@link EnyoChargerApplianceStatusEnum.Charging}, or it is `Charging` but
26
+ * draws (almost) no power.
27
+ * - The car is not the one holding the charge: do **not** send while the
28
+ * charger reports {@link EnyoChargerApplianceStatusEnum.Suspended} with
29
+ * reason {@link EnyoChargerApplianceSuspendedReasonEnum.Ev}. That means the
30
+ * car's battery is full or the car paused charging itself, which is not a
31
+ * fault.
32
+ * - No grid-operator limit (§14a /
33
+ * {@link EnyoDataBusMessageEnum.GridOperatorPowerLimitationV1}) is
34
+ * currently pushing the charger to 0.
35
+ *
36
+ * Send it **at most once per plug-in**: after sending, wait until the car is
37
+ * unplugged ({@link EnyoChargerApplianceStatusEnum.Available}) before sending
38
+ * again for that appliance.
39
+ *
40
+ * Always pass `options.applianceId` and
41
+ * `options.target = { type: 'appliance', applianceId }`.
42
+ *
43
+ * The host enforces a rate limit of one `charging-issue` per appliance per
44
+ * 30 minutes. A notification sent within that window is dropped and the call
45
+ * fails with a `rate-limit` error.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const notification = sdk.useNotification();
50
+ *
51
+ * notification.registerNotificationCategory({
52
+ * category: 'charging-issue',
53
+ * name: [
54
+ * {language: 'de', name: 'Ladeprobleme'},
55
+ * {language: 'en', name: 'Charging issues'},
56
+ * ],
57
+ * description: [
58
+ * {language: 'de', name: 'Ladeprobleme', description: 'Wenn dein Auto nicht lädt, obwohl es laden sollte.'},
59
+ * {language: 'en', name: 'Charging issues', description: 'When your car is not charging although it should.'},
60
+ * ],
61
+ * });
62
+ *
63
+ * notification.sendNotification(
64
+ * 'charging-issue',
65
+ * {applianceId, target: {type: 'appliance', applianceId}},
66
+ * [
67
+ * {language: 'de', title: 'Dein Auto lädt nicht', body: 'Die Wallbox sollte gerade laden, tut es aber nicht. Prüfe bitte Auto und Ladekabel.'},
68
+ * {language: 'en', title: 'Your car is not charging', body: 'The wallbox should be charging right now but isn\'t. Please check the car and the charging cable.'},
69
+ * ],
70
+ * );
71
+ * ```
72
+ *
73
+ * ## Sending a `battery-issue`
74
+ *
75
+ * Send it only when **all** of the following hold:
76
+ * - The app itself commanded the battery: it sent a
77
+ * {@link EnyoDataBusMessageEnum.SetStorageControlV2} with mode `Controlled`
78
+ * (or one of the `Start…`/`SetStorage…` storage commands) that is still in
79
+ * effect. Never send while the battery runs on its own logic (`Auto`).
80
+ * - For at least 5 minutes after that command, the battery has not followed
81
+ * it, judged by `batteryPowerW` in
82
+ * {@link EnyoDataBusMessageEnum.BatteryValuesUpdateV1}:
83
+ * - `Charge`: the battery is not charging, or charges at (almost) 0 W.
84
+ * - `Discharge`: the battery is not discharging, or discharges at
85
+ * (almost) 0 W.
86
+ * - `Hold`: the battery charges or discharges noticeably.
87
+ * - The battery is not simply at its limit: do **not** send for a charge
88
+ * command while the state of charge is at or above
89
+ * {@link EnyoBatteryApplianceMetadata.maxSoC} (or 100 %), or for a
90
+ * discharge command while it is at or below
91
+ * {@link EnyoBatteryApplianceMetadata.minSoC}.
92
+ * - A discharge to the house is not capped by low household consumption.
93
+ * Without grid discharging, the battery can only deliver what the house
94
+ * draws, which is not a fault.
95
+ * - No grid-operator limit (§14a / `gridOperatorLimit` in
96
+ * `BatteryValuesUpdateV1`, or
97
+ * {@link EnyoDataBusMessageEnum.GridOperatorPowerLimitationV1}) is
98
+ * currently pushing the battery's power to 0.
99
+ * - Control is allowed: {@link EnyoBatteryApplianceMetadata.controlAllowed}
100
+ * is not `false`. (The app must not command such a battery at all.)
101
+ *
102
+ * Send it **at most once per issue**: after sending, wait until the battery
103
+ * follows a command again, or the app releases it back to `Auto`, before
104
+ * sending again for that appliance.
105
+ *
106
+ * Always pass `options.applianceId` and
107
+ * `options.target = { type: 'appliance', applianceId }`.
108
+ *
109
+ * The host enforces a rate limit of one `battery-issue` per appliance per
110
+ * 30 minutes. A notification sent within that window is dropped and the call
111
+ * fails with a `rate-limit` error.
112
+ *
113
+ * ## Sending an `authentication-required`
114
+ *
115
+ * {@link EnergyAppAuthentication.requestAuthentication} already prompts the
116
+ * user, including for the first sign-in during onboarding. This category is
117
+ * only for a sign-in that **used to work** and was lost.
118
+ *
119
+ * Send it only when **all** of the following hold:
120
+ * - The package was authenticated before
121
+ * ({@link EnyoAuthenticationStateEnum.Authenticated}) and the session is now
122
+ * unusable: the vendor rejects the credentials or token, and a refresh (if
123
+ * the vendor supports one) failed.
124
+ * - The failure is not transient. Timeouts, 5xx responses or the vendor cloud
125
+ * being down are a `connection-issue`, not an `authentication-required`.
126
+ * - The user did not sign out themselves: do **not** send after
127
+ * {@link EnergyAppAuthentication.signOut} or a sign-out reported through
128
+ * {@link EnergyAppAuthentication.listenForSignOut}.
129
+ *
130
+ * Before sending, set the state via
131
+ * {@link EnergyAppAuthentication.setAuthenticationState} (to
132
+ * `AuthenticationFailed` or `Unauthenticated`) and create a new request via
133
+ * {@link EnergyAppAuthentication.requestAuthentication}, so the target has a
134
+ * sign-in to open.
135
+ *
136
+ * Send it **at most once per lost session**: after sending, wait until the
137
+ * package is `Authenticated` again before sending another one.
138
+ *
139
+ * Pass `options.target = { type: 'authentication' }`. For an appliance
140
+ * sign-in, also pass `options.applianceId` and set the target's
141
+ * `applianceId`.
142
+ *
143
+ * The host enforces a rate limit of one `authentication-required` per
144
+ * package (or per appliance, for an appliance sign-in) per 24 hours.
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * const notification = sdk.useNotification();
149
+ * const auth = sdk.useAuthentication();
150
+ *
151
+ * notification.registerNotificationCategory({
152
+ * category: 'authentication-required',
153
+ * name: [
154
+ * {language: 'de', name: 'Anmeldung erforderlich'},
155
+ * {language: 'en', name: 'Sign-in required'},
156
+ * ],
157
+ * });
158
+ *
159
+ * // The vendor rejected the refresh token: the session is gone for good.
160
+ * await auth.setAuthenticationState({state: EnyoAuthenticationStateEnum.AuthenticationFailed});
161
+ * await auth.requestAuthentication({authenticationType: 'oauth', oauth: {}, oneTimeAuthentication: false});
162
+ *
163
+ * notification.sendNotification(
164
+ * 'authentication-required',
165
+ * {target: {type: 'authentication'}},
166
+ * [
167
+ * {language: 'de', title: 'Bitte erneut anmelden', body: 'Deine Anmeldung ist abgelaufen. Melde dich erneut an, damit wir deine Geräte weiter steuern können.'},
168
+ * {language: 'en', title: 'Please sign in again', body: 'Your sign-in has expired. Sign in again so we can keep controlling your devices.'},
169
+ * ],
170
+ * );
171
+ * ```
14
172
  */
15
173
  export interface EnergyAppNotification {
16
174
  /**
@@ -44,9 +202,13 @@ export interface EnergyAppNotification {
44
202
  * `title` (used for list rows and system banners) and a `body` (used in
45
203
  * the expanded detail view). When `options.target` is provided, the host
46
204
  * routes the user to that destination on interaction — e.g. an onboarding
47
- * guide for the `onboarding-required` category.
205
+ * guide for the `onboarding-required` category, or the appliance's live
206
+ * view (`{ type: 'appliance', applianceId }`) for `charging-issue` and
207
+ * `battery-issue`, or the pending sign-in (`{ type: 'authentication' }`)
208
+ * for `authentication-required`. The "Sending a …" sections on
209
+ * {@link EnergyAppNotification} describe when those categories may be
210
+ * sent.
48
211
  *
49
- * @param type - Visual style of the notification (info, warning, error, success).
50
212
  * @param category - Lifecycle category; must be registered beforehand.
51
213
  * @param options - Display/behavior options, including optional routing target.
52
214
  * @param translations - Per-language `title`/`body` pairs.
@@ -12,8 +12,19 @@ import { EnergyAppPackageLanguage } from "../energy-app-package-definition.cjs";
12
12
  * an appliance, gateway, or upstream service that requires user attention.
13
13
  * - `onboarding-required`: The package needs the user to complete (or revisit)
14
14
  * an onboarding flow before it can continue operating normally.
15
+ * - `charging-issue`: The app expected an appliance to charge — it sent a
16
+ * start or resume, gave the charger power, or scheduled a charge — but the
17
+ * appliance is not charging. See "Sending a `charging-issue`" on
18
+ * {@link EnergyAppNotification} for when to send it.
19
+ * - `battery-issue`: The app commanded a battery to charge, discharge or hold,
20
+ * but the battery is not following the command. See "Sending a
21
+ * `battery-issue`" on {@link EnergyAppNotification} for when to send it.
22
+ * - `authentication-required`: A sign-in that used to work was lost (expired
23
+ * or revoked) and the user has to sign in again before the package can
24
+ * continue. See "Sending an `authentication-required`" on
25
+ * {@link EnergyAppNotification} for when to send it.
15
26
  */
16
- export type EnyoNotificationCategory = 'connection-issue' | 'onboarding-required';
27
+ export type EnyoNotificationCategory = 'connection-issue' | 'onboarding-required' | 'charging-issue' | 'battery-issue' | 'authentication-required';
17
28
  /**
18
29
  * Registration payload describing a notification category to the host.
19
30
  *
@@ -45,7 +56,7 @@ export interface EnyoNotificationCategoryTranslation {
45
56
  * they interact with a notification. Discriminated by the `type` field so
46
57
  * additional target kinds can be added without breaking existing consumers.
47
58
  */
48
- export type EnyoNotificationTarget = EnyoNotificationOnboardingTarget | EnyoNotificationEnergyAppSettingsTarget | EnyoNotificationApplianceSettingsTarget | EnyoNotificationSubscriptionOfferTarget;
59
+ export type EnyoNotificationTarget = EnyoNotificationOnboardingTarget | EnyoNotificationEnergyAppSettingsTarget | EnyoNotificationApplianceSettingsTarget | EnyoNotificationSubscriptionOfferTarget | EnyoNotificationApplianceTarget | EnyoNotificationAuthenticationTarget;
49
60
  /**
50
61
  * Target that opens an onboarding guide previously registered with the host.
51
62
  *
@@ -73,6 +84,9 @@ export interface EnyoNotificationEnergyAppSettingsTarget {
73
84
  /**
74
85
  * Target that opens the settings screen for a specific appliance managed by
75
86
  * the energy app package.
87
+ *
88
+ * To show the appliance's live state instead (e.g. for a `charging-issue`),
89
+ * use {@link EnyoNotificationApplianceTarget}.
76
90
  */
77
91
  export interface EnyoNotificationApplianceSettingsTarget {
78
92
  /** Discriminator identifying this as an appliance settings target. */
@@ -83,6 +97,42 @@ export interface EnyoNotificationApplianceSettingsTarget {
83
97
  */
84
98
  applianceId: string;
85
99
  }
100
+ /**
101
+ * Target that opens the live view of a specific appliance (its current
102
+ * state, power and session), as opposed to its settings screen.
103
+ *
104
+ * This is the target to use for `charging-issue` and `battery-issue`
105
+ * notifications.
106
+ */
107
+ export interface EnyoNotificationApplianceTarget {
108
+ /** Discriminator identifying this as an appliance target. */
109
+ type: 'appliance';
110
+ /**
111
+ * Identifier of the appliance to open. Must match an appliance id known
112
+ * to the host.
113
+ */
114
+ applianceId: string;
115
+ }
116
+ /**
117
+ * Target that opens the package's pending sign-in, i.e. the authentication
118
+ * request previously created via
119
+ * {@link EnergyAppAuthentication.requestAuthentication}.
120
+ *
121
+ * This is the target to use for `authentication-required` notifications.
122
+ * Create the authentication request before sending the notification, so that
123
+ * there is a sign-in for the host to open.
124
+ */
125
+ export interface EnyoNotificationAuthenticationTarget {
126
+ /** Discriminator identifying this as an authentication target. */
127
+ type: 'authentication';
128
+ /**
129
+ * Identifier of the appliance the sign-in belongs to. Set it when the
130
+ * authentication request was created for a specific appliance
131
+ * ({@link EnyoAuthentication.applianceId}); omit it for a package-wide
132
+ * sign-in.
133
+ */
134
+ applianceId?: string;
135
+ }
86
136
  /**
87
137
  * Target that opens enyo's offer screen for one of the package's
88
138
  * subscription plans, from where the user can buy it.
@@ -136,6 +186,9 @@ export interface EnyoNotificationTranslation {
136
186
  * host via {@link EnergyAppNotification.registerNotificationCategory}.
137
187
  * - `rate-limit`: The package has exceeded the host's notification rate
138
188
  * limit for the supplied category and must back off before retrying.
189
+ * For `charging-issue` and `battery-issue` the limit is one notification
190
+ * per appliance per 30 minutes; for `authentication-required` it is one
191
+ * per package (or per appliance, for an appliance sign-in) per 24 hours.
139
192
  */
140
193
  export type EnyoNotificationSendErrorType = 'not-registered' | 'rate-limit';
141
194
  /**
@@ -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.38.0';
12
+ exports.SDK_VERSION = '1.39.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.38.0";
8
+ export declare const SDK_VERSION = "1.39.0";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -11,6 +11,164 @@ import { EnyoNotificationCategory, EnyoNotificationCategoryRegistration, EnyoNot
11
11
  * The host may additionally enforce rate limits per category. When the limit
12
12
  * is exceeded, {@link sendNotification} fails with a `rate-limit` error and
13
13
  * the caller must back off before retrying.
14
+ *
15
+ * ## Sending a `charging-issue`
16
+ *
17
+ * The host relies on these rules and does not re-check them, so every app
18
+ * must send a `charging-issue` under the same conditions.
19
+ *
20
+ * Send it only when **all** of the following hold:
21
+ * - The app itself asked the charger to charge: it sent a start or resume,
22
+ * or it is giving the charger more than 0 W of available power.
23
+ * - The charger has stayed non-charging for at least 5 minutes after that
24
+ * request. Non-charging means its status is not
25
+ * {@link EnyoChargerApplianceStatusEnum.Charging}, or it is `Charging` but
26
+ * draws (almost) no power.
27
+ * - The car is not the one holding the charge: do **not** send while the
28
+ * charger reports {@link EnyoChargerApplianceStatusEnum.Suspended} with
29
+ * reason {@link EnyoChargerApplianceSuspendedReasonEnum.Ev}. That means the
30
+ * car's battery is full or the car paused charging itself, which is not a
31
+ * fault.
32
+ * - No grid-operator limit (§14a /
33
+ * {@link EnyoDataBusMessageEnum.GridOperatorPowerLimitationV1}) is
34
+ * currently pushing the charger to 0.
35
+ *
36
+ * Send it **at most once per plug-in**: after sending, wait until the car is
37
+ * unplugged ({@link EnyoChargerApplianceStatusEnum.Available}) before sending
38
+ * again for that appliance.
39
+ *
40
+ * Always pass `options.applianceId` and
41
+ * `options.target = { type: 'appliance', applianceId }`.
42
+ *
43
+ * The host enforces a rate limit of one `charging-issue` per appliance per
44
+ * 30 minutes. A notification sent within that window is dropped and the call
45
+ * fails with a `rate-limit` error.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const notification = sdk.useNotification();
50
+ *
51
+ * notification.registerNotificationCategory({
52
+ * category: 'charging-issue',
53
+ * name: [
54
+ * {language: 'de', name: 'Ladeprobleme'},
55
+ * {language: 'en', name: 'Charging issues'},
56
+ * ],
57
+ * description: [
58
+ * {language: 'de', name: 'Ladeprobleme', description: 'Wenn dein Auto nicht lädt, obwohl es laden sollte.'},
59
+ * {language: 'en', name: 'Charging issues', description: 'When your car is not charging although it should.'},
60
+ * ],
61
+ * });
62
+ *
63
+ * notification.sendNotification(
64
+ * 'charging-issue',
65
+ * {applianceId, target: {type: 'appliance', applianceId}},
66
+ * [
67
+ * {language: 'de', title: 'Dein Auto lädt nicht', body: 'Die Wallbox sollte gerade laden, tut es aber nicht. Prüfe bitte Auto und Ladekabel.'},
68
+ * {language: 'en', title: 'Your car is not charging', body: 'The wallbox should be charging right now but isn\'t. Please check the car and the charging cable.'},
69
+ * ],
70
+ * );
71
+ * ```
72
+ *
73
+ * ## Sending a `battery-issue`
74
+ *
75
+ * Send it only when **all** of the following hold:
76
+ * - The app itself commanded the battery: it sent a
77
+ * {@link EnyoDataBusMessageEnum.SetStorageControlV2} with mode `Controlled`
78
+ * (or one of the `Start…`/`SetStorage…` storage commands) that is still in
79
+ * effect. Never send while the battery runs on its own logic (`Auto`).
80
+ * - For at least 5 minutes after that command, the battery has not followed
81
+ * it, judged by `batteryPowerW` in
82
+ * {@link EnyoDataBusMessageEnum.BatteryValuesUpdateV1}:
83
+ * - `Charge`: the battery is not charging, or charges at (almost) 0 W.
84
+ * - `Discharge`: the battery is not discharging, or discharges at
85
+ * (almost) 0 W.
86
+ * - `Hold`: the battery charges or discharges noticeably.
87
+ * - The battery is not simply at its limit: do **not** send for a charge
88
+ * command while the state of charge is at or above
89
+ * {@link EnyoBatteryApplianceMetadata.maxSoC} (or 100 %), or for a
90
+ * discharge command while it is at or below
91
+ * {@link EnyoBatteryApplianceMetadata.minSoC}.
92
+ * - A discharge to the house is not capped by low household consumption.
93
+ * Without grid discharging, the battery can only deliver what the house
94
+ * draws, which is not a fault.
95
+ * - No grid-operator limit (§14a / `gridOperatorLimit` in
96
+ * `BatteryValuesUpdateV1`, or
97
+ * {@link EnyoDataBusMessageEnum.GridOperatorPowerLimitationV1}) is
98
+ * currently pushing the battery's power to 0.
99
+ * - Control is allowed: {@link EnyoBatteryApplianceMetadata.controlAllowed}
100
+ * is not `false`. (The app must not command such a battery at all.)
101
+ *
102
+ * Send it **at most once per issue**: after sending, wait until the battery
103
+ * follows a command again, or the app releases it back to `Auto`, before
104
+ * sending again for that appliance.
105
+ *
106
+ * Always pass `options.applianceId` and
107
+ * `options.target = { type: 'appliance', applianceId }`.
108
+ *
109
+ * The host enforces a rate limit of one `battery-issue` per appliance per
110
+ * 30 minutes. A notification sent within that window is dropped and the call
111
+ * fails with a `rate-limit` error.
112
+ *
113
+ * ## Sending an `authentication-required`
114
+ *
115
+ * {@link EnergyAppAuthentication.requestAuthentication} already prompts the
116
+ * user, including for the first sign-in during onboarding. This category is
117
+ * only for a sign-in that **used to work** and was lost.
118
+ *
119
+ * Send it only when **all** of the following hold:
120
+ * - The package was authenticated before
121
+ * ({@link EnyoAuthenticationStateEnum.Authenticated}) and the session is now
122
+ * unusable: the vendor rejects the credentials or token, and a refresh (if
123
+ * the vendor supports one) failed.
124
+ * - The failure is not transient. Timeouts, 5xx responses or the vendor cloud
125
+ * being down are a `connection-issue`, not an `authentication-required`.
126
+ * - The user did not sign out themselves: do **not** send after
127
+ * {@link EnergyAppAuthentication.signOut} or a sign-out reported through
128
+ * {@link EnergyAppAuthentication.listenForSignOut}.
129
+ *
130
+ * Before sending, set the state via
131
+ * {@link EnergyAppAuthentication.setAuthenticationState} (to
132
+ * `AuthenticationFailed` or `Unauthenticated`) and create a new request via
133
+ * {@link EnergyAppAuthentication.requestAuthentication}, so the target has a
134
+ * sign-in to open.
135
+ *
136
+ * Send it **at most once per lost session**: after sending, wait until the
137
+ * package is `Authenticated` again before sending another one.
138
+ *
139
+ * Pass `options.target = { type: 'authentication' }`. For an appliance
140
+ * sign-in, also pass `options.applianceId` and set the target's
141
+ * `applianceId`.
142
+ *
143
+ * The host enforces a rate limit of one `authentication-required` per
144
+ * package (or per appliance, for an appliance sign-in) per 24 hours.
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * const notification = sdk.useNotification();
149
+ * const auth = sdk.useAuthentication();
150
+ *
151
+ * notification.registerNotificationCategory({
152
+ * category: 'authentication-required',
153
+ * name: [
154
+ * {language: 'de', name: 'Anmeldung erforderlich'},
155
+ * {language: 'en', name: 'Sign-in required'},
156
+ * ],
157
+ * });
158
+ *
159
+ * // The vendor rejected the refresh token: the session is gone for good.
160
+ * await auth.setAuthenticationState({state: EnyoAuthenticationStateEnum.AuthenticationFailed});
161
+ * await auth.requestAuthentication({authenticationType: 'oauth', oauth: {}, oneTimeAuthentication: false});
162
+ *
163
+ * notification.sendNotification(
164
+ * 'authentication-required',
165
+ * {target: {type: 'authentication'}},
166
+ * [
167
+ * {language: 'de', title: 'Bitte erneut anmelden', body: 'Deine Anmeldung ist abgelaufen. Melde dich erneut an, damit wir deine Geräte weiter steuern können.'},
168
+ * {language: 'en', title: 'Please sign in again', body: 'Your sign-in has expired. Sign in again so we can keep controlling your devices.'},
169
+ * ],
170
+ * );
171
+ * ```
14
172
  */
15
173
  export interface EnergyAppNotification {
16
174
  /**
@@ -44,9 +202,13 @@ export interface EnergyAppNotification {
44
202
  * `title` (used for list rows and system banners) and a `body` (used in
45
203
  * the expanded detail view). When `options.target` is provided, the host
46
204
  * routes the user to that destination on interaction — e.g. an onboarding
47
- * guide for the `onboarding-required` category.
205
+ * guide for the `onboarding-required` category, or the appliance's live
206
+ * view (`{ type: 'appliance', applianceId }`) for `charging-issue` and
207
+ * `battery-issue`, or the pending sign-in (`{ type: 'authentication' }`)
208
+ * for `authentication-required`. The "Sending a …" sections on
209
+ * {@link EnergyAppNotification} describe when those categories may be
210
+ * sent.
48
211
  *
49
- * @param type - Visual style of the notification (info, warning, error, success).
50
212
  * @param category - Lifecycle category; must be registered beforehand.
51
213
  * @param options - Display/behavior options, including optional routing target.
52
214
  * @param translations - Per-language `title`/`body` pairs.
@@ -12,8 +12,19 @@ import { EnergyAppPackageLanguage } from "../energy-app-package-definition.js";
12
12
  * an appliance, gateway, or upstream service that requires user attention.
13
13
  * - `onboarding-required`: The package needs the user to complete (or revisit)
14
14
  * an onboarding flow before it can continue operating normally.
15
+ * - `charging-issue`: The app expected an appliance to charge — it sent a
16
+ * start or resume, gave the charger power, or scheduled a charge — but the
17
+ * appliance is not charging. See "Sending a `charging-issue`" on
18
+ * {@link EnergyAppNotification} for when to send it.
19
+ * - `battery-issue`: The app commanded a battery to charge, discharge or hold,
20
+ * but the battery is not following the command. See "Sending a
21
+ * `battery-issue`" on {@link EnergyAppNotification} for when to send it.
22
+ * - `authentication-required`: A sign-in that used to work was lost (expired
23
+ * or revoked) and the user has to sign in again before the package can
24
+ * continue. See "Sending an `authentication-required`" on
25
+ * {@link EnergyAppNotification} for when to send it.
15
26
  */
16
- export type EnyoNotificationCategory = 'connection-issue' | 'onboarding-required';
27
+ export type EnyoNotificationCategory = 'connection-issue' | 'onboarding-required' | 'charging-issue' | 'battery-issue' | 'authentication-required';
17
28
  /**
18
29
  * Registration payload describing a notification category to the host.
19
30
  *
@@ -45,7 +56,7 @@ export interface EnyoNotificationCategoryTranslation {
45
56
  * they interact with a notification. Discriminated by the `type` field so
46
57
  * additional target kinds can be added without breaking existing consumers.
47
58
  */
48
- export type EnyoNotificationTarget = EnyoNotificationOnboardingTarget | EnyoNotificationEnergyAppSettingsTarget | EnyoNotificationApplianceSettingsTarget | EnyoNotificationSubscriptionOfferTarget;
59
+ export type EnyoNotificationTarget = EnyoNotificationOnboardingTarget | EnyoNotificationEnergyAppSettingsTarget | EnyoNotificationApplianceSettingsTarget | EnyoNotificationSubscriptionOfferTarget | EnyoNotificationApplianceTarget | EnyoNotificationAuthenticationTarget;
49
60
  /**
50
61
  * Target that opens an onboarding guide previously registered with the host.
51
62
  *
@@ -73,6 +84,9 @@ export interface EnyoNotificationEnergyAppSettingsTarget {
73
84
  /**
74
85
  * Target that opens the settings screen for a specific appliance managed by
75
86
  * the energy app package.
87
+ *
88
+ * To show the appliance's live state instead (e.g. for a `charging-issue`),
89
+ * use {@link EnyoNotificationApplianceTarget}.
76
90
  */
77
91
  export interface EnyoNotificationApplianceSettingsTarget {
78
92
  /** Discriminator identifying this as an appliance settings target. */
@@ -83,6 +97,42 @@ export interface EnyoNotificationApplianceSettingsTarget {
83
97
  */
84
98
  applianceId: string;
85
99
  }
100
+ /**
101
+ * Target that opens the live view of a specific appliance (its current
102
+ * state, power and session), as opposed to its settings screen.
103
+ *
104
+ * This is the target to use for `charging-issue` and `battery-issue`
105
+ * notifications.
106
+ */
107
+ export interface EnyoNotificationApplianceTarget {
108
+ /** Discriminator identifying this as an appliance target. */
109
+ type: 'appliance';
110
+ /**
111
+ * Identifier of the appliance to open. Must match an appliance id known
112
+ * to the host.
113
+ */
114
+ applianceId: string;
115
+ }
116
+ /**
117
+ * Target that opens the package's pending sign-in, i.e. the authentication
118
+ * request previously created via
119
+ * {@link EnergyAppAuthentication.requestAuthentication}.
120
+ *
121
+ * This is the target to use for `authentication-required` notifications.
122
+ * Create the authentication request before sending the notification, so that
123
+ * there is a sign-in for the host to open.
124
+ */
125
+ export interface EnyoNotificationAuthenticationTarget {
126
+ /** Discriminator identifying this as an authentication target. */
127
+ type: 'authentication';
128
+ /**
129
+ * Identifier of the appliance the sign-in belongs to. Set it when the
130
+ * authentication request was created for a specific appliance
131
+ * ({@link EnyoAuthentication.applianceId}); omit it for a package-wide
132
+ * sign-in.
133
+ */
134
+ applianceId?: string;
135
+ }
86
136
  /**
87
137
  * Target that opens enyo's offer screen for one of the package's
88
138
  * subscription plans, from where the user can buy it.
@@ -136,6 +186,9 @@ export interface EnyoNotificationTranslation {
136
186
  * host via {@link EnergyAppNotification.registerNotificationCategory}.
137
187
  * - `rate-limit`: The package has exceeded the host's notification rate
138
188
  * limit for the supplied category and must back off before retrying.
189
+ * For `charging-issue` and `battery-issue` the limit is one notification
190
+ * per appliance per 30 minutes; for `authentication-required` it is one
191
+ * per package (or per appliance, for an appliance sign-in) per 24 hours.
139
192
  */
140
193
  export type EnyoNotificationSendErrorType = 'not-registered' | 'rate-limit';
141
194
  /**
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.38.0";
8
+ export declare const SDK_VERSION = "1.39.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.38.0';
8
+ export const SDK_VERSION = '1.39.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.38.0",
3
+ "version": "1.39.0",
4
4
  "description": "enyo Energy App SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",