@enyo-energy/energy-app-sdk 1.12.0 → 1.14.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 +134 -5
- package/dist/cjs/implementations/automation/automation-validators.cjs +143 -10
- package/dist/cjs/implementations/automation/automation-validators.d.cts +15 -3
- package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.cjs +95 -48
- package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.d.cts +94 -41
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-provider-validators.cjs +12 -50
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-provider-validators.d.cts +10 -7
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.cjs +188 -41
- package/dist/cjs/index.cjs +3 -0
- package/dist/cjs/index.d.cts +3 -0
- package/dist/cjs/packages/energy-app-automation.d.cts +44 -0
- package/dist/cjs/packages/energy-app-onboarding-v2.d.cts +48 -7
- package/dist/cjs/types/enyo-authentication.cjs +27 -1
- package/dist/cjs/types/enyo-authentication.d.cts +86 -7
- package/dist/cjs/types/enyo-automation.cjs +40 -0
- package/dist/cjs/types/enyo-automation.d.cts +252 -7
- package/dist/cjs/types/enyo-data-bus-value.cjs +32 -3
- package/dist/cjs/types/enyo-data-bus-value.d.cts +236 -4
- package/dist/cjs/types/enyo-eebus.cjs +47 -1
- package/dist/cjs/types/enyo-eebus.d.cts +57 -0
- package/dist/cjs/types/enyo-energy-manager.cjs +73 -0
- package/dist/cjs/types/enyo-energy-manager.d.cts +70 -1
- package/dist/cjs/types/enyo-heatpump-appliance.cjs +35 -1
- package/dist/cjs/types/enyo-heatpump-appliance.d.cts +147 -0
- package/dist/cjs/types/enyo-onboarding-v2-device-select.cjs +2 -2
- package/dist/cjs/types/enyo-onboarding-v2-device-select.d.cts +31 -6
- package/dist/cjs/types/enyo-onboarding-v2-eebus-device-select.cjs +29 -0
- package/dist/cjs/types/enyo-onboarding-v2-eebus-device-select.d.cts +174 -0
- package/dist/cjs/types/enyo-onboarding-v2.cjs +36 -64
- package/dist/cjs/types/enyo-onboarding-v2.d.cts +297 -94
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/implementations/automation/automation-validators.d.ts +15 -3
- package/dist/implementations/automation/automation-validators.js +142 -9
- package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.d.ts +94 -41
- package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.js +95 -48
- package/dist/implementations/onboarding-v2/onboarding-v2-provider-validators.d.ts +10 -7
- package/dist/implementations/onboarding-v2/onboarding-v2-provider-validators.js +12 -50
- package/dist/implementations/onboarding-v2/onboarding-v2-validators.js +188 -41
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/packages/energy-app-automation.d.ts +44 -0
- package/dist/packages/energy-app-onboarding-v2.d.ts +48 -7
- package/dist/types/enyo-authentication.d.ts +86 -7
- package/dist/types/enyo-authentication.js +26 -0
- package/dist/types/enyo-automation.d.ts +252 -7
- package/dist/types/enyo-automation.js +40 -0
- package/dist/types/enyo-data-bus-value.d.ts +236 -4
- package/dist/types/enyo-data-bus-value.js +32 -3
- package/dist/types/enyo-eebus.d.ts +57 -0
- package/dist/types/enyo-eebus.js +46 -0
- package/dist/types/enyo-energy-manager.d.ts +70 -1
- package/dist/types/enyo-energy-manager.js +73 -0
- package/dist/types/enyo-heatpump-appliance.d.ts +147 -0
- package/dist/types/enyo-heatpump-appliance.js +34 -0
- package/dist/types/enyo-onboarding-v2-device-select.d.ts +31 -6
- package/dist/types/enyo-onboarding-v2-device-select.js +2 -2
- package/dist/types/enyo-onboarding-v2-eebus-device-select.d.ts +174 -0
- package/dist/types/enyo-onboarding-v2-eebus-device-select.js +28 -0
- package/dist/types/enyo-onboarding-v2.d.ts +297 -94
- package/dist/types/enyo-onboarding-v2.js +36 -64
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,6 +85,7 @@ The official TypeScript SDK for building Energy Apps on the enyo platform. Creat
|
|
|
85
85
|
- [Device Integration](#device-integration)
|
|
86
86
|
- [Data Bus Messaging](#data-bus-messaging)
|
|
87
87
|
- [Announcing Appliance Flexibility](#announcing-appliance-flexibility)
|
|
88
|
+
- [Explaining Why a Command Was Issued](#explaining-why-a-command-was-issued)
|
|
88
89
|
- [Settings Management](#settings-management)
|
|
89
90
|
- [Troubleshooting](#troubleshooting)
|
|
90
91
|
- [External Libraries](#external-libraries)
|
|
@@ -1680,9 +1681,27 @@ await energyApp.useOnboardingV2().registerAdditionalSetupHandler(async (request)
|
|
|
1680
1681
|
|
|
1681
1682
|
Every such block must declare a `failed` outcome — it absorbs a rejection, a timeout, a missing handler, and an outcome value matching no branch (guide and handler are linked only by strings). Secrets are never logged, never prefilled, and not kept in run state. See [ONBOARDING.md](./ONBOARDING.md#app-defined-setups-additional-setup).
|
|
1682
1683
|
|
|
1684
|
+
Guides pick the device on a screen of their own. `device-select` lists the network devices the run found; `eebus-device-select` lists the discovered EEBUS peers, filtered by the SHIP device type they announce (`EnyoEebusDeviceTypeEnum`). Both carry their own `headline`/`description`, and both **skip the screen entirely** when exactly one candidate matches the filter — the handler still runs and the run is still bound, with `autoSelected: true` on the request. Register one handler per kind; a network device and an EEBUS peer are different things and neither handler is a fallback for the other:
|
|
1685
|
+
|
|
1686
|
+
```typescript
|
|
1687
|
+
const onboarding = energyApp.useOnboardingV2();
|
|
1688
|
+
|
|
1689
|
+
await onboarding.registerDeviceSelectHandler(async (request) => ({
|
|
1690
|
+
requestId: request.requestId,
|
|
1691
|
+
applianceIds: await adoptDevices(request.devices, request.applianceId),
|
|
1692
|
+
}));
|
|
1693
|
+
|
|
1694
|
+
await onboarding.registerEebusDeviceSelectHandler(async (request) => ({
|
|
1695
|
+
requestId: request.requestId, // request.peer.ski identifies the paired device
|
|
1696
|
+
applianceIds: await adoptEebusPeer(request.peer, request.applianceId),
|
|
1697
|
+
}));
|
|
1698
|
+
```
|
|
1699
|
+
|
|
1700
|
+
On an `offline-reconnect` run the request carries the appliance that went offline — re-point it and answer with the same `applianceId` rather than creating a second one. See [ONBOARDING.md](./ONBOARDING.md#picking-the-device-device-select).
|
|
1701
|
+
|
|
1683
1702
|
Your answer wins over the host's own resolution, so a wrong value is worse than none — the installer pastes it into a charger and it fails minutes later as an `ocpp-connect` timeout. `validateOnboardingV2DynamicResult()` catches the copy-target mistakes (whitespace, a non-absolute URL, a plaintext scheme).
|
|
1684
1703
|
|
|
1685
|
-
Each guide
|
|
1704
|
+
Each guide declares the `startVariant` it applies to (`device-not-found`, `device-found-config`, `manual-setup`, `maintenance`, `offline-reconnect`). It must **not** declare `vendorId` or `modelIds`: enyo attaches those bindings when the guide is registered, an app-supplied value is overwritten, and `validateOnboardingGuideV2()` warns about it. See [ONBOARDING.md](./ONBOARDING.md#serving-guides-the-host-pulls-the-app-never-publishes) for the full v2 model.
|
|
1686
1705
|
|
|
1687
1706
|
Not permission-gated.
|
|
1688
1707
|
|
|
@@ -2943,17 +2962,21 @@ An `EnyoAutomation` is `{ id, name, enabled, trigger, actions[] }`. The pieces:
|
|
|
2943
2962
|
|
|
2944
2963
|
| Concept | Type | Values / fields |
|
|
2945
2964
|
| --- | --- | --- |
|
|
2946
|
-
| **Trigger** | `EnyoAutomationTriggerTypeEnum` | `
|
|
2947
|
-
| **
|
|
2965
|
+
| **Trigger — PV surplus above** | `EnyoAutomationTriggerTypeEnum.PvSurplusThreshold` | `{ thresholdW }` (activate above, deactivate below) |
|
|
2966
|
+
| **Trigger — PV surplus below** | `EnyoAutomationTriggerTypeEnum.PvSurplusBelowThreshold` | `{ thresholdW }` — the inverse: active while surplus stays below, deactivates (turns the target **off**) as soon as `surplusW >= thresholdW` |
|
|
2967
|
+
| **Trigger — below price limit** | `EnyoAutomationTriggerTypeEnum.BelowPriceLimit` | `{ limitPerKwh, currency? }` — active while the current **full gross** price per kWh (incl. taxes, grid fees and all other components) is strictly below the limit; negative limits are allowed |
|
|
2968
|
+
| **Trigger — cheapest share of day** | `EnyoAutomationTriggerTypeEnum.CheapestShareOfDay` | `{ sharePercent }` — active during the cheapest `sharePercent` % of the day's price intervals (e.g. `25` for the cheapest 25 %); integer `1…100` |
|
|
2969
|
+
| **Trigger — schedule ("Zeitplan")** | `EnyoAutomationTriggerTypeEnum.Schedule` | `{ windows[], timezone? }` — each window is `{ startTimeOfDay, endTimeOfDay, daysOfWeek? }` in local `HH:mm` (`0` = Sunday … `6` = Saturday, omit for every day); windows may wrap past midnight and may overlap — the trigger is active while **any** window covers the current time |
|
|
2970
|
+
| **Action — smart plug** | `EnyoAutomationActionTypeEnum.SmartPlugSwitch` | `{ applianceId, minDurationMinutes }` — `minDurationMinutes` is `1…360`: whole minutes up to `5` (so a **1-minute** runtime is supported), multiples of `5` above that |
|
|
2948
2971
|
| **Action — MQTT** | `EnyoAutomationActionTypeEnum.Mqtt` | `{ topic, payloadTemplate, updateChargingPvSurplus, publishOptions? }` |
|
|
2949
2972
|
| **Scheduling** | `EnyoAutomationSchedulingModeEnum` | `Mandatory` (run exactly while active) or `Flexible` (Energy Manager may choose whether/when within the active window) |
|
|
2950
2973
|
| **Target kind** | `EnyoAutomationTargetKindEnum` | `Load` (consumes power — counts in the energy balance) or `Signal` (control signal only) |
|
|
2951
2974
|
|
|
2952
|
-
The **MQTT** `payloadTemplate` is JSON that may embed the placeholders in `EnyoAutomationMqttPlaceholderEnum` — `{{state}}` (`on`/`off`), `{{surplusW}}`, `{{timestampIso}}`, `{{automationId}}` — which the platform substitutes before publishing.
|
|
2975
|
+
The **MQTT** `payloadTemplate` is JSON that may embed the placeholders in `EnyoAutomationMqttPlaceholderEnum` — `{{state}}` (`on`/`off`), `{{surplusW}}`, `{{pricePerKwh}}`, `{{timestampIso}}`, `{{automationId}}` — which the platform substitutes before publishing.
|
|
2953
2976
|
|
|
2954
2977
|
Two things travel over the **data bus** vs. the **API**:
|
|
2955
2978
|
|
|
2956
|
-
- **Trigger state** is the data-bus message `AutomationTriggerV1` (`EnyoDataBusAutomationTriggerV1`): `{ automationId, data: { active, trigger } }`, where `trigger` is the per-type `EnyoAutomationTriggerData` (
|
|
2979
|
+
- **Trigger state** is the data-bus message `AutomationTriggerV1` (`EnyoDataBusAutomationTriggerV1`): `{ automationId, data: { active, trigger } }`, where `trigger` is the per-type `EnyoAutomationTriggerData` (PV surplus: `{ triggerType, surplusW, thresholdW }`; below-price-limit: `{ triggerType, pricePerKwh, limitPerKwh, currency }`; cheapest-share-of-day: `{ triggerType, pricePerKwh, sharePercent, thresholdPricePerKwh, currency }`; schedule: `{ triggerType, windowIndex?, windowStartIso?, windowEndIso? }`).
|
|
2957
2980
|
- The **forecast** is a method — `publishAutomationForecast()` — not a data-bus message.
|
|
2958
2981
|
|
|
2959
2982
|
### ⚡ Guide: Energy Manager apps
|
|
@@ -3603,6 +3626,112 @@ Notes:
|
|
|
3603
3626
|
grant can be read against the announcement it answers. It is advisory — the
|
|
3604
3627
|
command's `powerW` remains the only limit.
|
|
3605
3628
|
|
|
3629
|
+
#### Explaining Why a Command Was Issued
|
|
3630
|
+
|
|
3631
|
+
Every data bus command can carry an `EnyoDataBusCommandReason`. Its `type`
|
|
3632
|
+
(`EnyoDataBusCommandReasonTypeEnum`) and `category` are **closed, coarse
|
|
3633
|
+
vocabularies** — they exist for filtering, iconography and analytics, not for
|
|
3634
|
+
narrating a decision. There is no "thermal cost" or "efficiency" member, and you
|
|
3635
|
+
must not synthesize one: pick the honest type and attach the numbers.
|
|
3636
|
+
|
|
3637
|
+
The prose for the end user lives in `translation` (per language). The numbers
|
|
3638
|
+
behind that prose live in the optional `context`
|
|
3639
|
+
(`EnyoDataBusCommandReasonContext`), so a consumer can verify, re-render or audit
|
|
3640
|
+
the explanation instead of taking it on trust:
|
|
3641
|
+
|
|
3642
|
+
| Section | Answers | Key fields |
|
|
3643
|
+
| --- | --- | --- |
|
|
3644
|
+
| `proactive` | Did we act on a forecast rather than a measurement? | `boolean` |
|
|
3645
|
+
| `forecast` | What did we see coming? | `outdoorTemperatureC`, `thresholdTemperatureC`, `windowStartIso`/`windowEndIso`, `triggerTimestampIso` |
|
|
3646
|
+
| `placement` | When does the block run, and by when must it be done? | `startIso`, `endIso`, `durationMinutes`, `deadlineIso` |
|
|
3647
|
+
| `efficiency` | Why this slot and not the cheaper-looking one? | `coefficientOfPerformance`, `effectiveCostPerKwhThermal`, `compared*` |
|
|
3648
|
+
| `thermalStorage` | What ends the block? | `target`, `energyPerKelvinKwh`, `overheatKelvin`, `plannedEnergyKwh`, `absorbableEnergyKwh` |
|
|
3649
|
+
|
|
3650
|
+
Worked example — a buffer tank charged ahead of a cold front. The command is
|
|
3651
|
+
`ScheduledOptimization` (category `Schedule`), which is the honest type for a
|
|
3652
|
+
block an optimizer placed in advance:
|
|
3653
|
+
|
|
3654
|
+
```typescript
|
|
3655
|
+
import {
|
|
3656
|
+
EnyoCurrencyEnum,
|
|
3657
|
+
EnyoDataBusCommandReason,
|
|
3658
|
+
EnyoDataBusCommandReasonCategoryEnum,
|
|
3659
|
+
EnyoDataBusCommandReasonTypeEnum,
|
|
3660
|
+
EnyoFlexibilityTargetEnum,
|
|
3661
|
+
} from '@enyo-energy/energy-app-sdk';
|
|
3662
|
+
|
|
3663
|
+
const reason: EnyoDataBusCommandReason = {
|
|
3664
|
+
type: EnyoDataBusCommandReasonTypeEnum.ScheduledOptimization,
|
|
3665
|
+
category: EnyoDataBusCommandReasonCategoryEnum.Schedule,
|
|
3666
|
+
translation: [
|
|
3667
|
+
{
|
|
3668
|
+
language: 'en',
|
|
3669
|
+
value:
|
|
3670
|
+
'Banking 2 hours of cheap heat into the buffer tank before tonight\'s cold front, ' +
|
|
3671
|
+
'so the compressor can coast through the expensive hours.',
|
|
3672
|
+
},
|
|
3673
|
+
],
|
|
3674
|
+
// Per kWh of *electricity* — the raw price of the chosen slot.
|
|
3675
|
+
electricityPricePerKwh: 0.30,
|
|
3676
|
+
currency: EnyoCurrencyEnum.EUR,
|
|
3677
|
+
context: {
|
|
3678
|
+
// We watch the weather, not the tank.
|
|
3679
|
+
proactive: true,
|
|
3680
|
+
forecast: {
|
|
3681
|
+
outdoorTemperatureC: 6.5,
|
|
3682
|
+
thresholdTemperatureC: 10,
|
|
3683
|
+
windowStartIso: '2026-01-14T12:00:00Z',
|
|
3684
|
+
windowEndIso: '2026-01-15T12:00:00Z',
|
|
3685
|
+
triggerTimestampIso: '2026-01-14T21:00:00Z',
|
|
3686
|
+
},
|
|
3687
|
+
// A 120-minute block, finished before that cold front arrives.
|
|
3688
|
+
placement: {
|
|
3689
|
+
startIso: '2026-01-14T12:00:00Z',
|
|
3690
|
+
endIso: '2026-01-14T14:00:00Z',
|
|
3691
|
+
durationMinutes: 120,
|
|
3692
|
+
deadlineIso: '2026-01-14T21:00:00Z',
|
|
3693
|
+
},
|
|
3694
|
+
// Placed by price ÷ COP, not price alone: a warm midday hour at 30 ct
|
|
3695
|
+
// delivers cheaper heat than a cold night hour at 20 ct.
|
|
3696
|
+
efficiency: {
|
|
3697
|
+
coefficientOfPerformance: 4.2,
|
|
3698
|
+
effectiveCostPerKwhThermal: 0.0714,
|
|
3699
|
+
comparedElectricityPricePerKwh: 0.20,
|
|
3700
|
+
comparedCoefficientOfPerformance: 2.4,
|
|
3701
|
+
comparedEffectiveCostPerKwhThermal: 0.0833,
|
|
3702
|
+
comparedStartIso: '2026-01-14T02:00:00Z',
|
|
3703
|
+
},
|
|
3704
|
+
// And it stops once the store is physically full: the pump tells us one
|
|
3705
|
+
// Kelvin of overheat absorbs 1.2 kWh, so 5 K of headroom is 6.1 kWh.
|
|
3706
|
+
thermalStorage: {
|
|
3707
|
+
target: EnyoFlexibilityTargetEnum.BufferTank,
|
|
3708
|
+
energyPerKelvinKwh: 1.2,
|
|
3709
|
+
overheatKelvin: 5,
|
|
3710
|
+
plannedEnergyKwh: 6.0,
|
|
3711
|
+
absorbableEnergyKwh: 6.1,
|
|
3712
|
+
},
|
|
3713
|
+
},
|
|
3714
|
+
};
|
|
3715
|
+
```
|
|
3716
|
+
|
|
3717
|
+
Notes:
|
|
3718
|
+
|
|
3719
|
+
- **`context` is entirely optional and additive.** A reason that sets none of it
|
|
3720
|
+
behaves exactly as before; consumers must tolerate every section being absent.
|
|
3721
|
+
- **Costs are not all per kWh of electricity.** `electricityPricePerKwh` is per
|
|
3722
|
+
kWh drawn; `effectiveCostPerKwhThermal` is per kWh *delivered*, in the same
|
|
3723
|
+
`currency`. Mixing the two is what makes an efficiency-placed command look like
|
|
3724
|
+
a mistake.
|
|
3725
|
+
- **One rejected alternative, not a solver dump.** The `compared*` fields describe
|
|
3726
|
+
the single best slot that lost, because that is what explains the choice to a
|
|
3727
|
+
person.
|
|
3728
|
+
- **Nothing in a reason controls anything.** It explains a command; the command's
|
|
3729
|
+
own fields stay authoritative.
|
|
3730
|
+
- **`thermalStorage.target` reuses `EnyoFlexibilityTargetEnum`**, the same
|
|
3731
|
+
vocabulary as flexibility announcements and the `targets` breakdown on
|
|
3732
|
+
available-power commands — so a grant and its justification name the same
|
|
3733
|
+
physical sink without a mapping table.
|
|
3734
|
+
|
|
3606
3735
|
### Settings Management
|
|
3607
3736
|
|
|
3608
3737
|
Dynamic configuration with user interface:
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AUTOMATION_DURATION_STEP_MINUTES = exports.AUTOMATION_MAX_DURATION_MINUTES = exports.AUTOMATION_MIN_DURATION_MINUTES = exports.AutomationValidationError = void 0;
|
|
3
|
+
exports.AUTOMATION_MAX_CHEAPEST_SHARE_PERCENT = exports.AUTOMATION_MIN_CHEAPEST_SHARE_PERCENT = exports.AUTOMATION_DURATION_STEP_MINUTES = exports.AUTOMATION_MAX_DURATION_MINUTES = exports.AUTOMATION_MIN_DURATION_MINUTES = exports.AutomationValidationError = void 0;
|
|
4
4
|
exports.validateAutomation = validateAutomation;
|
|
5
5
|
exports.validateTrigger = validateTrigger;
|
|
6
6
|
exports.validateAction = validateAction;
|
|
7
7
|
exports.validateAutomationForecast = validateAutomationForecast;
|
|
8
8
|
exports.validateAutomationTriggerData = validateAutomationTriggerData;
|
|
9
9
|
const enyo_automation_js_1 = require("../../types/enyo-automation.cjs");
|
|
10
|
+
const enyo_currency_js_1 = require("../../types/enyo-currency.cjs");
|
|
10
11
|
const enyo_mqtt_js_1 = require("../../types/enyo-mqtt.cjs");
|
|
12
|
+
const price_schedule_resolver_js_1 = require("../pricing/price-schedule-resolver.cjs");
|
|
11
13
|
/**
|
|
12
14
|
* Thrown when an automation, action or automation forecast violates one of the
|
|
13
15
|
* invariants enforced by this module. The message names the offending field /
|
|
@@ -20,12 +22,24 @@ class AutomationValidationError extends Error {
|
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
exports.AutomationValidationError = AutomationValidationError;
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Minimum smart-plug on-duration, in minutes. Short runtimes below
|
|
27
|
+
* {@link AUTOMATION_DURATION_STEP_MINUTES} are allowed at a one-minute
|
|
28
|
+
* granularity, so a one-minute runtime is valid.
|
|
29
|
+
*/
|
|
30
|
+
exports.AUTOMATION_MIN_DURATION_MINUTES = 1;
|
|
25
31
|
/** Maximum smart-plug on-duration, in minutes (6 hours). */
|
|
26
32
|
exports.AUTOMATION_MAX_DURATION_MINUTES = 360;
|
|
27
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Step size the smart-plug on-duration must be a multiple of, in minutes, once
|
|
35
|
+
* it exceeds {@link AUTOMATION_DURATION_STEP_MINUTES}. Durations at or below
|
|
36
|
+
* this value are free to use any whole minute (1, 2, 3, 4, 5).
|
|
37
|
+
*/
|
|
28
38
|
exports.AUTOMATION_DURATION_STEP_MINUTES = 5;
|
|
39
|
+
/** Minimum share of the day, in percent, a cheapest-share trigger may select. */
|
|
40
|
+
exports.AUTOMATION_MIN_CHEAPEST_SHARE_PERCENT = 1;
|
|
41
|
+
/** Maximum share of the day, in percent, a cheapest-share trigger may select. */
|
|
42
|
+
exports.AUTOMATION_MAX_CHEAPEST_SHARE_PERCENT = 100;
|
|
29
43
|
/** Duration in seconds of each {@link EnyoForecastResolution} value. */
|
|
30
44
|
const FORECAST_RESOLUTION_SECONDS = {
|
|
31
45
|
'10s': 10,
|
|
@@ -87,6 +101,31 @@ function validateTrigger(trigger) {
|
|
|
87
101
|
throw new AutomationValidationError('PvSurplusThreshold trigger.thresholdW must be a finite number >= 0.');
|
|
88
102
|
}
|
|
89
103
|
break;
|
|
104
|
+
case enyo_automation_js_1.EnyoAutomationTriggerTypeEnum.PvSurplusBelowThreshold:
|
|
105
|
+
if (typeof trigger.thresholdW !== 'number' || !Number.isFinite(trigger.thresholdW) || trigger.thresholdW < 0) {
|
|
106
|
+
throw new AutomationValidationError('PvSurplusBelowThreshold trigger.thresholdW must be a finite number >= 0.');
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case enyo_automation_js_1.EnyoAutomationTriggerTypeEnum.BelowPriceLimit:
|
|
110
|
+
// Negative limits are valid: dynamic prices can turn negative.
|
|
111
|
+
if (typeof trigger.limitPerKwh !== 'number' || !Number.isFinite(trigger.limitPerKwh)) {
|
|
112
|
+
throw new AutomationValidationError('BelowPriceLimit trigger.limitPerKwh must be a finite number.');
|
|
113
|
+
}
|
|
114
|
+
if (trigger.currency !== undefined) {
|
|
115
|
+
requireEnumMember(trigger.currency, enyo_currency_js_1.EnyoCurrencyEnum, 'BelowPriceLimit trigger.currency');
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
case enyo_automation_js_1.EnyoAutomationTriggerTypeEnum.CheapestShareOfDay:
|
|
119
|
+
if (typeof trigger.sharePercent !== 'number' ||
|
|
120
|
+
!Number.isInteger(trigger.sharePercent) ||
|
|
121
|
+
trigger.sharePercent < exports.AUTOMATION_MIN_CHEAPEST_SHARE_PERCENT ||
|
|
122
|
+
trigger.sharePercent > exports.AUTOMATION_MAX_CHEAPEST_SHARE_PERCENT) {
|
|
123
|
+
throw new AutomationValidationError(`CheapestShareOfDay trigger.sharePercent must be an integer between ${exports.AUTOMATION_MIN_CHEAPEST_SHARE_PERCENT} and ${exports.AUTOMATION_MAX_CHEAPEST_SHARE_PERCENT}.`);
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
case enyo_automation_js_1.EnyoAutomationTriggerTypeEnum.Schedule:
|
|
127
|
+
validateScheduleTrigger(trigger);
|
|
128
|
+
break;
|
|
90
129
|
default:
|
|
91
130
|
throw new AutomationValidationError(`Automation.trigger.type is invalid: ${trigger.type}.`);
|
|
92
131
|
}
|
|
@@ -173,13 +212,90 @@ function validateAutomationTriggerData(trigger) {
|
|
|
173
212
|
}
|
|
174
213
|
switch (trigger.triggerType) {
|
|
175
214
|
case enyo_automation_js_1.EnyoAutomationTriggerTypeEnum.PvSurplusThreshold:
|
|
215
|
+
case enyo_automation_js_1.EnyoAutomationTriggerTypeEnum.PvSurplusBelowThreshold:
|
|
176
216
|
requireFiniteNonNegative(trigger.surplusW, 'AutomationTriggerData.surplusW');
|
|
177
217
|
requireFiniteNonNegative(trigger.thresholdW, 'AutomationTriggerData.thresholdW');
|
|
178
218
|
break;
|
|
219
|
+
case enyo_automation_js_1.EnyoAutomationTriggerTypeEnum.BelowPriceLimit:
|
|
220
|
+
requireFinite(trigger.pricePerKwh, 'AutomationTriggerData.pricePerKwh');
|
|
221
|
+
requireFinite(trigger.limitPerKwh, 'AutomationTriggerData.limitPerKwh');
|
|
222
|
+
requireEnumMember(trigger.currency, enyo_currency_js_1.EnyoCurrencyEnum, 'AutomationTriggerData.currency');
|
|
223
|
+
break;
|
|
224
|
+
case enyo_automation_js_1.EnyoAutomationTriggerTypeEnum.CheapestShareOfDay:
|
|
225
|
+
requireFinite(trigger.pricePerKwh, 'AutomationTriggerData.pricePerKwh');
|
|
226
|
+
requireFinite(trigger.thresholdPricePerKwh, 'AutomationTriggerData.thresholdPricePerKwh');
|
|
227
|
+
if (typeof trigger.sharePercent !== 'number' ||
|
|
228
|
+
!Number.isInteger(trigger.sharePercent) ||
|
|
229
|
+
trigger.sharePercent < exports.AUTOMATION_MIN_CHEAPEST_SHARE_PERCENT ||
|
|
230
|
+
trigger.sharePercent > exports.AUTOMATION_MAX_CHEAPEST_SHARE_PERCENT) {
|
|
231
|
+
throw new AutomationValidationError(`AutomationTriggerData.sharePercent must be an integer between ${exports.AUTOMATION_MIN_CHEAPEST_SHARE_PERCENT} and ${exports.AUTOMATION_MAX_CHEAPEST_SHARE_PERCENT}.`);
|
|
232
|
+
}
|
|
233
|
+
requireEnumMember(trigger.currency, enyo_currency_js_1.EnyoCurrencyEnum, 'AutomationTriggerData.currency');
|
|
234
|
+
break;
|
|
235
|
+
case enyo_automation_js_1.EnyoAutomationTriggerTypeEnum.Schedule:
|
|
236
|
+
if (trigger.windowIndex !== undefined && (!Number.isInteger(trigger.windowIndex) || trigger.windowIndex < 0)) {
|
|
237
|
+
throw new AutomationValidationError('AutomationTriggerData.windowIndex must be an integer >= 0.');
|
|
238
|
+
}
|
|
239
|
+
requireOptionalIsoTimestamp(trigger.windowStartIso, 'AutomationTriggerData.windowStartIso');
|
|
240
|
+
requireOptionalIsoTimestamp(trigger.windowEndIso, 'AutomationTriggerData.windowEndIso');
|
|
241
|
+
break;
|
|
179
242
|
default:
|
|
180
243
|
throw new AutomationValidationError(`AutomationTriggerData.triggerType is invalid: ${trigger.triggerType}.`);
|
|
181
244
|
}
|
|
182
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Validates the windows of a {@link EnyoAutomationTriggerTypeEnum.Schedule}
|
|
248
|
+
* ("Zeitplan") trigger: at least one window, well-formed `HH:mm` times that do
|
|
249
|
+
* not start and end at the same minute, weekday numbers in range, and a valid
|
|
250
|
+
* IANA timezone when given. Windows are a union and may overlap, so no overlap
|
|
251
|
+
* check is performed.
|
|
252
|
+
*/
|
|
253
|
+
function validateScheduleTrigger(trigger) {
|
|
254
|
+
if (!Array.isArray(trigger.windows) || trigger.windows.length === 0) {
|
|
255
|
+
throw new AutomationValidationError('Schedule trigger.windows must contain at least one window.');
|
|
256
|
+
}
|
|
257
|
+
trigger.windows.forEach((window, index) => {
|
|
258
|
+
const label = `Schedule trigger.windows[${index}]`;
|
|
259
|
+
if (!window || typeof window !== 'object') {
|
|
260
|
+
throw new AutomationValidationError(`${label} must be an object.`);
|
|
261
|
+
}
|
|
262
|
+
const start = (0, price_schedule_resolver_js_1.parseTimeOfDay)(window.startTimeOfDay);
|
|
263
|
+
if (start === null) {
|
|
264
|
+
throw new AutomationValidationError(`${label}.startTimeOfDay must be a 24-hour 'HH:mm' time, got '${window.startTimeOfDay}'.`);
|
|
265
|
+
}
|
|
266
|
+
const end = (0, price_schedule_resolver_js_1.parseTimeOfDay)(window.endTimeOfDay);
|
|
267
|
+
if (end === null) {
|
|
268
|
+
throw new AutomationValidationError(`${label}.endTimeOfDay must be a 24-hour 'HH:mm' time, got '${window.endTimeOfDay}'.`);
|
|
269
|
+
}
|
|
270
|
+
if (start === end) {
|
|
271
|
+
throw new AutomationValidationError(`${label} must not start and end at the same time of day.`);
|
|
272
|
+
}
|
|
273
|
+
if (window.daysOfWeek !== undefined) {
|
|
274
|
+
if (!Array.isArray(window.daysOfWeek) || window.daysOfWeek.length === 0) {
|
|
275
|
+
throw new AutomationValidationError(`${label}.daysOfWeek must not be empty — omit it for 'every day'.`);
|
|
276
|
+
}
|
|
277
|
+
const seenDays = new Set();
|
|
278
|
+
for (const day of window.daysOfWeek) {
|
|
279
|
+
if (!Number.isInteger(day) || day < 0 || day > 6) {
|
|
280
|
+
throw new AutomationValidationError(`${label}.daysOfWeek must contain integers 0 (Sunday) to 6 (Saturday), got ${day}.`);
|
|
281
|
+
}
|
|
282
|
+
if (seenDays.has(day)) {
|
|
283
|
+
throw new AutomationValidationError(`${label}.daysOfWeek contains the duplicate day ${day}.`);
|
|
284
|
+
}
|
|
285
|
+
seenDays.add(day);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
if (trigger.timezone !== undefined) {
|
|
290
|
+
requireNonEmptyString(trigger.timezone, 'Schedule trigger.timezone');
|
|
291
|
+
try {
|
|
292
|
+
new Intl.DateTimeFormat('en-US', { timeZone: trigger.timezone });
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
throw new AutomationValidationError(`Schedule trigger.timezone must be a valid IANA time zone identifier, got '${trigger.timezone}'.`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
183
299
|
function validateMqttAction(action, label) {
|
|
184
300
|
requireNonEmptyString(action.topic, `${label}.topic`);
|
|
185
301
|
if (typeof action.updateChargingPvSurplus !== 'boolean') {
|
|
@@ -203,12 +319,16 @@ function validateSmartPlugSwitchAction(action, label, knownSmartPlugApplianceIds
|
|
|
203
319
|
throw new AutomationValidationError(`${label}.applianceId does not reference a smart-plug appliance that supports the smart-plug-switch action: ${action.applianceId}.`);
|
|
204
320
|
}
|
|
205
321
|
const { minDurationMinutes } = action;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
minDurationMinutes
|
|
210
|
-
minDurationMinutes
|
|
211
|
-
|
|
322
|
+
// Short runtimes (1-5 minutes) are allowed at whole-minute granularity so a
|
|
323
|
+
// one-minute pulse can be configured; anything longer keeps the 5-minute step.
|
|
324
|
+
const isWholeMinutesInRange = typeof minDurationMinutes === 'number' &&
|
|
325
|
+
Number.isInteger(minDurationMinutes) &&
|
|
326
|
+
minDurationMinutes >= exports.AUTOMATION_MIN_DURATION_MINUTES &&
|
|
327
|
+
minDurationMinutes <= exports.AUTOMATION_MAX_DURATION_MINUTES;
|
|
328
|
+
const matchesStep = minDurationMinutes <= exports.AUTOMATION_DURATION_STEP_MINUTES ||
|
|
329
|
+
minDurationMinutes % exports.AUTOMATION_DURATION_STEP_MINUTES === 0;
|
|
330
|
+
if (!isWholeMinutesInRange || !matchesStep) {
|
|
331
|
+
throw new AutomationValidationError(`${label}.minDurationMinutes must be an integer between ${exports.AUTOMATION_MIN_DURATION_MINUTES} and ${exports.AUTOMATION_MAX_DURATION_MINUTES}; values above ${exports.AUTOMATION_DURATION_STEP_MINUTES} must be a multiple of ${exports.AUTOMATION_DURATION_STEP_MINUTES}.`);
|
|
212
332
|
}
|
|
213
333
|
}
|
|
214
334
|
/**
|
|
@@ -233,6 +353,19 @@ function validatePayloadTemplate(template, label) {
|
|
|
233
353
|
throw new AutomationValidationError(`${label} is not valid JSON once placeholders are substituted.`);
|
|
234
354
|
}
|
|
235
355
|
}
|
|
356
|
+
function requireOptionalIsoTimestamp(value, label) {
|
|
357
|
+
if (value === undefined) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (typeof value !== 'string' || Number.isNaN(Date.parse(value))) {
|
|
361
|
+
throw new AutomationValidationError(`${label} must be a valid ISO 8601 timestamp: ${String(value)}.`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function requireFinite(value, label) {
|
|
365
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
366
|
+
throw new AutomationValidationError(`${label} must be a finite number.`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
236
369
|
function requireFiniteNonNegative(value, label) {
|
|
237
370
|
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
238
371
|
throw new AutomationValidationError(`${label} must be a finite number >= 0.`);
|
|
@@ -7,12 +7,24 @@ import { EnyoAutomation, EnyoAutomationAction, EnyoAutomationForecast, EnyoAutom
|
|
|
7
7
|
export declare class AutomationValidationError extends Error {
|
|
8
8
|
constructor(message: string);
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Minimum smart-plug on-duration, in minutes. Short runtimes below
|
|
12
|
+
* {@link AUTOMATION_DURATION_STEP_MINUTES} are allowed at a one-minute
|
|
13
|
+
* granularity, so a one-minute runtime is valid.
|
|
14
|
+
*/
|
|
15
|
+
export declare const AUTOMATION_MIN_DURATION_MINUTES = 1;
|
|
12
16
|
/** Maximum smart-plug on-duration, in minutes (6 hours). */
|
|
13
17
|
export declare const AUTOMATION_MAX_DURATION_MINUTES = 360;
|
|
14
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Step size the smart-plug on-duration must be a multiple of, in minutes, once
|
|
20
|
+
* it exceeds {@link AUTOMATION_DURATION_STEP_MINUTES}. Durations at or below
|
|
21
|
+
* this value are free to use any whole minute (1, 2, 3, 4, 5).
|
|
22
|
+
*/
|
|
15
23
|
export declare const AUTOMATION_DURATION_STEP_MINUTES = 5;
|
|
24
|
+
/** Minimum share of the day, in percent, a cheapest-share trigger may select. */
|
|
25
|
+
export declare const AUTOMATION_MIN_CHEAPEST_SHARE_PERCENT = 1;
|
|
26
|
+
/** Maximum share of the day, in percent, a cheapest-share trigger may select. */
|
|
27
|
+
export declare const AUTOMATION_MAX_CHEAPEST_SHARE_PERCENT = 100;
|
|
16
28
|
/**
|
|
17
29
|
* Validates a complete {@link EnyoAutomation}. Throws on the first violation.
|
|
18
30
|
*
|
|
@@ -243,69 +243,111 @@ exports.onboardingV2Block = {
|
|
|
243
243
|
outcomes,
|
|
244
244
|
}),
|
|
245
245
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
246
|
+
* A device-select picker: its own screen listing the network devices the run
|
|
247
|
+
* has found, so the installer can say which one is being onboarded.
|
|
248
248
|
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* {@link
|
|
253
|
-
*
|
|
249
|
+
* Use it whenever a scan can turn up more than one candidate. A
|
|
250
|
+
* {@link onboardingV2Block.networkScan} branches on found/not-found but binds
|
|
251
|
+
* nothing, so without this the run does not know *which* device it is working
|
|
252
|
+
* on — and {@link EnyoOnboardingV2DeviceSelection.Current} and
|
|
253
|
+
* {@link EnyoOnboardingV2DynamicKind.DeviceIp} have nothing to resolve
|
|
254
|
+
* against.
|
|
254
255
|
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
256
|
+
* The block owns the step: give it one of its own, put the screen's wording
|
|
257
|
+
* in `headline`/`description` rather than in neighbouring blocks, and let it
|
|
258
|
+
* disappear when it has nothing to ask — with `autoSelectSingleMatch` left at
|
|
259
|
+
* its default, a single matching device is bound without rendering anything.
|
|
259
260
|
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
261
|
+
* The list is what discovery found, so the guide must have scanned: keep
|
|
262
|
+
* {@link EnyoOnboardingV2Guide.requiresNetworkScan} at its default, or place a
|
|
263
|
+
* {@link onboardingV2Block.networkScan} ahead of this one.
|
|
264
|
+
*
|
|
265
|
+
* Outcome `value`s must be {@link EnyoOnboardingV2DeviceSelectOutcome}
|
|
266
|
+
* members; route `not-found` to troubleshooting rather than to a step that
|
|
267
|
+
* assumes a device exists.
|
|
268
|
+
*
|
|
269
|
+
* Register an {@link EnyoOnboardingV2DeviceSelectHandler}
|
|
270
|
+
* ({@link EnergyAppOnboardingV2.registerDeviceSelectHandler}) to turn the pick
|
|
271
|
+
* into appliances — the host awaits it and binds the run to the ids it
|
|
272
|
+
* returns, whether or not the screen was shown. Without one the pick binds an
|
|
273
|
+
* address and nothing more.
|
|
264
274
|
*
|
|
265
275
|
* @param id - Stable block id, unique within the guide.
|
|
266
|
-
* @param
|
|
267
|
-
*
|
|
276
|
+
* @param options - Screen wording, optional `detectedAt` filter, skip
|
|
277
|
+
* behaviour, and the `selected` / `not-found` routing handles.
|
|
278
|
+
* @returns The device-select block.
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```ts
|
|
282
|
+
* onboardingV2Block.deviceSelect('pick', {
|
|
283
|
+
* headline: t('Gerät auswählen', 'Select the device'),
|
|
284
|
+
* description: t(
|
|
285
|
+
* 'Vergleichen Sie die Seriennummer auf dem Typenschild.',
|
|
286
|
+
* 'Compare the serial number on the type plate.',
|
|
287
|
+
* ),
|
|
288
|
+
* detectedAt: [EnyoNetworkDeviceDetectedAtEnum.Modbus],
|
|
289
|
+
* outcomes: [
|
|
290
|
+
* {id: 'ok', value: EnyoOnboardingV2DeviceSelectOutcome.Selected, label: t('Ausgewählt', 'Selected')},
|
|
291
|
+
* {id: 'none', value: EnyoOnboardingV2DeviceSelectOutcome.NotFound, label: t('Nicht dabei', 'Not listed')},
|
|
292
|
+
* ],
|
|
293
|
+
* });
|
|
294
|
+
* ```
|
|
268
295
|
*/
|
|
269
|
-
|
|
296
|
+
deviceSelect: (id, options) => ({
|
|
270
297
|
id,
|
|
271
|
-
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.
|
|
272
|
-
|
|
273
|
-
label,
|
|
274
|
-
outcomes,
|
|
298
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.DeviceSelect,
|
|
299
|
+
...options,
|
|
275
300
|
}),
|
|
276
301
|
/**
|
|
277
|
-
*
|
|
278
|
-
*
|
|
302
|
+
* An EEBUS device-select picker: its own screen listing the discovered EEBUS
|
|
303
|
+
* peers, so the installer can pick the one to trust — the host pairs it and
|
|
304
|
+
* records its SKI.
|
|
279
305
|
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
* {@link EnyoOnboardingV2DynamicKind.DeviceIp} have nothing to resolve
|
|
285
|
-
* against.
|
|
306
|
+
* Filter it. `deviceTypes` is what turns this from "here are the six EEBUS
|
|
307
|
+
* devices in the house" into "here is your heat pump", and with one match it
|
|
308
|
+
* skips the screen entirely instead of asking a question with one possible
|
|
309
|
+
* answer.
|
|
286
310
|
*
|
|
287
|
-
* The picker
|
|
288
|
-
* keep {@link EnyoOnboardingV2Guide.requiresNetworkScan} at its
|
|
289
|
-
* place a {@link
|
|
311
|
+
* The picker is drawn from what mDNS discovery found, so the guide must have
|
|
312
|
+
* scanned — keep {@link EnyoOnboardingV2Guide.requiresNetworkScan} at its
|
|
313
|
+
* default or place a {@link onboardingV2Block.networkScan} ahead of this one.
|
|
290
314
|
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
315
|
+
* Most EEBUS devices only announce themselves once pairing is enabled in
|
|
316
|
+
* their own menu or portal, and many ask for a confirmation there while the
|
|
317
|
+
* handshake runs, so put that instruction in a text/hint block on the
|
|
318
|
+
* preceding step — the app cannot do it for the installer.
|
|
294
319
|
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
320
|
+
* Outcome `value`s must be {@link EnyoOnboardingV2EebusPairOutcome} members;
|
|
321
|
+
* route `not-found` to troubleshooting and `failure` to a step describing the
|
|
322
|
+
* confirmation on the device. A retry must lead into a *second* picker step:
|
|
323
|
+
* a back-edge onto the same step reads as a loop and ends the run.
|
|
324
|
+
*
|
|
325
|
+
* Register an {@link EnyoOnboardingV2EebusDeviceSelectHandler}
|
|
326
|
+
* ({@link EnergyAppOnboardingV2.registerEebusDeviceSelectHandler}) to turn the
|
|
327
|
+
* paired peer into appliances.
|
|
298
328
|
*
|
|
299
329
|
* @param id - Stable block id, unique within the guide.
|
|
300
|
-
* @param
|
|
301
|
-
*
|
|
330
|
+
* @param options - Screen wording, optional `deviceTypes` filter, skip
|
|
331
|
+
* behaviour, and the `paired` / `not-found` / `failure` routing handles.
|
|
332
|
+
* @returns The EEBUS device-select block.
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* onboardingV2Block.eebusDeviceSelect('pair', {
|
|
337
|
+
* headline: t('Wärmepumpe auswählen', 'Select the heat pump'),
|
|
338
|
+
* deviceTypes: [EnyoEebusDeviceTypeEnum.HeatPumpAppliance],
|
|
339
|
+
* outcomes: [
|
|
340
|
+
* {id: 'ok', value: EnyoOnboardingV2EebusPairOutcome.Paired, label: t('Gekoppelt', 'Paired')},
|
|
341
|
+
* {id: 'none', value: EnyoOnboardingV2EebusPairOutcome.NotFound, label: t('Nichts gefunden', 'Nothing found')},
|
|
342
|
+
* {id: 'error', value: EnyoOnboardingV2EebusPairOutcome.Failure, label: t('Kopplung fehlgeschlagen', 'Pairing failed')},
|
|
343
|
+
* ],
|
|
344
|
+
* });
|
|
345
|
+
* ```
|
|
302
346
|
*/
|
|
303
|
-
|
|
347
|
+
eebusDeviceSelect: (id, options) => ({
|
|
304
348
|
id,
|
|
305
|
-
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.
|
|
306
|
-
|
|
307
|
-
label,
|
|
308
|
-
outcomes,
|
|
349
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.EebusDeviceSelect,
|
|
350
|
+
...options,
|
|
309
351
|
}),
|
|
310
352
|
/**
|
|
311
353
|
* An auth block: the installer signs into the energy app's own account
|
|
@@ -320,8 +362,12 @@ exports.onboardingV2Block = {
|
|
|
320
362
|
* @param label - Translated sign-in button text (de/en).
|
|
321
363
|
* @param outcome - The single success handle (`{id, label}`).
|
|
322
364
|
* @param opts - Optional translated `help` naming the account that is needed,
|
|
323
|
-
* and `
|
|
324
|
-
* when the provider rejects a custom-scheme redirect
|
|
365
|
+
* and `redirectUrlFilter` to constrain the enyo callback URL the host
|
|
366
|
+
* generates — `webOnly` when the provider rejects a custom-scheme redirect
|
|
367
|
+
* such as `enyoapp://`, `pattern` when its OAuth app was registered with a
|
|
368
|
+
* redirect URI that carries the request id as a path segment rather than a
|
|
369
|
+
* query parameter. `requiresWebAuthentication` is the deprecated spelling
|
|
370
|
+
* of `redirectUrlFilter.webOnly`.
|
|
325
371
|
*/
|
|
326
372
|
auth: (id, label, outcome, opts) => ({
|
|
327
373
|
id,
|
|
@@ -330,6 +376,7 @@ exports.onboardingV2Block = {
|
|
|
330
376
|
outcome,
|
|
331
377
|
help: opts?.help,
|
|
332
378
|
requiresWebAuthentication: opts?.requiresWebAuthentication,
|
|
379
|
+
redirectUrlFilter: opts?.redirectUrlFilter,
|
|
333
380
|
}),
|
|
334
381
|
/**
|
|
335
382
|
* A link block: a fixed URL the installer opens or copies.
|