@enyo-energy/energy-app-sdk 1.44.0 → 1.45.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 +141 -1
- package/dist/cjs/energy-app-package-definition.d.cts +46 -1
- package/dist/cjs/implementations/heatpump/heatpump-metadata-validators.cjs +79 -1
- package/dist/cjs/implementations/heatpump/heatpump-metadata-validators.d.cts +4 -2
- package/dist/cjs/implementations/subscriptions/define-subscription-plan.cjs +48 -0
- package/dist/cjs/implementations/subscriptions/define-subscription-plan.d.cts +40 -0
- package/dist/cjs/implementations/subscriptions/subscription-offer-validators.cjs +301 -0
- package/dist/cjs/implementations/subscriptions/subscription-offer-validators.d.cts +54 -0
- package/dist/cjs/implementations/subscriptions/subscription-plan-validators.cjs +8 -0
- package/dist/cjs/implementations/subscriptions/subscription-plan-validators.d.cts +8 -3
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/types/enyo-authentication.d.cts +11 -0
- package/dist/cjs/types/enyo-heatpump-appliance.cjs +33 -1
- package/dist/cjs/types/enyo-heatpump-appliance.d.cts +86 -0
- package/dist/cjs/types/enyo-settings.d.cts +22 -1
- package/dist/cjs/types/enyo-subscription-offer.cjs +113 -0
- package/dist/cjs/types/enyo-subscription-offer.d.cts +358 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/energy-app-package-definition.d.ts +46 -1
- package/dist/implementations/heatpump/heatpump-metadata-validators.d.ts +4 -2
- package/dist/implementations/heatpump/heatpump-metadata-validators.js +80 -2
- package/dist/implementations/subscriptions/define-subscription-plan.d.ts +40 -0
- package/dist/implementations/subscriptions/define-subscription-plan.js +45 -0
- package/dist/implementations/subscriptions/subscription-offer-validators.d.ts +54 -0
- package/dist/implementations/subscriptions/subscription-offer-validators.js +298 -0
- package/dist/implementations/subscriptions/subscription-plan-validators.d.ts +8 -3
- package/dist/implementations/subscriptions/subscription-plan-validators.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types/enyo-authentication.d.ts +11 -0
- package/dist/types/enyo-heatpump-appliance.d.ts +86 -0
- package/dist/types/enyo-heatpump-appliance.js +32 -0
- package/dist/types/enyo-settings.d.ts +22 -1
- package/dist/types/enyo-subscription-offer.d.ts +358 -0
- package/dist/types/enyo-subscription-offer.js +110 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1255,6 +1255,33 @@ settings.listenForSettingsChanges((settingName, newValue) => {
|
|
|
1255
1255
|
const allSettings = await settings.getSettingsConfig();
|
|
1256
1256
|
```
|
|
1257
1257
|
|
|
1258
|
+
##### Action settings (buttons)
|
|
1259
|
+
|
|
1260
|
+
A setting of type `'action'` renders as a button for a one-off command (restart a device, start a self-test, …). Every tap writes the current time as an ISO 8601 string to the setting, so each tap arrives as a change event — react to the event, not to the stored value.
|
|
1261
|
+
|
|
1262
|
+
Use an action only for something that *happens*. Anything that has a state the user turns on or off (e.g. "charging cable locked") is a `checkbox`, not an action.
|
|
1263
|
+
|
|
1264
|
+
```typescript
|
|
1265
|
+
await settings.addSettingConfig({
|
|
1266
|
+
name: `restart-${applianceId}`,
|
|
1267
|
+
applianceId,
|
|
1268
|
+
type: 'action',
|
|
1269
|
+
required: false,
|
|
1270
|
+
fieldName: [{ language: 'en', value: 'Restart wallbox' }, { language: 'de', value: 'Wallbox neu starten' }],
|
|
1271
|
+
actionOptions: {
|
|
1272
|
+
buttonCaption: [{ language: 'en', value: 'Restart now' }, { language: 'de', value: 'Jetzt neu starten' }],
|
|
1273
|
+
// optional: ask before triggering
|
|
1274
|
+
confirmationText: [{ language: 'en', value: 'Restart the wallbox?' }, { language: 'de', value: 'Wallbox neu starten?' }],
|
|
1275
|
+
},
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
settings.listenForSettingsChanges((event) => {
|
|
1279
|
+
if (event.settingName === `restart-${applianceId}`) {
|
|
1280
|
+
void restartWallbox();
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1258
1285
|
#### `useConfigurationManager(): EnergyAppConfigurationManager`
|
|
1259
1286
|
|
|
1260
1287
|
Register **internal**, non user-facing configurations for your package and react to value changes. Unlike `useSettings()`, configurations registered here are NOT rendered in the Energy App UI — they are intended for values the package itself reads and writes at runtime (e.g. internal feature toggles, tuning parameters, calibration values) and need to be persisted across restarts.
|
|
@@ -3180,7 +3207,9 @@ const packageDef = defineEnergyAppPackage({
|
|
|
3180
3207
|
| `pricingModel` | `per-package` (default): one price for the package on the device. `per-unit`: the price is charged per unit — see [Per-unit plans](#per-unit-plans). |
|
|
3181
3208
|
| `prices` | One net price per currency. `amountWithoutTax` is always the **total for one interval** — per month, per year, or the single one-time amount; per unit for `per-unit` plans — in the currency (not cent), at most two decimals; enyo adds VAT. |
|
|
3182
3209
|
| `trialDays` | Optional free trial in days. Not allowed for `one-time` plans. |
|
|
3183
|
-
| `storeEntry` | Title, short description and optional benefits; `de` and `en` are required. |
|
|
3210
|
+
| `storeEntry` | Title, short description and optional benefits; `de` and `en` are required. Prefer `includedFeatures` over `benefits`. |
|
|
3211
|
+
| `includedFeatures` | Optional feature keys from `subscriptionFeatures`, or `{ featureKey, value }` with a translated value — see [Offer screen](#offer-screen). |
|
|
3212
|
+
| `highlight` | Optional badge: `recommended`, `best-value` or `most-popular`. At most one plan per package. |
|
|
3184
3213
|
| `internalComment` | Optional note, never shown to users. |
|
|
3185
3214
|
|
|
3186
3215
|
What a plan unlocks is decided by the app: check `isPlanActive(subscriptions, planKey)` at runtime (see
|
|
@@ -3209,6 +3238,97 @@ In tests, `InMemorySubscriptions` takes the mode as its third constructor argume
|
|
|
3209
3238
|
throws when an `optional` package reports `SubscriptionRequired`. The same check is available as
|
|
3210
3239
|
`assertEnergyAppStateMatchesSubscriptionMode(mode, state)`.
|
|
3211
3240
|
|
|
3241
|
+
### Offer screen
|
|
3242
|
+
|
|
3243
|
+
Promote the plans on the screen where enyo sells them: declare a **feature catalog** once, say which features the
|
|
3244
|
+
**free tier** and each plan include, and build the screen from **blocks**. All three are optional. Without an offer
|
|
3245
|
+
screen, enyo shows only the plan cards.
|
|
3246
|
+
|
|
3247
|
+
```typescript
|
|
3248
|
+
import {
|
|
3249
|
+
defineSubscriptionFeature,
|
|
3250
|
+
defineSubscriptionOfferScreen,
|
|
3251
|
+
EnyoSubscriptionOfferHintKindEnum,
|
|
3252
|
+
EnyoSubscriptionOfferIconEnum,
|
|
3253
|
+
EnyoSubscriptionPlanHighlightEnum
|
|
3254
|
+
} from '@enyo-energy/energy-app-sdk';
|
|
3255
|
+
|
|
3256
|
+
defineEnergyAppPackage({
|
|
3257
|
+
// ...
|
|
3258
|
+
subscriptionMode: EnyoSubscriptionModeEnum.Optional,
|
|
3259
|
+
subscriptionFeatures: [
|
|
3260
|
+
defineSubscriptionFeature({
|
|
3261
|
+
featureKey: 'pv-charging',
|
|
3262
|
+
icon: EnyoSubscriptionOfferIconEnum.Sun,
|
|
3263
|
+
storeEntry: [
|
|
3264
|
+
{ language: 'en', title: 'PV surplus charging' },
|
|
3265
|
+
{ language: 'de', title: 'PV-Überschussladen' }
|
|
3266
|
+
]
|
|
3267
|
+
}),
|
|
3268
|
+
defineSubscriptionFeature({
|
|
3269
|
+
featureKey: 'price-optimisation',
|
|
3270
|
+
icon: EnyoSubscriptionOfferIconEnum.PiggyBank,
|
|
3271
|
+
storeEntry: [
|
|
3272
|
+
{ language: 'en', title: 'Dynamic price optimisation', description: 'Charges when electricity is cheapest.' },
|
|
3273
|
+
{ language: 'de', title: 'Optimierung nach dynamischen Preisen', description: 'Lädt, wenn Strom am günstigsten ist.' }
|
|
3274
|
+
]
|
|
3275
|
+
}),
|
|
3276
|
+
defineSubscriptionFeature({
|
|
3277
|
+
featureKey: 'vehicles',
|
|
3278
|
+
icon: EnyoSubscriptionOfferIconEnum.Car,
|
|
3279
|
+
storeEntry: [{ language: 'en', title: 'Connected vehicles' }, { language: 'de', title: 'Verbundene Fahrzeuge' }]
|
|
3280
|
+
})
|
|
3281
|
+
],
|
|
3282
|
+
subscriptionFreeTier: {
|
|
3283
|
+
storeEntry: [{ language: 'en', title: 'Basic' }, { language: 'de', title: 'Basis' }],
|
|
3284
|
+
includedFeatures: ['pv-charging']
|
|
3285
|
+
},
|
|
3286
|
+
subscriptionPlans: [
|
|
3287
|
+
defineSubscriptionPlan({
|
|
3288
|
+
planKey: 'premium',
|
|
3289
|
+
highlight: EnyoSubscriptionPlanHighlightEnum.Recommended,
|
|
3290
|
+
includedFeatures: [
|
|
3291
|
+
'pv-charging',
|
|
3292
|
+
'price-optimisation',
|
|
3293
|
+
{ featureKey: 'vehicles', value: [{ language: 'en', value: 'up to 5' }, { language: 'de', value: 'bis zu 5' }] }
|
|
3294
|
+
],
|
|
3295
|
+
// paymentInterval, prices, storeEntry ...
|
|
3296
|
+
})
|
|
3297
|
+
],
|
|
3298
|
+
subscriptionOfferScreen: defineSubscriptionOfferScreen({
|
|
3299
|
+
blocks: [
|
|
3300
|
+
{ type: 'hero', icon: 'piggy-bank', storeEntry: [
|
|
3301
|
+
{ language: 'en', title: 'Charge for less', text: 'Smart charging with dynamic prices.' },
|
|
3302
|
+
{ language: 'de', title: 'Günstiger laden', text: 'Intelligentes Laden mit dynamischen Preisen.' }
|
|
3303
|
+
] },
|
|
3304
|
+
{ type: 'comparison' }, // Basic vs. Premium, one row per feature
|
|
3305
|
+
{ type: 'hint', kind: EnyoSubscriptionOfferHintKindEnum.PricingReason, icon: 'handshake', storeEntry: [
|
|
3306
|
+
{ language: 'en', title: 'Why does this cost money?', text: 'The car vendors charge us for every connected vehicle.' },
|
|
3307
|
+
{ language: 'de', title: 'Warum kostet das Geld?', text: 'Die Fahrzeughersteller berechnen uns jedes verbundene Fahrzeug.' }
|
|
3308
|
+
] },
|
|
3309
|
+
{ type: 'plans' }
|
|
3310
|
+
]
|
|
3311
|
+
})
|
|
3312
|
+
});
|
|
3313
|
+
```
|
|
3314
|
+
|
|
3315
|
+
| Block | Shows | Notes |
|
|
3316
|
+
|---|---|---|
|
|
3317
|
+
| `hero` | A large headline with optional text and icon | `title` is required. |
|
|
3318
|
+
| `features` | Features with icon and description | `featureKeys` picks and orders them; omit it to list all. |
|
|
3319
|
+
| `comparison` | A table: one row per feature, one column per tier | The free column is shown when a free tier is declared (`includeFreeTier` overrides this). `planKeys` and `featureKeys` pick the columns and rows. A cell shows a check mark, the feature's `value`, or nothing. Needs at least two columns. |
|
|
3320
|
+
| `hint` | A highlighted note | `kind`: `pricing-reason` (why it costs money), `requirement`, `info`, `legal`. `text` is required. |
|
|
3321
|
+
| `plans` | The plan cards and checkout | At most one. Without it, the plans follow the last block. |
|
|
3322
|
+
|
|
3323
|
+
- **Icons** come from a fixed set, `EnyoSubscriptionOfferIconEnum` (`piggy-bank`, `sun`, `car`, `wallbox`, `heat-pump`,
|
|
3324
|
+
`handshake`, …), so every app's offer screen looks the same.
|
|
3325
|
+
- **A plan's `includedFeatures` should list everything the customer gets with it**, including the free tier's
|
|
3326
|
+
features. The comparison table takes each column straight from the tier's list.
|
|
3327
|
+
- **`subscriptionFreeTier` is only allowed with `subscriptionMode: 'optional'`**.
|
|
3328
|
+
- Every text needs `de` and `en`. `validatePackageSubscriptionPlans()` checks all of this as well (it calls
|
|
3329
|
+
`validatePackageSubscriptionOffer()`). It also warns about features that no tier includes and comparison rows
|
|
3330
|
+
that stay empty.
|
|
3331
|
+
|
|
3212
3332
|
### Per-unit plans
|
|
3213
3333
|
|
|
3214
3334
|
For a `per-unit` plan the price is charged once per unit — 3 units on a yearly plan for 10 € cost 30 € per year
|
|
@@ -3873,6 +3993,26 @@ await applianceManager.updateHeatpumpDomesticHotWater(applianceId, 0, { timeProg
|
|
|
3873
3993
|
|
|
3874
3994
|
Validate before publishing with `validateHeatpumpHeatingCurve`, `validateHeatpumpTimeProgram` or `validateHeatpumpAdvancedMetadata` (or their `assertValid*` counterparts). Each returns `{ ok, errors, warnings }`.
|
|
3875
3995
|
|
|
3996
|
+
**SG Ready relays.** You can declare 0–n SG Ready inputs in `metadata.heatpump.sgReadyRelays`. Each one has a
|
|
3997
|
+
`function` that says what it acts on: `heating` (a heating circuit), `dhw-heatpump` (DHW heated by the compressor)
|
|
3998
|
+
or `dhw-heating-rod` (DHW heated by the heating rod). `zoneIndex` names the heating circuit or DHW zone; omit it for
|
|
3999
|
+
the first one. If only some of the four states (`blocked`, `normal`, `recommended`, `forced`) are wired, list them
|
|
4000
|
+
in `supportedStates`. `activeState` reports the current state. This works independently of `connectionType`.
|
|
4001
|
+
|
|
4002
|
+
```typescript
|
|
4003
|
+
sgReadyRelays: [
|
|
4004
|
+
{ index: 0, function: EnyoHeatpumpSgReadyFunctionEnum.Heating, zoneIndex: 0, name: 'K1/K2' },
|
|
4005
|
+
{ index: 1, function: EnyoHeatpumpSgReadyFunctionEnum.DhwHeatpump, name: 'K3/K4' },
|
|
4006
|
+
{ index: 2, function: EnyoHeatpumpSgReadyFunctionEnum.DhwHeatingRod,
|
|
4007
|
+
supportedStates: [EnyoHeatpumpSgReadyStateEnum.Normal, EnyoHeatpumpSgReadyStateEnum.Forced] },
|
|
4008
|
+
]
|
|
4009
|
+
```
|
|
4010
|
+
|
|
4011
|
+
`validateHeatpumpAdvancedMetadata` checks the relays too. It rejects duplicate indexes, unknown functions or
|
|
4012
|
+
states, and an `activeState` that is not in `supportedStates`. It warns about unknown zones, two relays with the
|
|
4013
|
+
same function on one zone, a relay without `normal`, and a `dhw-heating-rod` relay when `additionalDevices` has
|
|
4014
|
+
no `HeatingRod`.
|
|
4015
|
+
|
|
3876
4016
|
### WallboxIntegrationEnergyApp
|
|
3877
4017
|
|
|
3878
4018
|
Drives an EV wallbox / charger. Has the richest command surface of all integrations.
|
|
@@ -4,6 +4,7 @@ import type { EnyoFileTranslation } from "./types/enyo-file.cjs";
|
|
|
4
4
|
import type { EnyoOnboardingV2Guide } from "./types/enyo-onboarding-v2.cjs";
|
|
5
5
|
import type { EnyoOnboardingTranslatedContent } from "./types/enyo-onboarding.cjs";
|
|
6
6
|
import type { EnyoSubscriptionMode, EnyoSubscriptionPaymentInterval, EnyoSubscriptionPrice, EnyoSubscriptionPricingModel } from "./types/enyo-subscription.cjs";
|
|
7
|
+
import type { EnyoSubscriptionFeature, EnyoSubscriptionFreeTier, EnyoSubscriptionIncludedFeature, EnyoSubscriptionOfferScreen, EnyoSubscriptionPlanHighlight } from "./types/enyo-subscription-offer.cjs";
|
|
7
8
|
export type EnergyAppPackageLanguage = 'de' | 'en';
|
|
8
9
|
export declare enum EnergyAppPackageCategory {
|
|
9
10
|
Inverter = "inverter",
|
|
@@ -294,7 +295,13 @@ export interface EnergyAppPackageSubscriptionPlanStoreEntry {
|
|
|
294
295
|
title: string;
|
|
295
296
|
/** One sentence describing what the plan unlocks */
|
|
296
297
|
shortDescription: string;
|
|
297
|
-
/**
|
|
298
|
+
/**
|
|
299
|
+
* Optional list of benefits, shown as bullet points on the plan card.
|
|
300
|
+
*
|
|
301
|
+
* Prefer {@link EnergyAppPackageSubscriptionPlan.includedFeatures}: features
|
|
302
|
+
* carry icons and descriptions and feed the comparison table. The host
|
|
303
|
+
* shows `benefits` only for plans without `includedFeatures`.
|
|
304
|
+
*/
|
|
298
305
|
benefits?: string[];
|
|
299
306
|
}
|
|
300
307
|
/**
|
|
@@ -358,6 +365,21 @@ export interface EnergyAppPackageSubscriptionPlan {
|
|
|
358
365
|
trialDays?: number;
|
|
359
366
|
/** Localized store information. Entries for `de` and `en` are required. */
|
|
360
367
|
storeEntry: EnergyAppPackageSubscriptionPlanStoreEntry[];
|
|
368
|
+
/**
|
|
369
|
+
* The features this plan includes, as keys of
|
|
370
|
+
* {@link EnergyAppPackageDefinition.subscriptionFeatures} — or with a
|
|
371
|
+
* translated value such as "up to 5 vehicles" — in display order. Shown on
|
|
372
|
+
* the plan card and in the comparison table.
|
|
373
|
+
*
|
|
374
|
+
* List every feature the customer gets with the plan, including those of
|
|
375
|
+
* the free tier, so the comparison table reads correctly.
|
|
376
|
+
*/
|
|
377
|
+
includedFeatures?: EnyoSubscriptionIncludedFeature[];
|
|
378
|
+
/**
|
|
379
|
+
* Optional badge that makes this plan stand out on the offer screen, e.g.
|
|
380
|
+
* `recommended`. At most one plan of a package may be highlighted.
|
|
381
|
+
*/
|
|
382
|
+
highlight?: EnyoSubscriptionPlanHighlight;
|
|
361
383
|
/** Optional internal note explaining this plan; never shown to users. */
|
|
362
384
|
internalComment?: string;
|
|
363
385
|
}
|
|
@@ -1064,6 +1086,29 @@ export interface EnergyAppPackageDefinition {
|
|
|
1064
1086
|
* Omit for packages that are free.
|
|
1065
1087
|
*/
|
|
1066
1088
|
subscriptionPlans?: EnergyAppPackageSubscriptionPlan[];
|
|
1089
|
+
/**
|
|
1090
|
+
* The package's feature catalog: every feature that the plans and the free
|
|
1091
|
+
* tier refer to, declared once with its icon and translations. enyo builds
|
|
1092
|
+
* the feature lists and the comparison table of the offer screen from it.
|
|
1093
|
+
*
|
|
1094
|
+
* Only used together with {@link subscriptionPlans}. Validate it with
|
|
1095
|
+
* `validatePackageSubscriptionPlans()`.
|
|
1096
|
+
*/
|
|
1097
|
+
subscriptionFeatures?: EnyoSubscriptionFeature[];
|
|
1098
|
+
/**
|
|
1099
|
+
* What the app offers without a plan, shown as the free column of the
|
|
1100
|
+
* comparison table. Only allowed with `subscriptionMode: 'optional'`.
|
|
1101
|
+
*/
|
|
1102
|
+
subscriptionFreeTier?: EnyoSubscriptionFreeTier;
|
|
1103
|
+
/**
|
|
1104
|
+
* The blocks of the screen on which enyo offers the plans — a headline,
|
|
1105
|
+
* feature lists, a comparison table, hints such as why the app costs
|
|
1106
|
+
* money, and the plan cards — in display order.
|
|
1107
|
+
*
|
|
1108
|
+
* Omit to show only the plan cards. Validate it with
|
|
1109
|
+
* `validatePackageSubscriptionPlans()`.
|
|
1110
|
+
*/
|
|
1111
|
+
subscriptionOfferScreen?: EnyoSubscriptionOfferScreen;
|
|
1067
1112
|
/**
|
|
1068
1113
|
* Whether the app needs one of its plans to work at all.
|
|
1069
1114
|
*
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Client-side validation for the advanced heatpump metadata an integration
|
|
4
4
|
* publishes: heating curves ({@link EnyoHeatpumpApplianceHeatingCurve}), time
|
|
5
5
|
* programs ({@link EnyoHeatpumpTimeProgram}), the room-temperature wiring of
|
|
6
|
-
* heating circuits
|
|
6
|
+
* heating circuits, the program-switch modes of circuits and DHW zones, and
|
|
7
|
+
* the SG Ready relays ({@link EnyoHeatpumpApplianceSgReadyRelay}).
|
|
7
8
|
*
|
|
8
9
|
* None of these shapes can be checked at the type level — a curve with one
|
|
9
10
|
* point, an unsorted curve, `'25:00'` as a switching time or a period on day
|
|
@@ -47,6 +48,8 @@ const TIME_OF_DAY = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
|
47
48
|
const LEVELS = new Set(Object.values(enyo_heatpump_appliance_js_1.EnyoHeatpumpTimeProgramLevelEnum));
|
|
48
49
|
const PROGRAM_MODES = new Set(Object.values(enyo_heatpump_appliance_js_1.EnyoHeatpumpProgramModeEnum));
|
|
49
50
|
const OVERRIDE_PROGRAM_MODES = new Set([enyo_heatpump_appliance_js_1.EnyoHeatpumpProgramModeEnum.Comfort, enyo_heatpump_appliance_js_1.EnyoHeatpumpProgramModeEnum.Eco]);
|
|
51
|
+
const SG_READY_FUNCTIONS = new Set(Object.values(enyo_heatpump_appliance_js_1.EnyoHeatpumpSgReadyFunctionEnum));
|
|
52
|
+
const SG_READY_STATES = new Set(Object.values(enyo_heatpump_appliance_js_1.EnyoHeatpumpSgReadyStateEnum));
|
|
50
53
|
/**
|
|
51
54
|
* Validates the program-switch fields of one heating circuit or DHW zone
|
|
52
55
|
* (`supportedProgramModes`, `programModeTemperaturesC`, `activeProgramMode`)
|
|
@@ -121,6 +124,79 @@ function checkHeatingCurveOffset(circuit, prefix, errors) {
|
|
|
121
124
|
}
|
|
122
125
|
});
|
|
123
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Validates the SG Ready relays of a heatpump and appends the problems to
|
|
129
|
+
* `errors` / `warnings`.
|
|
130
|
+
*
|
|
131
|
+
* Errors: duplicate relay `index`, an unknown `function`, an unknown or empty
|
|
132
|
+
* `supportedStates`, an unknown `activeState` or one outside
|
|
133
|
+
* `supportedStates`.
|
|
134
|
+
*
|
|
135
|
+
* Warnings: a `zoneIndex` naming no declared heating circuit / DHW zone, two
|
|
136
|
+
* relays with the same function on the same zone, `supportedStates` without
|
|
137
|
+
* `Normal` or with duplicates, a `dhw-heating-rod` relay while the
|
|
138
|
+
* installation does not list a heating rod in `additionalDevices`.
|
|
139
|
+
*
|
|
140
|
+
* @param relays - The declared SG Ready relays.
|
|
141
|
+
* @param metadata - The heatpump metadata, for zones and additional devices.
|
|
142
|
+
* @param errors - Receives blocking problems.
|
|
143
|
+
* @param warnings - Receives advisory problems.
|
|
144
|
+
*/
|
|
145
|
+
function checkSgReadyRelays(relays, metadata, errors, warnings) {
|
|
146
|
+
const indexes = relays.map(relay => relay.index);
|
|
147
|
+
const duplicates = indexes.filter((index, i) => indexes.indexOf(index) !== i);
|
|
148
|
+
if (duplicates.length > 0) {
|
|
149
|
+
errors.push(`sgReadyRelays contains duplicate index ${[...new Set(duplicates)].join(', ')}`);
|
|
150
|
+
}
|
|
151
|
+
const targets = new Set();
|
|
152
|
+
for (const relay of relays) {
|
|
153
|
+
const prefix = `sgReadyRelays[index=${relay.index}]`;
|
|
154
|
+
if (!SG_READY_FUNCTIONS.has(relay.function)) {
|
|
155
|
+
errors.push(`${prefix}: function "${relay.function}" is not a valid value`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const isHeating = relay.function === enyo_heatpump_appliance_js_1.EnyoHeatpumpSgReadyFunctionEnum.Heating;
|
|
159
|
+
const zones = isHeating ? metadata.heatingCircuits : metadata.domesticHotWater;
|
|
160
|
+
const zoneField = isHeating ? 'heatingCircuits' : 'domesticHotWater';
|
|
161
|
+
const zoneIndex = relay.zoneIndex ?? zones?.[0]?.index ?? 0;
|
|
162
|
+
if (relay.zoneIndex !== undefined && zones?.length && !zones.some(zone => zone.index === relay.zoneIndex)) {
|
|
163
|
+
warnings.push(`${prefix}: zoneIndex ${relay.zoneIndex} is not an index of ${zoneField}`);
|
|
164
|
+
}
|
|
165
|
+
const target = `${relay.function}@${zoneIndex}`;
|
|
166
|
+
if (targets.has(target)) {
|
|
167
|
+
warnings.push(`${prefix}: another relay already has function ${relay.function} on zone ${zoneIndex}`);
|
|
168
|
+
}
|
|
169
|
+
targets.add(target);
|
|
170
|
+
if (relay.function === enyo_heatpump_appliance_js_1.EnyoHeatpumpSgReadyFunctionEnum.DhwHeatingRod
|
|
171
|
+
&& !(metadata.additionalDevices ?? []).includes(enyo_heatpump_appliance_js_1.EnyoHeatpumpApplianceAdditionalDeviceEnum.HeatingRod)) {
|
|
172
|
+
warnings.push(`${prefix}: function dhw-heating-rod is declared but additionalDevices does not list a HeatingRod`);
|
|
173
|
+
}
|
|
174
|
+
const supported = relay.supportedStates;
|
|
175
|
+
if (supported !== undefined) {
|
|
176
|
+
if (supported.length === 0) {
|
|
177
|
+
errors.push(`${prefix}: supportedStates must not be empty — omit it when all four states are wired`);
|
|
178
|
+
}
|
|
179
|
+
for (const state of supported) {
|
|
180
|
+
if (!SG_READY_STATES.has(state))
|
|
181
|
+
errors.push(`${prefix}: supportedStates contains unknown state "${state}"`);
|
|
182
|
+
}
|
|
183
|
+
if (new Set(supported).size !== supported.length) {
|
|
184
|
+
warnings.push(`${prefix}: supportedStates contains duplicate states`);
|
|
185
|
+
}
|
|
186
|
+
if (supported.length > 0 && !supported.includes(enyo_heatpump_appliance_js_1.EnyoHeatpumpSgReadyStateEnum.Normal)) {
|
|
187
|
+
warnings.push(`${prefix}: supportedStates does not include normal — the relay could never release the heatpump`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (relay.activeState !== undefined) {
|
|
191
|
+
if (!SG_READY_STATES.has(relay.activeState)) {
|
|
192
|
+
errors.push(`${prefix}: activeState "${relay.activeState}" is not a valid value`);
|
|
193
|
+
}
|
|
194
|
+
else if (supported !== undefined && !supported.includes(relay.activeState)) {
|
|
195
|
+
errors.push(`${prefix}: activeState ${relay.activeState} is not one of supportedStates`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
124
200
|
/**
|
|
125
201
|
* Parses an `HH:mm` time of day into minutes since midnight.
|
|
126
202
|
*
|
|
@@ -374,6 +450,7 @@ function assertValidHeatpumpTimeProgram(program) {
|
|
|
374
450
|
* - Heating curve offset: errors from {@link checkHeatingCurveOffset}.
|
|
375
451
|
* Warning: `HeatingCurveOffset` is declared but no circuit has a
|
|
376
452
|
* `heatingCurveOffsetRangeK` — no offset other than `0` could be accepted.
|
|
453
|
+
* - SG Ready relays: errors and warnings from {@link checkSgReadyRelays}.
|
|
377
454
|
*
|
|
378
455
|
* @param metadata - The heatpump metadata to validate.
|
|
379
456
|
* @returns The validation result.
|
|
@@ -476,5 +553,6 @@ function validateHeatpumpAdvancedMetadata(metadata) {
|
|
|
476
553
|
merge(`domesticHotWater[index=${zone.index}].timeProgram`, validateHeatpumpTimeProgram(zone.timeProgram));
|
|
477
554
|
}
|
|
478
555
|
}
|
|
556
|
+
checkSgReadyRelays(metadata.sgReadyRelays ?? [], metadata, errors, warnings);
|
|
479
557
|
return { ok: errors.length === 0, errors, warnings };
|
|
480
558
|
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Client-side validation for the advanced heatpump metadata an integration
|
|
3
3
|
* publishes: heating curves ({@link EnyoHeatpumpApplianceHeatingCurve}), time
|
|
4
4
|
* programs ({@link EnyoHeatpumpTimeProgram}), the room-temperature wiring of
|
|
5
|
-
* heating circuits
|
|
5
|
+
* heating circuits, the program-switch modes of circuits and DHW zones, and
|
|
6
|
+
* the SG Ready relays ({@link EnyoHeatpumpApplianceSgReadyRelay}).
|
|
6
7
|
*
|
|
7
8
|
* None of these shapes can be checked at the type level — a curve with one
|
|
8
9
|
* point, an unsorted curve, `'25:00'` as a switching time or a period on day
|
|
@@ -116,8 +117,9 @@ export declare function assertValidHeatpumpTimeProgram(program: EnyoHeatpumpTime
|
|
|
116
117
|
* - Heating curve offset: errors from {@link checkHeatingCurveOffset}.
|
|
117
118
|
* Warning: `HeatingCurveOffset` is declared but no circuit has a
|
|
118
119
|
* `heatingCurveOffsetRangeK` — no offset other than `0` could be accepted.
|
|
120
|
+
* - SG Ready relays: errors and warnings from {@link checkSgReadyRelays}.
|
|
119
121
|
*
|
|
120
122
|
* @param metadata - The heatpump metadata to validate.
|
|
121
123
|
* @returns The validation result.
|
|
122
124
|
*/
|
|
123
|
-
export declare function validateHeatpumpAdvancedMetadata(metadata: Pick<EnyoHeatpumpApplianceMetadata, 'availableFeatures' | 'heatingCircuits' | 'domesticHotWater' | 'building'>): HeatpumpMetadataValidationResult;
|
|
125
|
+
export declare function validateHeatpumpAdvancedMetadata(metadata: Pick<EnyoHeatpumpApplianceMetadata, 'availableFeatures' | 'heatingCircuits' | 'domesticHotWater' | 'building'> & Partial<Pick<EnyoHeatpumpApplianceMetadata, 'sgReadyRelays' | 'additionalDevices'>>): HeatpumpMetadataValidationResult;
|
|
@@ -20,6 +20,9 @@ exports.isSubscriptionActive = isSubscriptionActive;
|
|
|
20
20
|
exports.isPlanActive = isPlanActive;
|
|
21
21
|
exports.isSameSubscriptionUnit = isSameSubscriptionUnit;
|
|
22
22
|
exports.isUnitActive = isUnitActive;
|
|
23
|
+
exports.defineSubscriptionFeature = defineSubscriptionFeature;
|
|
24
|
+
exports.defineSubscriptionOfferScreen = defineSubscriptionOfferScreen;
|
|
25
|
+
exports.getIncludedFeatureKey = getIncludedFeatureKey;
|
|
23
26
|
const enyo_subscription_js_1 = require("../../types/enyo-subscription.cjs");
|
|
24
27
|
/**
|
|
25
28
|
* Subscription statuses in which the customer may use what a plan unlocks.
|
|
@@ -122,3 +125,48 @@ function isUnitActive(subscriptions, planKey, unit) {
|
|
|
122
125
|
&& isSubscriptionActive(subscription)
|
|
123
126
|
&& (subscription.units ?? []).some(paid => isSameSubscriptionUnit(paid, unit)));
|
|
124
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Identity helper that type-checks a feature of the package's feature catalog
|
|
130
|
+
* ({@link EnergyAppPackageDefinition.subscriptionFeatures}) where it is
|
|
131
|
+
* written.
|
|
132
|
+
*
|
|
133
|
+
* @param feature - The feature declaration.
|
|
134
|
+
* @returns The same feature, unchanged.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* defineSubscriptionFeature({
|
|
139
|
+
* featureKey: 'price-optimisation',
|
|
140
|
+
* icon: EnyoSubscriptionOfferIconEnum.PiggyBank,
|
|
141
|
+
* storeEntry: [
|
|
142
|
+
* {language: 'en', title: 'Dynamic price optimisation'},
|
|
143
|
+
* {language: 'de', title: 'Optimierung nach dynamischen Preisen'}
|
|
144
|
+
* ]
|
|
145
|
+
* })
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
function defineSubscriptionFeature(feature) {
|
|
149
|
+
return feature;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Identity helper that type-checks the package's offer screen
|
|
153
|
+
* ({@link EnergyAppPackageDefinition.subscriptionOfferScreen}) where it is
|
|
154
|
+
* written.
|
|
155
|
+
*
|
|
156
|
+
* @param screen - The offer screen declaration.
|
|
157
|
+
* @returns The same offer screen, unchanged.
|
|
158
|
+
*/
|
|
159
|
+
function defineSubscriptionOfferScreen(screen) {
|
|
160
|
+
return screen;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Returns the feature key of an included feature, whether it was declared as
|
|
164
|
+
* a plain key or with a value.
|
|
165
|
+
*
|
|
166
|
+
* @param feature - An entry of {@link EnergyAppPackageSubscriptionPlan.includedFeatures}
|
|
167
|
+
* or {@link EnyoSubscriptionFreeTier.includedFeatures}.
|
|
168
|
+
* @returns Its {@link EnyoSubscriptionFeature.featureKey}.
|
|
169
|
+
*/
|
|
170
|
+
function getIncludedFeatureKey(feature) {
|
|
171
|
+
return typeof feature === 'string' ? feature : feature.featureKey;
|
|
172
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { EnergyAppPackageSubscriptionPlan } from '../../energy-app-package-definition.cjs';
|
|
16
16
|
import { EnyoSubscription, EnyoSubscriptionStatus, EnyoSubscriptionUnit } from '../../types/enyo-subscription.cjs';
|
|
17
|
+
import type { EnyoSubscriptionFeature, EnyoSubscriptionIncludedFeature, EnyoSubscriptionOfferScreen } from '../../types/enyo-subscription-offer.cjs';
|
|
17
18
|
/**
|
|
18
19
|
* Subscription statuses in which the customer may use what a plan unlocks.
|
|
19
20
|
*
|
|
@@ -98,3 +99,42 @@ export declare function isSameSubscriptionUnit(a: Pick<EnyoSubscriptionUnit, 'ty
|
|
|
98
99
|
* ```
|
|
99
100
|
*/
|
|
100
101
|
export declare function isUnitActive(subscriptions: EnyoSubscription[], planKey: string, unit: Pick<EnyoSubscriptionUnit, 'type' | 'referenceId'>): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Identity helper that type-checks a feature of the package's feature catalog
|
|
104
|
+
* ({@link EnergyAppPackageDefinition.subscriptionFeatures}) where it is
|
|
105
|
+
* written.
|
|
106
|
+
*
|
|
107
|
+
* @param feature - The feature declaration.
|
|
108
|
+
* @returns The same feature, unchanged.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* defineSubscriptionFeature({
|
|
113
|
+
* featureKey: 'price-optimisation',
|
|
114
|
+
* icon: EnyoSubscriptionOfferIconEnum.PiggyBank,
|
|
115
|
+
* storeEntry: [
|
|
116
|
+
* {language: 'en', title: 'Dynamic price optimisation'},
|
|
117
|
+
* {language: 'de', title: 'Optimierung nach dynamischen Preisen'}
|
|
118
|
+
* ]
|
|
119
|
+
* })
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare function defineSubscriptionFeature(feature: EnyoSubscriptionFeature): EnyoSubscriptionFeature;
|
|
123
|
+
/**
|
|
124
|
+
* Identity helper that type-checks the package's offer screen
|
|
125
|
+
* ({@link EnergyAppPackageDefinition.subscriptionOfferScreen}) where it is
|
|
126
|
+
* written.
|
|
127
|
+
*
|
|
128
|
+
* @param screen - The offer screen declaration.
|
|
129
|
+
* @returns The same offer screen, unchanged.
|
|
130
|
+
*/
|
|
131
|
+
export declare function defineSubscriptionOfferScreen(screen: EnyoSubscriptionOfferScreen): EnyoSubscriptionOfferScreen;
|
|
132
|
+
/**
|
|
133
|
+
* Returns the feature key of an included feature, whether it was declared as
|
|
134
|
+
* a plain key or with a value.
|
|
135
|
+
*
|
|
136
|
+
* @param feature - An entry of {@link EnergyAppPackageSubscriptionPlan.includedFeatures}
|
|
137
|
+
* or {@link EnyoSubscriptionFreeTier.includedFeatures}.
|
|
138
|
+
* @returns Its {@link EnyoSubscriptionFeature.featureKey}.
|
|
139
|
+
*/
|
|
140
|
+
export declare function getIncludedFeatureKey(feature: EnyoSubscriptionIncludedFeature): string;
|