@enyo-energy/energy-app-sdk 0.0.192 → 0.0.194
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 +10 -0
- package/dist/cjs/energy-app-package-definition.d.cts +12 -0
- package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.cjs +105 -2
- package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.d.cts +85 -4
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.cjs +199 -14
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.d.cts +10 -4
- package/dist/cjs/integrations/wallbox-integration-energy-app.cjs +34 -0
- package/dist/cjs/integrations/wallbox-integration-energy-app.d.cts +23 -1
- package/dist/cjs/types/enyo-data-bus-value.cjs +21 -1
- package/dist/cjs/types/enyo-data-bus-value.d.cts +155 -1
- package/dist/cjs/types/enyo-onboarding-v2.cjs +104 -7
- package/dist/cjs/types/enyo-onboarding-v2.d.cts +179 -12
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/energy-app-package-definition.d.ts +12 -0
- package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.d.ts +85 -4
- package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.js +106 -3
- package/dist/implementations/onboarding-v2/onboarding-v2-validators.d.ts +10 -4
- package/dist/implementations/onboarding-v2/onboarding-v2-validators.js +200 -15
- package/dist/integrations/wallbox-integration-energy-app.d.ts +23 -1
- package/dist/integrations/wallbox-integration-energy-app.js +34 -0
- package/dist/types/enyo-data-bus-value.d.ts +155 -1
- package/dist/types/enyo-data-bus-value.js +20 -0
- package/dist/types/enyo-onboarding-v2.d.ts +179 -12
- package/dist/types/enyo-onboarding-v2.js +103 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2054,6 +2054,16 @@ Drives an EV wallbox / charger. Has the richest command surface of all integrati
|
|
|
2054
2054
|
- `publishChargingMeterValues(applianceId, data)` — periodic meter values during a session.
|
|
2055
2055
|
- `publishMaxChargingPowerChanged(applianceId, maxChargingPowerKw)` — e.g. on thermal derating.
|
|
2056
2056
|
- `publishChargerStatusChanged(applianceId, data)` — OCPP-style status changes.
|
|
2057
|
+
- `publishEnergyManagementChargingState(applianceId, data)` — the app's own continuously
|
|
2058
|
+
updated state for the energy manager. Facts only: `activePhases` (the phase count in
|
|
2059
|
+
force), `phaseSwitching` (`supported` / `available` right now, `availableFromIso` when
|
|
2060
|
+
it is barred until a known time, `inProgress`), `appliedCurrentLimitA` and the
|
|
2061
|
+
`appliedPowerW` it implies, `lastLimitRequest` (`accepted` \| `clamped` \| `rejected`,
|
|
2062
|
+
with what was requested), and `measuredAtIso` — when the reading was taken, as opposed
|
|
2063
|
+
to when the message was sent. No recommended minimum power, no suggested setpoint,
|
|
2064
|
+
nothing derived from the manager's own plan; static nameplate data stays in the
|
|
2065
|
+
appliance metadata. Republish the **complete** payload on every change and periodically
|
|
2066
|
+
as a heartbeat — an omitted field means "unknown", never "unchanged".
|
|
2057
2067
|
|
|
2058
2068
|
```typescript
|
|
2059
2069
|
class MyWallbox extends WallboxIntegrationEnergyApp {
|
|
@@ -228,6 +228,18 @@ export interface EnergyAppPackageCompatibilityModel {
|
|
|
228
228
|
* upgrade graph in {@link EnergyAppPackageDefinition.firmware} instead.
|
|
229
229
|
*/
|
|
230
230
|
minimumFirmwareVersion?: string;
|
|
231
|
+
/**
|
|
232
|
+
* Optional device category of this concrete model (e.g. `Inverter`,
|
|
233
|
+
* `BatteryStorage`).
|
|
234
|
+
*
|
|
235
|
+
* Packages can support models of different kinds — a hybrid inverter plus a
|
|
236
|
+
* matching battery, for example — while
|
|
237
|
+
* {@link EnergyAppPackageDefinition.categories} only describes the package
|
|
238
|
+
* as a whole. Declaring the category per model lets the enyo Store and
|
|
239
|
+
* onboarding flows group and filter individual models correctly. Omit when
|
|
240
|
+
* the package-level categories are precise enough.
|
|
241
|
+
*/
|
|
242
|
+
category?: EnergyAppPackageCategory;
|
|
231
243
|
/** Optional internal note explaining model-specific caveats or limitations */
|
|
232
244
|
internalComment?: string;
|
|
233
245
|
/**
|
|
@@ -133,6 +133,83 @@ exports.onboardingV2Block = {
|
|
|
133
133
|
outcomes,
|
|
134
134
|
deviceSelection,
|
|
135
135
|
}),
|
|
136
|
+
/**
|
|
137
|
+
* An OCPP-connect action block: wait for the charger to dial into enyo's
|
|
138
|
+
* CSMS after the installer has entered the dynamic OCPP URL in it.
|
|
139
|
+
*
|
|
140
|
+
* A convenience wrapper over {@link onboardingV2Block.action} that pins the
|
|
141
|
+
* action kind. Nothing is searched — an OCPP wallbox is never on the LAN, so
|
|
142
|
+
* {@link EnyoOnboardingV2ActionKind.NetworkScan} is not a substitute. Pair it
|
|
143
|
+
* with an {@link onboardingV2Block.dynamic} `ocpp-url` block on the same or a
|
|
144
|
+
* preceding step.
|
|
145
|
+
*
|
|
146
|
+
* Outcome `value`s must be {@link EnyoOnboardingV2OcppConnectOutcome} members
|
|
147
|
+
* and both must be wired — the validator enforces that, since a charger that
|
|
148
|
+
* never calls home is the common case.
|
|
149
|
+
*
|
|
150
|
+
* @param id - Stable block id, unique within the guide.
|
|
151
|
+
* @param label - Translated trigger button text (de/en).
|
|
152
|
+
* @param outcomes - The `connected` / `timeout` results; each is a routing handle.
|
|
153
|
+
*/
|
|
154
|
+
ocppConnect: (id, label, outcomes) => ({
|
|
155
|
+
id,
|
|
156
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action,
|
|
157
|
+
action: enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.OcppConnect,
|
|
158
|
+
label,
|
|
159
|
+
outcomes,
|
|
160
|
+
}),
|
|
161
|
+
/**
|
|
162
|
+
* An EEBUS-pair action block: the installer picks one of the discovered
|
|
163
|
+
* EEBUS peers and the host trusts its SKI.
|
|
164
|
+
*
|
|
165
|
+
* A convenience wrapper over {@link onboardingV2Block.action} that pins the
|
|
166
|
+
* action kind. The picker is drawn from what mDNS discovery found, so the
|
|
167
|
+
* guide must have scanned — keep
|
|
168
|
+
* {@link EnyoOnboardingV2Guide.requiresNetworkScan} at its default or place
|
|
169
|
+
* a {@link EnyoOnboardingV2ActionKind.NetworkScan} block ahead of this one.
|
|
170
|
+
*
|
|
171
|
+
* Most EEBUS devices only announce themselves once pairing is enabled in
|
|
172
|
+
* their own menu or portal, and many ask for a confirmation there while the
|
|
173
|
+
* handshake runs, so put that instruction in a text/hint block on the
|
|
174
|
+
* preceding step — the app cannot do it for the installer.
|
|
175
|
+
*
|
|
176
|
+
* Outcome `value`s must be {@link EnyoOnboardingV2EebusPairOutcome} members;
|
|
177
|
+
* route `not-found` to troubleshooting and `failure` to a step describing
|
|
178
|
+
* the confirmation on the device. A retry must lead into a *second* pairing
|
|
179
|
+
* step: a back-edge onto the same step reads as a loop and ends the run.
|
|
180
|
+
*
|
|
181
|
+
* @param id - Stable block id, unique within the guide.
|
|
182
|
+
* @param label - Translated trigger button text (de/en).
|
|
183
|
+
* @param outcomes - The `paired` / `not-found` / `failure` results; each is a routing handle.
|
|
184
|
+
*/
|
|
185
|
+
eebusPair: (id, label, outcomes) => ({
|
|
186
|
+
id,
|
|
187
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action,
|
|
188
|
+
action: enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.EebusPair,
|
|
189
|
+
label,
|
|
190
|
+
outcomes,
|
|
191
|
+
}),
|
|
192
|
+
/**
|
|
193
|
+
* An auth block: the installer signs into the energy app's own account
|
|
194
|
+
* system (OAuth / vendor portal).
|
|
195
|
+
*
|
|
196
|
+
* Exactly one routing handle, and it means "the login succeeded". The server
|
|
197
|
+
* decides when it fires, so the installer cannot skip it; there is no failure
|
|
198
|
+
* branch to author — a failed attempt simply keeps them on the step. Route
|
|
199
|
+
* the handle with {@link onOutcomeV2}, passing `outcome.id`.
|
|
200
|
+
*
|
|
201
|
+
* @param id - Stable block id, unique within the guide.
|
|
202
|
+
* @param label - Translated sign-in button text (de/en).
|
|
203
|
+
* @param outcome - The single success handle (`{id, label}`).
|
|
204
|
+
* @param opts - Optional translated `help` naming the account that is needed.
|
|
205
|
+
*/
|
|
206
|
+
auth: (id, label, outcome, opts) => ({
|
|
207
|
+
id,
|
|
208
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Auth,
|
|
209
|
+
label,
|
|
210
|
+
outcome,
|
|
211
|
+
help: opts?.help,
|
|
212
|
+
}),
|
|
136
213
|
/**
|
|
137
214
|
* A link block: a fixed URL the installer opens or copies.
|
|
138
215
|
*
|
|
@@ -191,14 +268,40 @@ exports.onboardingV2Target = {
|
|
|
191
268
|
step: (stepId) => ({ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Step, stepId }),
|
|
192
269
|
/** Exit: onboarding succeeded (hand back to the app). */
|
|
193
270
|
success: () => ({ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Success }),
|
|
194
|
-
/**
|
|
195
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Exit: escalate to enyo support.
|
|
273
|
+
* @param reason - Optional short internal key describing what failed, e.g.
|
|
274
|
+
* `firmware-too-old`. Never shown to the installer; it travels with the
|
|
275
|
+
* hand-off so support knows why it arrived.
|
|
276
|
+
*/
|
|
277
|
+
support: (reason) => ({
|
|
278
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Support,
|
|
279
|
+
reason,
|
|
280
|
+
}),
|
|
196
281
|
/**
|
|
197
282
|
* Exit: pause the run (resumable) with a reason.
|
|
283
|
+
*
|
|
284
|
+
* For {@link EnyoOnboardingV2PauseReason.EnyoTodo} prefer
|
|
285
|
+
* {@link onboardingV2Target.enyoTakeover} — that reason is a terminal
|
|
286
|
+
* hand-off, not a park, and `resumeStepName` does not apply to it.
|
|
287
|
+
*
|
|
198
288
|
* @param reason - Why the run is parked.
|
|
199
289
|
* @param resumeStepName - Optional step `name` to resume at.
|
|
200
290
|
*/
|
|
201
291
|
pause: (reason, resumeStepName) => ({ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause, reason, resumeStepName }),
|
|
292
|
+
/**
|
|
293
|
+
* Exit: **enyo übernimmt** — the installer is done and enyo finishes the
|
|
294
|
+
* setup. A terminal exit alongside `success` and `support`: the app shows the
|
|
295
|
+
* takeover screen rather than returning to the cockpit.
|
|
296
|
+
*
|
|
297
|
+
* Emits the unchanged wire shape (`pause` with reason `enyo-todo`), and
|
|
298
|
+
* counts as a completing exit for {@link validateOnboardingGuideV2} — a guide
|
|
299
|
+
* that only ends here needs no `success` branch.
|
|
300
|
+
*/
|
|
301
|
+
enyoTakeover: () => ({
|
|
302
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause,
|
|
303
|
+
reason: enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo,
|
|
304
|
+
}),
|
|
202
305
|
/**
|
|
203
306
|
* Jump into another start variant's flow for the same vendor/model.
|
|
204
307
|
* @param variant - The start variant to hand off to.
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* before publishing.
|
|
11
11
|
*/
|
|
12
12
|
import type { EnyoOnboardingTranslatedContent } from '../../types/enyo-onboarding.cjs';
|
|
13
|
-
import { EnyoOnboardingV2ActionKind, EnyoOnboardingV2ChoiceLayout, EnyoOnboardingV2DeviceSelection } from '../../types/enyo-onboarding-v2.cjs';
|
|
14
|
-
import type { EnyoOnboardingV2ActionOutcome, EnyoOnboardingV2Block, EnyoOnboardingV2ChoiceOption, EnyoOnboardingV2DynamicKind, EnyoOnboardingV2Guide, EnyoOnboardingV2HintVariant, EnyoOnboardingV2InputOutcome, EnyoOnboardingV2InputValueType,
|
|
13
|
+
import { EnyoOnboardingV2ActionKind, EnyoOnboardingV2ChoiceLayout, EnyoOnboardingV2DeviceSelection, EnyoOnboardingV2PauseReason } from '../../types/enyo-onboarding-v2.cjs';
|
|
14
|
+
import type { EnyoOnboardingV2ActionOutcome, EnyoOnboardingV2AuthOutcome, EnyoOnboardingV2Block, EnyoOnboardingV2ChoiceOption, EnyoOnboardingV2DynamicKind, EnyoOnboardingV2Guide, EnyoOnboardingV2HintVariant, EnyoOnboardingV2InputOutcome, EnyoOnboardingV2InputValueType, EnyoOnboardingV2StartVariant, EnyoOnboardingV2Target, EnyoOnboardingV2Transition } from '../../types/enyo-onboarding-v2.cjs';
|
|
15
15
|
/**
|
|
16
16
|
* Identity helper that type-checks a v2 guide literal at definition time
|
|
17
17
|
* (mirrors `defineEnergyAppPackage`). Prefer this over a bare object literal so
|
|
@@ -98,6 +98,67 @@ export declare const onboardingV2Block: {
|
|
|
98
98
|
* @param deviceSelection - Which devices to test (defaults to `Detected`).
|
|
99
99
|
*/
|
|
100
100
|
deviceTest: (id: string, label: EnyoOnboardingTranslatedContent[], outcomes: EnyoOnboardingV2ActionOutcome[], deviceSelection?: EnyoOnboardingV2DeviceSelection) => EnyoOnboardingV2Block;
|
|
101
|
+
/**
|
|
102
|
+
* An OCPP-connect action block: wait for the charger to dial into enyo's
|
|
103
|
+
* CSMS after the installer has entered the dynamic OCPP URL in it.
|
|
104
|
+
*
|
|
105
|
+
* A convenience wrapper over {@link onboardingV2Block.action} that pins the
|
|
106
|
+
* action kind. Nothing is searched — an OCPP wallbox is never on the LAN, so
|
|
107
|
+
* {@link EnyoOnboardingV2ActionKind.NetworkScan} is not a substitute. Pair it
|
|
108
|
+
* with an {@link onboardingV2Block.dynamic} `ocpp-url` block on the same or a
|
|
109
|
+
* preceding step.
|
|
110
|
+
*
|
|
111
|
+
* Outcome `value`s must be {@link EnyoOnboardingV2OcppConnectOutcome} members
|
|
112
|
+
* and both must be wired — the validator enforces that, since a charger that
|
|
113
|
+
* never calls home is the common case.
|
|
114
|
+
*
|
|
115
|
+
* @param id - Stable block id, unique within the guide.
|
|
116
|
+
* @param label - Translated trigger button text (de/en).
|
|
117
|
+
* @param outcomes - The `connected` / `timeout` results; each is a routing handle.
|
|
118
|
+
*/
|
|
119
|
+
ocppConnect: (id: string, label: EnyoOnboardingTranslatedContent[], outcomes: EnyoOnboardingV2ActionOutcome[]) => EnyoOnboardingV2Block;
|
|
120
|
+
/**
|
|
121
|
+
* An EEBUS-pair action block: the installer picks one of the discovered
|
|
122
|
+
* EEBUS peers and the host trusts its SKI.
|
|
123
|
+
*
|
|
124
|
+
* A convenience wrapper over {@link onboardingV2Block.action} that pins the
|
|
125
|
+
* action kind. The picker is drawn from what mDNS discovery found, so the
|
|
126
|
+
* guide must have scanned — keep
|
|
127
|
+
* {@link EnyoOnboardingV2Guide.requiresNetworkScan} at its default or place
|
|
128
|
+
* a {@link EnyoOnboardingV2ActionKind.NetworkScan} block ahead of this one.
|
|
129
|
+
*
|
|
130
|
+
* Most EEBUS devices only announce themselves once pairing is enabled in
|
|
131
|
+
* their own menu or portal, and many ask for a confirmation there while the
|
|
132
|
+
* handshake runs, so put that instruction in a text/hint block on the
|
|
133
|
+
* preceding step — the app cannot do it for the installer.
|
|
134
|
+
*
|
|
135
|
+
* Outcome `value`s must be {@link EnyoOnboardingV2EebusPairOutcome} members;
|
|
136
|
+
* route `not-found` to troubleshooting and `failure` to a step describing
|
|
137
|
+
* the confirmation on the device. A retry must lead into a *second* pairing
|
|
138
|
+
* step: a back-edge onto the same step reads as a loop and ends the run.
|
|
139
|
+
*
|
|
140
|
+
* @param id - Stable block id, unique within the guide.
|
|
141
|
+
* @param label - Translated trigger button text (de/en).
|
|
142
|
+
* @param outcomes - The `paired` / `not-found` / `failure` results; each is a routing handle.
|
|
143
|
+
*/
|
|
144
|
+
eebusPair: (id: string, label: EnyoOnboardingTranslatedContent[], outcomes: EnyoOnboardingV2ActionOutcome[]) => EnyoOnboardingV2Block;
|
|
145
|
+
/**
|
|
146
|
+
* An auth block: the installer signs into the energy app's own account
|
|
147
|
+
* system (OAuth / vendor portal).
|
|
148
|
+
*
|
|
149
|
+
* Exactly one routing handle, and it means "the login succeeded". The server
|
|
150
|
+
* decides when it fires, so the installer cannot skip it; there is no failure
|
|
151
|
+
* branch to author — a failed attempt simply keeps them on the step. Route
|
|
152
|
+
* the handle with {@link onOutcomeV2}, passing `outcome.id`.
|
|
153
|
+
*
|
|
154
|
+
* @param id - Stable block id, unique within the guide.
|
|
155
|
+
* @param label - Translated sign-in button text (de/en).
|
|
156
|
+
* @param outcome - The single success handle (`{id, label}`).
|
|
157
|
+
* @param opts - Optional translated `help` naming the account that is needed.
|
|
158
|
+
*/
|
|
159
|
+
auth: (id: string, label: EnyoOnboardingTranslatedContent[], outcome: EnyoOnboardingV2AuthOutcome, opts?: {
|
|
160
|
+
help?: EnyoOnboardingTranslatedContent[];
|
|
161
|
+
}) => EnyoOnboardingV2Block;
|
|
101
162
|
/**
|
|
102
163
|
* A link block: a fixed URL the installer opens or copies.
|
|
103
164
|
*
|
|
@@ -143,14 +204,34 @@ export declare const onboardingV2Target: {
|
|
|
143
204
|
step: (stepId: string) => EnyoOnboardingV2Target;
|
|
144
205
|
/** Exit: onboarding succeeded (hand back to the app). */
|
|
145
206
|
success: () => EnyoOnboardingV2Target;
|
|
146
|
-
/**
|
|
147
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Exit: escalate to enyo support.
|
|
209
|
+
* @param reason - Optional short internal key describing what failed, e.g.
|
|
210
|
+
* `firmware-too-old`. Never shown to the installer; it travels with the
|
|
211
|
+
* hand-off so support knows why it arrived.
|
|
212
|
+
*/
|
|
213
|
+
support: (reason?: string) => EnyoOnboardingV2Target;
|
|
148
214
|
/**
|
|
149
215
|
* Exit: pause the run (resumable) with a reason.
|
|
216
|
+
*
|
|
217
|
+
* For {@link EnyoOnboardingV2PauseReason.EnyoTodo} prefer
|
|
218
|
+
* {@link onboardingV2Target.enyoTakeover} — that reason is a terminal
|
|
219
|
+
* hand-off, not a park, and `resumeStepName` does not apply to it.
|
|
220
|
+
*
|
|
150
221
|
* @param reason - Why the run is parked.
|
|
151
222
|
* @param resumeStepName - Optional step `name` to resume at.
|
|
152
223
|
*/
|
|
153
224
|
pause: (reason: EnyoOnboardingV2PauseReason, resumeStepName?: string) => EnyoOnboardingV2Target;
|
|
225
|
+
/**
|
|
226
|
+
* Exit: **enyo übernimmt** — the installer is done and enyo finishes the
|
|
227
|
+
* setup. A terminal exit alongside `success` and `support`: the app shows the
|
|
228
|
+
* takeover screen rather than returning to the cockpit.
|
|
229
|
+
*
|
|
230
|
+
* Emits the unchanged wire shape (`pause` with reason `enyo-todo`), and
|
|
231
|
+
* counts as a completing exit for {@link validateOnboardingGuideV2} — a guide
|
|
232
|
+
* that only ends here needs no `success` branch.
|
|
233
|
+
*/
|
|
234
|
+
enyoTakeover: () => EnyoOnboardingV2Target;
|
|
154
235
|
/**
|
|
155
236
|
* Jump into another start variant's flow for the same vendor/model.
|
|
156
237
|
* @param variant - The start variant to hand off to.
|
|
@@ -5,8 +5,14 @@
|
|
|
5
5
|
* energy app can fail fast locally before publishing.
|
|
6
6
|
*
|
|
7
7
|
* `errors` block publishing; `warnings` are advisory (e.g. unreachable step, no
|
|
8
|
-
* path to
|
|
9
|
-
* result, or {@link assertValidOnboardingGuideV2} to throw on the
|
|
8
|
+
* path to a completing exit). Use {@link validateOnboardingGuideV2} for the
|
|
9
|
+
* non-throwing result, or {@link assertValidOnboardingGuideV2} to throw on the
|
|
10
|
+
* first failure.
|
|
11
|
+
*
|
|
12
|
+
* A run completes either through a `success` target **or** through the
|
|
13
|
+
* "enyo übernimmt" hand-off (`pause` with reason
|
|
14
|
+
* {@link EnyoOnboardingV2PauseReason.EnyoTodo}) — both count, so a guide that
|
|
15
|
+
* legitimately ends in a hand-off is not nagged about a missing success path.
|
|
10
16
|
*/
|
|
11
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
18
|
exports.OnboardingV2ValidationError = void 0;
|
|
@@ -34,8 +40,8 @@ exports.OnboardingV2ValidationError = OnboardingV2ValidationError;
|
|
|
34
40
|
const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
35
41
|
/**
|
|
36
42
|
* Structural + plausibility validation of a v2 guide graph. `errors` block
|
|
37
|
-
* publishing; `warnings` are advisory (e.g. unreachable step, no path to
|
|
38
|
-
* success).
|
|
43
|
+
* publishing; `warnings` are advisory (e.g. unreachable step, no path to a
|
|
44
|
+
* completing exit — `success` or the `enyo-todo` hand-off).
|
|
39
45
|
*
|
|
40
46
|
* @param guide - The v2 guide to validate.
|
|
41
47
|
* @returns The {@link OnboardingV2ValidationResult}.
|
|
@@ -72,11 +78,12 @@ function validateOnboardingGuideV2(guide) {
|
|
|
72
78
|
validateActionBlocks(step, at, errors, warnings);
|
|
73
79
|
validateLinkBlocks(step, at, errors, warnings);
|
|
74
80
|
validateInputBlocks(step, at, errors, warnings);
|
|
81
|
+
validateAuthBlocks(step, at, errors, warnings);
|
|
75
82
|
}
|
|
76
83
|
if (!guide.startStepId || !stepIds.has(guide.startStepId)) {
|
|
77
84
|
errors.push('`startStepId` must reference an existing step.');
|
|
78
85
|
}
|
|
79
|
-
let
|
|
86
|
+
let hasCompletingExit = false;
|
|
80
87
|
for (const [i, step] of guide.steps.entries()) {
|
|
81
88
|
const at = `steps[${i}] (${step.name})`;
|
|
82
89
|
const required = requiredHandleKeys(step);
|
|
@@ -90,14 +97,24 @@ function validateOnboardingGuideV2(guide) {
|
|
|
90
97
|
wired.add(key);
|
|
91
98
|
const tg = t.target;
|
|
92
99
|
if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Success)
|
|
93
|
-
|
|
100
|
+
hasCompletingExit = true;
|
|
94
101
|
else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Step && !stepIds.has(tg.stepId)) {
|
|
95
102
|
errors.push(`${at}: transition targets missing step "${tg.stepId}".`);
|
|
96
103
|
}
|
|
97
|
-
else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause) {
|
|
105
|
+
// "enyo übernimmt" is a terminal hand-off, not a park: it completes
|
|
106
|
+
// the run for the installer, so it satisfies the completion check
|
|
107
|
+
// and has nothing to resume at.
|
|
108
|
+
if (tg.reason === enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo) {
|
|
109
|
+
hasCompletingExit = true;
|
|
110
|
+
if (tg.resumeStepName) {
|
|
111
|
+
warnings.push(`${at}: pause reason "${enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo}" is a hand-off to enyo, ` +
|
|
112
|
+
`not a resumable park — resumeStepName "${tg.resumeStepName}" is ignored.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (tg.resumeStepName && !stepNames.has(tg.resumeStepName)) {
|
|
116
|
+
errors.push(`${at}: pause resumeStepName "${tg.resumeStepName}" is unknown.`);
|
|
117
|
+
}
|
|
101
118
|
}
|
|
102
119
|
else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.StartVariant &&
|
|
103
120
|
tg.variant === guide.startVariant) {
|
|
@@ -117,8 +134,11 @@ function validateOnboardingGuideV2(guide) {
|
|
|
117
134
|
warnings.push(`step "${step.name}" is unreachable from the start.`);
|
|
118
135
|
}
|
|
119
136
|
}
|
|
120
|
-
if (!
|
|
121
|
-
warnings.push('No branch reaches a `success`
|
|
137
|
+
if (!hasCompletingExit) {
|
|
138
|
+
warnings.push('No branch reaches a completing exit — wire a `success` target, or a `pause` with reason ' +
|
|
139
|
+
`"${enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo}" when enyo takes the setup over.`);
|
|
140
|
+
}
|
|
141
|
+
validateNetworkScanFlag(guide, warnings);
|
|
122
142
|
return { ok: errors.length === 0, errors, warnings };
|
|
123
143
|
}
|
|
124
144
|
/**
|
|
@@ -155,6 +175,12 @@ function validateActionBlocks(step, at, errors, warnings) {
|
|
|
155
175
|
if (block.deviceSelection) {
|
|
156
176
|
warnings.push(`${at}: block "${block.id}" sets deviceSelection but is not a device-test action; it is ignored.`);
|
|
157
177
|
}
|
|
178
|
+
if (block.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.OcppConnect) {
|
|
179
|
+
validateOcppConnectOutcomes(block, at, errors);
|
|
180
|
+
}
|
|
181
|
+
if (block.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.EebusPair) {
|
|
182
|
+
validateEebusPairOutcomes(block, at, errors, warnings);
|
|
183
|
+
}
|
|
158
184
|
continue;
|
|
159
185
|
}
|
|
160
186
|
const values = new Set();
|
|
@@ -210,8 +236,17 @@ function validateLinkBlocks(step, at, errors, warnings) {
|
|
|
210
236
|
}
|
|
211
237
|
/** Every valid {@link EnyoOnboardingV2InputValueType} value. */
|
|
212
238
|
const INPUT_VALUE_TYPES = new Set(Object.values(enyo_onboarding_v2_js_1.EnyoOnboardingV2InputValueType));
|
|
213
|
-
/**
|
|
214
|
-
|
|
239
|
+
/**
|
|
240
|
+
* Outcome values the host treats as "the check succeeded" — mirrors the
|
|
241
|
+
* runtime's own positive-outcome set, `paired` included, so an eebus-pair
|
|
242
|
+
* result is not read as a failure.
|
|
243
|
+
*/
|
|
244
|
+
const POSITIVE_INPUT_OUTCOMES = new Set([
|
|
245
|
+
'reachable',
|
|
246
|
+
'success',
|
|
247
|
+
'found',
|
|
248
|
+
enyo_onboarding_v2_js_1.EnyoOnboardingV2EebusPairOutcome.Paired,
|
|
249
|
+
]);
|
|
215
250
|
/**
|
|
216
251
|
* The {@link EnyoDeviceTestOutcomeEnum} verdicts that collapse onto a positive
|
|
217
252
|
* input outcome; every other verdict collapses onto a negative one.
|
|
@@ -284,6 +319,153 @@ function validateInputBlocks(step, at, errors, warnings) {
|
|
|
284
319
|
}
|
|
285
320
|
}
|
|
286
321
|
}
|
|
322
|
+
/** Every {@link EnyoOnboardingV2OcppConnectOutcome} value. */
|
|
323
|
+
const OCPP_CONNECT_OUTCOMES = new Set(Object.values(enyo_onboarding_v2_js_1.EnyoOnboardingV2OcppConnectOutcome));
|
|
324
|
+
/**
|
|
325
|
+
* Validates the outcomes of an {@link EnyoOnboardingV2ActionKind.OcppConnect}
|
|
326
|
+
* block.
|
|
327
|
+
*
|
|
328
|
+
* The block waits for the charger to dial into our CSMS; it can only ever report
|
|
329
|
+
* that the connection arrived or that it did not, so its outcome `value`s are
|
|
330
|
+
* closed over {@link EnyoOnboardingV2OcppConnectOutcome}. Both must be wired: a
|
|
331
|
+
* charger that never calls home — wrong URL typed, no mobile coverage in the
|
|
332
|
+
* garage — is the *common* case, and a guide without a `timeout` branch strands
|
|
333
|
+
* the installer on a spinner.
|
|
334
|
+
*
|
|
335
|
+
* @param block - The ocpp-connect action block being checked.
|
|
336
|
+
* @param at - Human-readable location prefix for messages.
|
|
337
|
+
* @param errors - Collector for blocking problems.
|
|
338
|
+
*/
|
|
339
|
+
function validateOcppConnectOutcomes(block, at, errors) {
|
|
340
|
+
const values = new Set();
|
|
341
|
+
for (const outcome of block.outcomes ?? []) {
|
|
342
|
+
if (!OCPP_CONNECT_OUTCOMES.has(outcome.value)) {
|
|
343
|
+
errors.push(`${at}: ocpp-connect block "${block.id}" has outcome value "${outcome.value}", which is not an EnyoOnboardingV2OcppConnectOutcome member.`);
|
|
344
|
+
}
|
|
345
|
+
else if (values.has(outcome.value)) {
|
|
346
|
+
errors.push(`${at}: ocpp-connect block "${block.id}" wires outcome value "${outcome.value}" more than once.`);
|
|
347
|
+
}
|
|
348
|
+
values.add(outcome.value);
|
|
349
|
+
}
|
|
350
|
+
for (const required of OCPP_CONNECT_OUTCOMES) {
|
|
351
|
+
if (!values.has(required)) {
|
|
352
|
+
errors.push(`${at}: ocpp-connect block "${block.id}" has no "${required}" outcome; both results must be routed.`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/** Every {@link EnyoOnboardingV2EebusPairOutcome} value. */
|
|
357
|
+
const EEBUS_PAIR_OUTCOMES = new Set(Object.values(enyo_onboarding_v2_js_1.EnyoOnboardingV2EebusPairOutcome));
|
|
358
|
+
/**
|
|
359
|
+
* Validates the outcomes of an {@link EnyoOnboardingV2ActionKind.EebusPair}
|
|
360
|
+
* block.
|
|
361
|
+
*
|
|
362
|
+
* The block reports one of three things — a peer was picked and the SHIP
|
|
363
|
+
* handshake came up, discovery found nothing, or the handshake failed — so its
|
|
364
|
+
* outcome `value`s are closed over {@link EnyoOnboardingV2EebusPairOutcome}.
|
|
365
|
+
* Anything else is an outcome that can never fire.
|
|
366
|
+
*
|
|
367
|
+
* The missing `paired` branch is a warning rather than an error: it strands
|
|
368
|
+
* every successful pairing, but an author staging a guide step by step may
|
|
369
|
+
* legitimately not have wired it yet.
|
|
370
|
+
*
|
|
371
|
+
* @param block - The eebus-pair action block being checked.
|
|
372
|
+
* @param at - Human-readable location prefix for messages.
|
|
373
|
+
* @param errors - Collector for blocking problems.
|
|
374
|
+
* @param warnings - Collector for advisory problems.
|
|
375
|
+
*/
|
|
376
|
+
function validateEebusPairOutcomes(block, at, errors, warnings) {
|
|
377
|
+
const values = new Set();
|
|
378
|
+
for (const outcome of block.outcomes ?? []) {
|
|
379
|
+
if (!EEBUS_PAIR_OUTCOMES.has(outcome.value)) {
|
|
380
|
+
errors.push(`${at}: eebus-pair block "${block.id}" has outcome value "${outcome.value}", which is not an EnyoOnboardingV2EebusPairOutcome member.`);
|
|
381
|
+
}
|
|
382
|
+
else if (values.has(outcome.value)) {
|
|
383
|
+
errors.push(`${at}: eebus-pair block "${block.id}" wires outcome value "${outcome.value}" more than once.`);
|
|
384
|
+
}
|
|
385
|
+
values.add(outcome.value);
|
|
386
|
+
}
|
|
387
|
+
if (!values.has(enyo_onboarding_v2_js_1.EnyoOnboardingV2EebusPairOutcome.Paired)) {
|
|
388
|
+
warnings.push(`${at}: eebus-pair block "${block.id}" has no "${enyo_onboarding_v2_js_1.EnyoOnboardingV2EebusPairOutcome.Paired}" outcome — ` +
|
|
389
|
+
'a successful pairing would have nowhere to go.');
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Validates the auth blocks of a step.
|
|
394
|
+
*
|
|
395
|
+
* An auth block has exactly one handle, and the **server** decides when it
|
|
396
|
+
* fires — there is no failure branch to author, because a failed login keeps the
|
|
397
|
+
* installer on the step to retry. So the checks are about the handle existing and
|
|
398
|
+
* being routable at all, and about the step not pretending the login is optional:
|
|
399
|
+
* a second decision block next to it would offer a way past a gate the client is
|
|
400
|
+
* not allowed to skip.
|
|
401
|
+
*
|
|
402
|
+
* @param step - The step whose blocks are checked.
|
|
403
|
+
* @param at - Human-readable location prefix for messages.
|
|
404
|
+
* @param errors - Collector for blocking problems.
|
|
405
|
+
* @param warnings - Collector for advisory problems.
|
|
406
|
+
*/
|
|
407
|
+
function validateAuthBlocks(step, at, errors, warnings) {
|
|
408
|
+
const authBlocks = (step.blocks ?? []).filter((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Auth);
|
|
409
|
+
for (const block of authBlocks) {
|
|
410
|
+
if (!block.label?.length)
|
|
411
|
+
errors.push(`${at}: auth block "${block.id}" has no label.`);
|
|
412
|
+
if (!block.outcome?.id) {
|
|
413
|
+
errors.push(`${at}: auth block "${block.id}" has no outcome id — its success handle cannot be routed.`);
|
|
414
|
+
}
|
|
415
|
+
if (!block.outcome?.label?.length) {
|
|
416
|
+
warnings.push(`${at}: auth block "${block.id}" outcome has no label.`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (authBlocks.length > 1) {
|
|
420
|
+
errors.push(`${at}: more than one auth block; a step can hold at most one login.`);
|
|
421
|
+
}
|
|
422
|
+
if (authBlocks.length === 1) {
|
|
423
|
+
const others = (step.blocks ?? []).filter((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Choice ||
|
|
424
|
+
b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action ||
|
|
425
|
+
b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Input);
|
|
426
|
+
if (others.length) {
|
|
427
|
+
warnings.push(`${at}: auth block "${authBlocks[0].id}" shares the step with ${others.length} other decision block(s) — ` +
|
|
428
|
+
'those give the installer a way past a login the server gates.');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Checks {@link EnyoOnboardingV2Guide.requiresNetworkScan} against what the guide
|
|
434
|
+
* actually does.
|
|
435
|
+
*
|
|
436
|
+
* Opting out means "don't search, start here" — right for a device that is never
|
|
437
|
+
* on the LAN (an OCPP wallbox), wrong if the guide then relies on scan results.
|
|
438
|
+
* Both mismatches are warnings, not errors: the flag describes the host's
|
|
439
|
+
* behaviour before the guide runs, and an author may have a reason.
|
|
440
|
+
*
|
|
441
|
+
* @param guide - The guide being validated.
|
|
442
|
+
* @param warnings - Collector for advisory problems.
|
|
443
|
+
*/
|
|
444
|
+
function validateNetworkScanFlag(guide, warnings) {
|
|
445
|
+
if (guide.requiresNetworkScan !== false)
|
|
446
|
+
return;
|
|
447
|
+
const blocks = guide.steps.flatMap((s) => s.blocks ?? []);
|
|
448
|
+
const scansItself = blocks.some((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action &&
|
|
449
|
+
b.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.NetworkScan);
|
|
450
|
+
if (scansItself)
|
|
451
|
+
return;
|
|
452
|
+
const dependsOnDetected = blocks.some((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action &&
|
|
453
|
+
b.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.DeviceTest &&
|
|
454
|
+
(b.deviceSelection ?? enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelection.Detected) !==
|
|
455
|
+
enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelection.Current);
|
|
456
|
+
if (dependsOnDetected) {
|
|
457
|
+
warnings.push('requiresNetworkScan is false, but a device-test block selects from detected devices — ' +
|
|
458
|
+
'nothing was scanned, so it has nothing to test. Use deviceSelection "current", or run a ' +
|
|
459
|
+
'network-scan action inside the guide.');
|
|
460
|
+
}
|
|
461
|
+
const pairsEebus = blocks.some((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action &&
|
|
462
|
+
b.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.EebusPair);
|
|
463
|
+
if (pairsEebus) {
|
|
464
|
+
warnings.push('requiresNetworkScan is false, but an eebus-pair block asks the installer to pick a ' +
|
|
465
|
+
'discovered EEBUS peer — nothing was scanned, so the picker would open on an empty ' +
|
|
466
|
+
'list. Run a network-scan action inside the guide ahead of it.');
|
|
467
|
+
}
|
|
468
|
+
}
|
|
287
469
|
/**
|
|
288
470
|
* The set of routing-handle keys a step must wire exactly once: one per
|
|
289
471
|
* choice option / action outcome / input outcome, or the single `continue`
|
|
@@ -303,6 +485,9 @@ function requiredHandleKeys(step) {
|
|
|
303
485
|
for (const o of b.outcomes)
|
|
304
486
|
keys.add(`outcome:${b.id}:${o.id}`);
|
|
305
487
|
}
|
|
488
|
+
else if (b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Auth && b.outcome?.id) {
|
|
489
|
+
keys.add(`outcome:${b.id}:${b.outcome.id}`);
|
|
490
|
+
}
|
|
306
491
|
}
|
|
307
492
|
if (keys.size === 0)
|
|
308
493
|
keys.add('continue');
|
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
* energy app can fail fast locally before publishing.
|
|
5
5
|
*
|
|
6
6
|
* `errors` block publishing; `warnings` are advisory (e.g. unreachable step, no
|
|
7
|
-
* path to
|
|
8
|
-
* result, or {@link assertValidOnboardingGuideV2} to throw on the
|
|
7
|
+
* path to a completing exit). Use {@link validateOnboardingGuideV2} for the
|
|
8
|
+
* non-throwing result, or {@link assertValidOnboardingGuideV2} to throw on the
|
|
9
|
+
* first failure.
|
|
10
|
+
*
|
|
11
|
+
* A run completes either through a `success` target **or** through the
|
|
12
|
+
* "enyo übernimmt" hand-off (`pause` with reason
|
|
13
|
+
* {@link EnyoOnboardingV2PauseReason.EnyoTodo}) — both count, so a guide that
|
|
14
|
+
* legitimately ends in a hand-off is not nagged about a missing success path.
|
|
9
15
|
*/
|
|
10
16
|
import type { EnyoOnboardingV2Guide } from '../../types/enyo-onboarding-v2.cjs';
|
|
11
17
|
/**
|
|
@@ -31,8 +37,8 @@ export interface OnboardingV2ValidationResult {
|
|
|
31
37
|
}
|
|
32
38
|
/**
|
|
33
39
|
* Structural + plausibility validation of a v2 guide graph. `errors` block
|
|
34
|
-
* publishing; `warnings` are advisory (e.g. unreachable step, no path to
|
|
35
|
-
* success).
|
|
40
|
+
* publishing; `warnings` are advisory (e.g. unreachable step, no path to a
|
|
41
|
+
* completing exit — `success` or the `enyo-todo` hand-off).
|
|
36
42
|
*
|
|
37
43
|
* @param guide - The v2 guide to validate.
|
|
38
44
|
* @returns The {@link OnboardingV2ValidationResult}.
|
|
@@ -117,6 +117,40 @@ class WallboxIntegrationEnergyApp extends integration_energy_app_js_1.Integratio
|
|
|
117
117
|
};
|
|
118
118
|
this.useDataBus().sendMessage([msg]);
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Publishes an `EnergyManagementChargingStateV1` message: the app's own
|
|
122
|
+
* state — the phase count in force, whether a phase switch is possible
|
|
123
|
+
* right now, the limit actually applied and the verdict on the last
|
|
124
|
+
* request — reported to the energy manager.
|
|
125
|
+
*
|
|
126
|
+
* This is a continuously updated state, not an event. Call it whenever
|
|
127
|
+
* anything in the payload changes (a phase switch starts or finishes, a
|
|
128
|
+
* cool-down expires, a new limit is applied) and periodically as a
|
|
129
|
+
* heartbeat, always passing the **complete** picture: an omitted field
|
|
130
|
+
* means "unknown", never "unchanged".
|
|
131
|
+
*
|
|
132
|
+
* Report facts, never advice: the phase count, the applied limit and the
|
|
133
|
+
* power it implies, the request verdict, the measurement timestamp — no
|
|
134
|
+
* recommended minimum power, no suggested setpoint, nothing derived from
|
|
135
|
+
* the energy manager's own plan.
|
|
136
|
+
*
|
|
137
|
+
* @param applianceId - The charger appliance the state describes.
|
|
138
|
+
* @param data - The full charging state; see
|
|
139
|
+
* {@link EnyoDataBusEnergyManagementChargingStateV1}.
|
|
140
|
+
*/
|
|
141
|
+
publishEnergyManagementChargingState(applianceId, data) {
|
|
142
|
+
const msg = {
|
|
143
|
+
id: this.generateMessageId(),
|
|
144
|
+
type: 'message',
|
|
145
|
+
message: enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.EnergyManagementChargingStateV1,
|
|
146
|
+
source: this.source,
|
|
147
|
+
applianceId,
|
|
148
|
+
timestampIso: new Date().toISOString(),
|
|
149
|
+
resolution: 'dynamic',
|
|
150
|
+
data
|
|
151
|
+
};
|
|
152
|
+
this.useDataBus().sendMessage([msg]);
|
|
153
|
+
}
|
|
120
154
|
/**
|
|
121
155
|
* Publishes a `ChargerStatusChangedV1` message reporting the current OCPP
|
|
122
156
|
* status (Available, Preparing, Charging, …).
|