@enyo-energy/energy-app-sdk 0.0.186 → 0.0.188

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.
Files changed (26) hide show
  1. package/README.md +31 -0
  2. package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.cjs +45 -0
  3. package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.d.cts +36 -1
  4. package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.cjs +115 -3
  5. package/dist/cjs/packages/energy-app-charge.d.cts +80 -0
  6. package/dist/cjs/types/enyo-air-conditioning-appliance.cjs +6 -0
  7. package/dist/cjs/types/enyo-air-conditioning-appliance.d.cts +7 -1
  8. package/dist/cjs/types/enyo-heating-rod-appliance.cjs +10 -0
  9. package/dist/cjs/types/enyo-heating-rod-appliance.d.cts +11 -1
  10. package/dist/cjs/types/enyo-onboarding-v2.cjs +17 -2
  11. package/dist/cjs/types/enyo-onboarding-v2.d.cts +142 -6
  12. package/dist/cjs/version.cjs +1 -1
  13. package/dist/cjs/version.d.cts +1 -1
  14. package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.d.ts +36 -1
  15. package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.js +45 -0
  16. package/dist/implementations/onboarding-v2/onboarding-v2-validators.js +116 -4
  17. package/dist/packages/energy-app-charge.d.ts +80 -0
  18. package/dist/types/enyo-air-conditioning-appliance.d.ts +7 -1
  19. package/dist/types/enyo-air-conditioning-appliance.js +6 -0
  20. package/dist/types/enyo-heating-rod-appliance.d.ts +11 -1
  21. package/dist/types/enyo-heating-rod-appliance.js +10 -0
  22. package/dist/types/enyo-onboarding-v2.d.ts +142 -6
  23. package/dist/types/enyo-onboarding-v2.js +16 -1
  24. package/dist/version.d.ts +1 -1
  25. package/dist/version.js +1 -1
  26. package/package.json +1 -1
package/README.md CHANGED
@@ -724,6 +724,37 @@ const sessions = await charging.getActiveSessions();
724
724
  await charging.stopCharge(sessionId);
725
725
  ```
726
726
 
727
+ React to charging sessions as they happen:
728
+
729
+ ```typescript
730
+ const charge = energyApp.useCharge();
731
+
732
+ const startedId = charge.listenForChargeStarted((session) => {
733
+ console.log(`charging started on ${session.applianceId}`);
734
+ });
735
+
736
+ const updatedId = charge.listenForChargeUpdated((session) => {
737
+ const latest = session.meterValues?.at(-1);
738
+ console.log(`now at ${latest?.valueWh} Wh`);
739
+ });
740
+
741
+ const stoppedId = charge.listenForChargeStopped((session) => {
742
+ if (session.status === EnyoChargeStatus.Completed) {
743
+ console.log(`delivered ${session.totalEnergyKwh} kWh`);
744
+ }
745
+ });
746
+
747
+ // later
748
+ charge.removeListener(startedId);
749
+ ```
750
+
751
+ The three events are disjoint: `listenForChargeUpdated` fires only for changes to a
752
+ *running* session (meter values, charge mode, smart-charging schedule, additional
753
+ transaction IDs), never for the start or the end, so a handler never sees the same
754
+ event twice. Check `session.status` in the stopped listener — `Completed` and
755
+ `Failed` both end a charge. Meter updates can arrive every few seconds, so keep
756
+ the update callback cheap.
757
+
727
758
  #### `useChargingCard(): EnergyAppChargingCard`
728
759
 
729
760
  Handle charging authentication:
@@ -133,6 +133,51 @@ exports.onboardingV2Block = {
133
133
  outcomes,
134
134
  deviceSelection,
135
135
  }),
136
+ /**
137
+ * A link block: a fixed URL the installer opens or copies.
138
+ *
139
+ * Passive content — it produces no routing handle, so a step whose only
140
+ * non-content block is a link still routes through `continue`.
141
+ *
142
+ * @param id - Stable block id, unique within the guide.
143
+ * @param url - Absolute `http(s)` URL. Other schemes are rejected by the validator.
144
+ * @param label - Translated link text (de/en).
145
+ * @param opts - Optional translated `description` and `copyable` (defaults to `true`).
146
+ */
147
+ link: (id, url, label, opts) => ({
148
+ id,
149
+ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Link,
150
+ url,
151
+ label,
152
+ description: opts?.description,
153
+ copyable: opts?.copyable ?? true,
154
+ }),
155
+ /**
156
+ * An input block: the installer types a value and the host checks it,
157
+ * producing the branch.
158
+ *
159
+ * For {@link EnyoOnboardingV2InputValueType.IpAddress} the host runs this
160
+ * app's registered device-test handler against the typed address; see
161
+ * {@link EnyoOnboardingV2InputBlock} for how a verdict picks an outcome.
162
+ * Route the outcomes with {@link onOutcomeV2} — there is no separate helper.
163
+ *
164
+ * @param id - Stable block id, unique within the guide.
165
+ * @param valueType - What is asked for (`Text` | `IpAddress` | `Number`).
166
+ * @param label - Translated field label (de/en).
167
+ * @param submitLabel - Translated submit button text (de/en).
168
+ * @param outcomes - The possible verdicts; each is a routing handle.
169
+ * @param opts - Optional translated `placeholder` and `help`.
170
+ */
171
+ input: (id, valueType, label, submitLabel, outcomes, opts) => ({
172
+ id,
173
+ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Input,
174
+ valueType,
175
+ label,
176
+ submitLabel,
177
+ outcomes,
178
+ placeholder: opts?.placeholder,
179
+ help: opts?.help,
180
+ }),
136
181
  };
137
182
  // ---------------------------------------------------------------------------
138
183
  // Target factories
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import type { EnyoOnboardingTranslatedContent } from '../../types/enyo-onboarding.cjs';
13
13
  import { EnyoOnboardingV2ActionKind, EnyoOnboardingV2ChoiceLayout, EnyoOnboardingV2DeviceSelection } from '../../types/enyo-onboarding-v2.cjs';
14
- import type { EnyoOnboardingV2ActionOutcome, EnyoOnboardingV2Block, EnyoOnboardingV2ChoiceOption, EnyoOnboardingV2DynamicKind, EnyoOnboardingV2Guide, EnyoOnboardingV2HintVariant, EnyoOnboardingV2PauseReason, EnyoOnboardingV2StartVariant, EnyoOnboardingV2Target, EnyoOnboardingV2Transition } from '../../types/enyo-onboarding-v2.cjs';
14
+ import type { EnyoOnboardingV2ActionOutcome, EnyoOnboardingV2Block, EnyoOnboardingV2ChoiceOption, EnyoOnboardingV2DynamicKind, EnyoOnboardingV2Guide, EnyoOnboardingV2HintVariant, EnyoOnboardingV2InputOutcome, EnyoOnboardingV2InputValueType, EnyoOnboardingV2PauseReason, 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,41 @@ 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
+ * A link block: a fixed URL the installer opens or copies.
103
+ *
104
+ * Passive content — it produces no routing handle, so a step whose only
105
+ * non-content block is a link still routes through `continue`.
106
+ *
107
+ * @param id - Stable block id, unique within the guide.
108
+ * @param url - Absolute `http(s)` URL. Other schemes are rejected by the validator.
109
+ * @param label - Translated link text (de/en).
110
+ * @param opts - Optional translated `description` and `copyable` (defaults to `true`).
111
+ */
112
+ link: (id: string, url: string, label: EnyoOnboardingTranslatedContent[], opts?: {
113
+ description?: EnyoOnboardingTranslatedContent[];
114
+ copyable?: boolean;
115
+ }) => EnyoOnboardingV2Block;
116
+ /**
117
+ * An input block: the installer types a value and the host checks it,
118
+ * producing the branch.
119
+ *
120
+ * For {@link EnyoOnboardingV2InputValueType.IpAddress} the host runs this
121
+ * app's registered device-test handler against the typed address; see
122
+ * {@link EnyoOnboardingV2InputBlock} for how a verdict picks an outcome.
123
+ * Route the outcomes with {@link onOutcomeV2} — there is no separate helper.
124
+ *
125
+ * @param id - Stable block id, unique within the guide.
126
+ * @param valueType - What is asked for (`Text` | `IpAddress` | `Number`).
127
+ * @param label - Translated field label (de/en).
128
+ * @param submitLabel - Translated submit button text (de/en).
129
+ * @param outcomes - The possible verdicts; each is a routing handle.
130
+ * @param opts - Optional translated `placeholder` and `help`.
131
+ */
132
+ input: (id: string, valueType: EnyoOnboardingV2InputValueType, label: EnyoOnboardingTranslatedContent[], submitLabel: EnyoOnboardingTranslatedContent[], outcomes: EnyoOnboardingV2InputOutcome[], opts?: {
133
+ placeholder?: EnyoOnboardingTranslatedContent[];
134
+ help?: EnyoOnboardingTranslatedContent[];
135
+ }) => EnyoOnboardingV2Block;
101
136
  };
102
137
  /** Typed factories for each transition {@link EnyoOnboardingV2Target}. */
103
138
  export declare const onboardingV2Target: {
@@ -70,6 +70,8 @@ function validateOnboardingGuideV2(guide) {
70
70
  if (!step.blocks?.length)
71
71
  warnings.push(`${at}: no content blocks.`);
72
72
  validateActionBlocks(step, at, errors, warnings);
73
+ validateLinkBlocks(step, at, errors, warnings);
74
+ validateInputBlocks(step, at, errors, warnings);
73
75
  }
74
76
  if (!guide.startStepId || !stepIds.has(guide.startStepId)) {
75
77
  errors.push('`startStepId` must reference an existing step.');
@@ -175,10 +177,119 @@ function validateActionBlocks(step, at, errors, warnings) {
175
177
  }
176
178
  }
177
179
  }
180
+ /** An absolute `http(s)` URL — the only scheme a link block may carry. */
181
+ const HTTP_URL_RE = /^https?:\/\/\S+$/i;
182
+ /**
183
+ * Validates the link blocks of a step.
184
+ *
185
+ * The scheme check is **security-relevant, not cosmetic**: the installer app
186
+ * renders a link block as a tap target, so this keeps `javascript:`, `data:` and
187
+ * `file:` payloads out of it. connect-core enforces the identical rule
188
+ * server-side and the app re-checks before opening — three gates, deliberately.
189
+ * Do not relax it to a generic "looks like a URL" test.
190
+ *
191
+ * @param step - The step whose blocks are checked.
192
+ * @param at - Human-readable location prefix for messages.
193
+ * @param errors - Collector for blocking problems.
194
+ * @param warnings - Collector for advisory problems.
195
+ */
196
+ function validateLinkBlocks(step, at, errors, warnings) {
197
+ for (const block of step.blocks ?? []) {
198
+ if (block.type !== enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Link)
199
+ continue;
200
+ if (!block.url?.trim()) {
201
+ errors.push(`${at}: link block "${block.id}" has no url.`);
202
+ }
203
+ else if (!HTTP_URL_RE.test(block.url.trim())) {
204
+ errors.push(`${at}: link block "${block.id}" url must be an absolute http(s) URL.`);
205
+ }
206
+ if (!block.label?.length) {
207
+ warnings.push(`${at}: link block "${block.id}" has no label — the raw URL is shown instead.`);
208
+ }
209
+ }
210
+ }
211
+ /** Every valid {@link EnyoOnboardingV2InputValueType} value. */
212
+ const INPUT_VALUE_TYPES = new Set(Object.values(enyo_onboarding_v2_js_1.EnyoOnboardingV2InputValueType));
213
+ /** Outcome values the host treats as "the check succeeded". */
214
+ const POSITIVE_INPUT_OUTCOMES = new Set(['reachable', 'success', 'found']);
215
+ /**
216
+ * The {@link EnyoDeviceTestOutcomeEnum} verdicts that collapse onto a positive
217
+ * input outcome; every other verdict collapses onto a negative one.
218
+ */
219
+ const POSITIVE_DEVICE_TEST_OUTCOMES = new Set([
220
+ enyo_device_test_js_1.EnyoDeviceTestOutcomeEnum.AppliancesCreated,
221
+ enyo_device_test_js_1.EnyoDeviceTestOutcomeEnum.AppliancesAlreadyExisted,
222
+ enyo_device_test_js_1.EnyoDeviceTestOutcomeEnum.DeviceConfirmedNoAppliance,
223
+ ]);
224
+ /** True when `value` routes a successful check (exact verdict or binary key). */
225
+ function isPositiveInputOutcomeValue(value) {
226
+ return POSITIVE_INPUT_OUTCOMES.has(value) || POSITIVE_DEVICE_TEST_OUTCOMES.has(value);
227
+ }
228
+ /**
229
+ * Validates the input blocks of a step.
230
+ *
231
+ * An {@link EnyoOnboardingV2InputValueType.IpAddress} block is checked more
232
+ * strictly than the other value types because it is the only one the host
233
+ * actually runs a check for: a one-sided outcome set there is a step the
234
+ * installer can enter and never leave, so both "no positive" and "no negative"
235
+ * are errors rather than warnings. Unlike a device test there is no `failed`
236
+ * verdict the host can fall back to.
237
+ *
238
+ * @param step - The step whose blocks are checked.
239
+ * @param at - Human-readable location prefix for messages.
240
+ * @param errors - Collector for blocking problems.
241
+ * @param warnings - Collector for advisory problems.
242
+ */
243
+ function validateInputBlocks(step, at, errors, warnings) {
244
+ for (const block of step.blocks ?? []) {
245
+ if (block.type !== enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Input)
246
+ continue;
247
+ if (!INPUT_VALUE_TYPES.has(block.valueType)) {
248
+ errors.push(`${at}: input block "${block.id}" has an unknown valueType "${block.valueType}".`);
249
+ }
250
+ if (!block.label?.length)
251
+ errors.push(`${at}: input block "${block.id}" has no label.`);
252
+ if (!block.submitLabel?.length) {
253
+ errors.push(`${at}: input block "${block.id}" has no submitLabel.`);
254
+ }
255
+ const outcomes = block.outcomes ?? [];
256
+ if (outcomes.length < 2) {
257
+ errors.push(`${at}: input block "${block.id}" needs at least 2 outcomes.`);
258
+ }
259
+ const ids = new Set();
260
+ const values = new Set();
261
+ for (const outcome of outcomes) {
262
+ if (ids.has(outcome.id)) {
263
+ errors.push(`${at}: input block "${block.id}" has a duplicate outcome id "${outcome.id}".`);
264
+ }
265
+ ids.add(outcome.id);
266
+ if (values.has(outcome.value)) {
267
+ errors.push(`${at}: input block "${block.id}" wires outcome value "${outcome.value}" more than once.`);
268
+ }
269
+ values.add(outcome.value);
270
+ }
271
+ if (block.valueType === enyo_onboarding_v2_js_1.EnyoOnboardingV2InputValueType.IpAddress) {
272
+ if (![...values].some(isPositiveInputOutcomeValue)) {
273
+ errors.push(`${at}: input block "${block.id}" has no outcome for a successful check — ` +
274
+ `wire "reachable" (or an EnyoDeviceTestOutcomeEnum success member).`);
275
+ }
276
+ if (![...values].some((v) => !isPositiveInputOutcomeValue(v))) {
277
+ errors.push(`${at}: input block "${block.id}" has no outcome for a failed check — ` +
278
+ `the installer would be stranded when the device does not answer.`);
279
+ }
280
+ }
281
+ else if (outcomes.length > 1) {
282
+ warnings.push(`${at}: input block "${block.id}" has valueType "${block.valueType}", which runs no check — ` +
283
+ `only the positive outcome can ever fire, so its other ${outcomes.length - 1} outcome(s) are dead branches.`);
284
+ }
285
+ }
286
+ }
178
287
  /**
179
288
  * The set of routing-handle keys a step must wire exactly once: one per
180
- * choice option / action outcome, or the single `continue` handle when the step
181
- * has no interactive block.
289
+ * choice option / action outcome / input outcome, or the single `continue`
290
+ * handle when the step has no interactive block.
291
+ *
292
+ * A `Link` block contributes nothing — it is passive content.
182
293
  */
183
294
  function requiredHandleKeys(step) {
184
295
  const keys = new Set();
@@ -187,7 +298,8 @@ function requiredHandleKeys(step) {
187
298
  for (const o of b.options)
188
299
  keys.add(`choice:${b.id}:${o.id}`);
189
300
  }
190
- else if (b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action) {
301
+ else if (b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action ||
302
+ b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Input) {
191
303
  for (const o of b.outcomes)
192
304
  keys.add(`outcome:${b.id}:${o.id}`);
193
305
  }
@@ -52,4 +52,84 @@ export interface EnergyAppCharge {
52
52
  * @returns Promise resolving to the configured default charge mode
53
53
  */
54
54
  getDefaultChargeMode: (applianceId: string) => Promise<EnyoDefaultChargeMode>;
55
+ /**
56
+ * Listen for charging sessions that have just started.
57
+ *
58
+ * Fires once per session, when the charge is first created and enters
59
+ * {@link EnyoChargeStatus.Charging} — not on subsequent meter updates (use
60
+ * {@link listenForChargeUpdated} for those). The delivered charge carries the
61
+ * data known at start: `applianceId`, `transactionId`, `startTime` and
62
+ * `meterStartValueWh`; the totals are only meaningful once the session ends.
63
+ *
64
+ * Listeners receive sessions of every appliance the package can see — filter
65
+ * on {@link EnyoCharge.applianceId} when only one charger is of interest.
66
+ *
67
+ * @param listener - Callback invoked with the started charging session
68
+ * @returns A unique listener ID that can be used to remove the listener
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const charge = energyApp.useCharge();
73
+ * const listenerId = charge.listenForChargeStarted(async (session) => {
74
+ * console.log(`charging started on ${session.applianceId}`);
75
+ * });
76
+ * ```
77
+ */
78
+ listenForChargeStarted: (listener: (charge: EnyoCharge) => void | Promise<void>) => string;
79
+ /**
80
+ * Listen for charging sessions that have ended.
81
+ *
82
+ * Fires once per session, when it leaves {@link EnyoChargeStatus.Charging}
83
+ * for a terminal status. Check {@link EnyoCharge.status} to tell a session
84
+ * that {@link EnyoChargeStatus.Completed completed} from one that
85
+ * {@link EnyoChargeStatus.Failed failed} — both end a charge, and a listener
86
+ * that assumes success will mis-report faulted sessions.
87
+ *
88
+ * The delivered charge is the final record, including `endTime`,
89
+ * `meterEndValueWh` and `totalEnergyKwh`.
90
+ *
91
+ * @param listener - Callback invoked with the ended charging session
92
+ * @returns A unique listener ID that can be used to remove the listener
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * charge.listenForChargeStopped(async (session) => {
97
+ * if (session.status === EnyoChargeStatus.Completed) {
98
+ * console.log(`delivered ${session.totalEnergyKwh} kWh`);
99
+ * }
100
+ * });
101
+ * ```
102
+ */
103
+ listenForChargeStopped: (listener: (charge: EnyoCharge) => void | Promise<void>) => string;
104
+ /**
105
+ * Listen for changes to a running charging session.
106
+ *
107
+ * Fires whenever an active charge is modified — new meter values, a changed
108
+ * {@link EnyoCharge.chargeMode}, an updated smart-charging
109
+ * {@link EnyoCharge.schedule}, or an additional transaction ID. It does NOT
110
+ * fire for the start and the end of a session; those have their own
111
+ * listeners, so a handler registered here never sees the same event twice.
112
+ *
113
+ * Meter updates can arrive at the charger's reporting interval (often every
114
+ * few seconds), so keep the callback cheap and do not persist on every call.
115
+ *
116
+ * @param listener - Callback invoked with the updated charging session
117
+ * @returns A unique listener ID that can be used to remove the listener
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * charge.listenForChargeUpdated(async (session) => {
122
+ * const latest = session.meterValues?.at(-1);
123
+ * console.log(`now at ${latest?.valueWh} Wh`);
124
+ * });
125
+ * ```
126
+ */
127
+ listenForChargeUpdated: (listener: (charge: EnyoCharge) => void | Promise<void>) => string;
128
+ /**
129
+ * Removes a previously registered listener.
130
+ *
131
+ * @param listenerId - The ID returned by {@link listenForChargeStarted},
132
+ * {@link listenForChargeStopped} or {@link listenForChargeUpdated}
133
+ */
134
+ removeListener: (listenerId: string) => void;
55
135
  }
@@ -10,6 +10,12 @@ var EnyoAirConditioningApplianceAvailableFeaturesEnum;
10
10
  EnyoAirConditioningApplianceAvailableFeaturesEnum["Cooling"] = "Cooling";
11
11
  /** If the air conditioning unit supports heating */
12
12
  EnyoAirConditioningApplianceAvailableFeaturesEnum["Heating"] = "Heating";
13
+ /** If the air conditioning unit reports electrical power values (e.g. consumption in watts) */
14
+ EnyoAirConditioningApplianceAvailableFeaturesEnum["Power"] = "Power";
15
+ /** If the air conditioning unit's electrical power draw can be steered (e.g. limited to a target in watts) */
16
+ EnyoAirConditioningApplianceAvailableFeaturesEnum["PowerControllable"] = "PowerControllable";
17
+ /** If the air conditioning unit's target room temperature can be set */
18
+ EnyoAirConditioningApplianceAvailableFeaturesEnum["TemperatureControllable"] = "TemperatureControllable";
13
19
  })(EnyoAirConditioningApplianceAvailableFeaturesEnum || (exports.EnyoAirConditioningApplianceAvailableFeaturesEnum = EnyoAirConditioningApplianceAvailableFeaturesEnum = {}));
14
20
  /**
15
21
  * Energy-optimization modes for an air conditioning appliance. These control how
@@ -5,7 +5,13 @@ export declare enum EnyoAirConditioningApplianceAvailableFeaturesEnum {
5
5
  /** If the air conditioning unit supports cooling */
6
6
  Cooling = "Cooling",
7
7
  /** If the air conditioning unit supports heating */
8
- Heating = "Heating"
8
+ Heating = "Heating",
9
+ /** If the air conditioning unit reports electrical power values (e.g. consumption in watts) */
10
+ Power = "Power",
11
+ /** If the air conditioning unit's electrical power draw can be steered (e.g. limited to a target in watts) */
12
+ PowerControllable = "PowerControllable",
13
+ /** If the air conditioning unit's target room temperature can be set */
14
+ TemperatureControllable = "TemperatureControllable"
9
15
  }
10
16
  /**
11
17
  * Energy-optimization modes for an air conditioning appliance. These control how
@@ -11,6 +11,16 @@ var EnyoHeatingRodApplianceAvailableFeaturesEnum;
11
11
  EnyoHeatingRodApplianceAvailableFeaturesEnum["Power"] = "Power";
12
12
  /** If the heating rod supports available power announcements */
13
13
  EnyoHeatingRodApplianceAvailableFeaturesEnum["AvailablePowerAnnouncement"] = "AvailablePowerAnnouncement";
14
+ /**
15
+ * If the heating rod has a domestic hot water temperature sensor, i.e. it
16
+ * reports the measured DHW tank temperature rather than only its own
17
+ * operating state.
18
+ *
19
+ * Without this feature {@link EnyoHeatingRodApplianceMetadata.targetTemperatureC}
20
+ * is a setpoint the appliance cannot verify — consumers should not expect a
21
+ * measured temperature to compare it against.
22
+ */
23
+ EnyoHeatingRodApplianceAvailableFeaturesEnum["DomesticHotWaterSensor"] = "DomesticHotWaterSensor";
14
24
  })(EnyoHeatingRodApplianceAvailableFeaturesEnum || (exports.EnyoHeatingRodApplianceAvailableFeaturesEnum = EnyoHeatingRodApplianceAvailableFeaturesEnum = {}));
15
25
  /**
16
26
  * Operating modes for a heating rod appliance.
@@ -6,7 +6,17 @@ export declare enum EnyoHeatingRodApplianceAvailableFeaturesEnum {
6
6
  /** If the heating rod reports electrical power values (e.g. consumption in watts) */
7
7
  Power = "Power",
8
8
  /** If the heating rod supports available power announcements */
9
- AvailablePowerAnnouncement = "AvailablePowerAnnouncement"
9
+ AvailablePowerAnnouncement = "AvailablePowerAnnouncement",
10
+ /**
11
+ * If the heating rod has a domestic hot water temperature sensor, i.e. it
12
+ * reports the measured DHW tank temperature rather than only its own
13
+ * operating state.
14
+ *
15
+ * Without this feature {@link EnyoHeatingRodApplianceMetadata.targetTemperatureC}
16
+ * is a setpoint the appliance cannot verify — consumers should not expect a
17
+ * measured temperature to compare it against.
18
+ */
19
+ DomesticHotWaterSensor = "DomesticHotWaterSensor"
10
20
  }
11
21
  /**
12
22
  * Operating modes for a heating rod appliance.
@@ -22,7 +22,7 @@
22
22
  * before publishing.
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.EnyoOnboardingV2TargetType = exports.EnyoOnboardingV2TransitionSourceKind = exports.EnyoOnboardingV2BlockType = exports.EnyoOnboardingV2ChoiceLayout = exports.EnyoOnboardingV2IconKey = exports.EnyoOnboardingV2DeviceSelection = exports.EnyoOnboardingV2ActionKind = exports.EnyoOnboardingV2DynamicKind = exports.EnyoOnboardingV2HintVariant = exports.EnyoOnboardingV2PauseReason = exports.EnyoOnboardingV2StartVariant = void 0;
25
+ exports.EnyoOnboardingV2TargetType = exports.EnyoOnboardingV2TransitionSourceKind = exports.EnyoOnboardingV2BlockType = exports.EnyoOnboardingV2ChoiceLayout = exports.EnyoOnboardingV2IconKey = exports.EnyoOnboardingV2DeviceSelection = exports.EnyoOnboardingV2InputValueType = exports.EnyoOnboardingV2ActionKind = exports.EnyoOnboardingV2DynamicKind = exports.EnyoOnboardingV2HintVariant = exports.EnyoOnboardingV2PauseReason = exports.EnyoOnboardingV2StartVariant = void 0;
26
26
  // ---------------------------------------------------------------------------
27
27
  // Enumerable string enums
28
28
  // ---------------------------------------------------------------------------
@@ -88,6 +88,19 @@ var EnyoOnboardingV2ActionKind;
88
88
  */
89
89
  EnyoOnboardingV2ActionKind["DeviceTest"] = "device-test";
90
90
  })(EnyoOnboardingV2ActionKind || (exports.EnyoOnboardingV2ActionKind = EnyoOnboardingV2ActionKind = {}));
91
+ /**
92
+ * What an {@link EnyoOnboardingV2InputBlock} asks the installer for. Drives the
93
+ * keyboard the app shows, the format check it applies and the seeded texts.
94
+ */
95
+ var EnyoOnboardingV2InputValueType;
96
+ (function (EnyoOnboardingV2InputValueType) {
97
+ /** Free text; any non-empty value is accepted. */
98
+ EnyoOnboardingV2InputValueType["Text"] = "text";
99
+ /** An IPv4 address. This is the only type the host actually checks. */
100
+ EnyoOnboardingV2InputValueType["IpAddress"] = "ip-address";
101
+ /** A number; `,` and `.` are both accepted as the decimal separator. */
102
+ EnyoOnboardingV2InputValueType["Number"] = "number";
103
+ })(EnyoOnboardingV2InputValueType || (exports.EnyoOnboardingV2InputValueType = EnyoOnboardingV2InputValueType = {}));
91
104
  /**
92
105
  * Which devices a {@link EnyoOnboardingV2ActionKind.DeviceTest} block hands to
93
106
  * the app.
@@ -129,6 +142,8 @@ var EnyoOnboardingV2BlockType;
129
142
  EnyoOnboardingV2BlockType["Dynamic"] = "dynamic";
130
143
  EnyoOnboardingV2BlockType["Choice"] = "choice";
131
144
  EnyoOnboardingV2BlockType["Action"] = "action";
145
+ EnyoOnboardingV2BlockType["Link"] = "link";
146
+ EnyoOnboardingV2BlockType["Input"] = "input";
132
147
  })(EnyoOnboardingV2BlockType || (exports.EnyoOnboardingV2BlockType = EnyoOnboardingV2BlockType = {}));
133
148
  // ---------------------------------------------------------------------------
134
149
  // Routing: transitions & targets
@@ -140,7 +155,7 @@ var EnyoOnboardingV2TransitionSourceKind;
140
155
  EnyoOnboardingV2TransitionSourceKind["Continue"] = "continue";
141
156
  /** A specific option of a `Choice` block was picked. */
142
157
  EnyoOnboardingV2TransitionSourceKind["Choice"] = "choice";
143
- /** A specific outcome of an `Action` block fired. */
158
+ /** A specific outcome of an `Action` or `Input` block fired. */
144
159
  EnyoOnboardingV2TransitionSourceKind["Outcome"] = "outcome";
145
160
  })(EnyoOnboardingV2TransitionSourceKind || (exports.EnyoOnboardingV2TransitionSourceKind = EnyoOnboardingV2TransitionSourceKind = {}));
146
161
  /** Discriminator for the {@link EnyoOnboardingV2Target} union. */
@@ -78,6 +78,18 @@ export declare enum EnyoOnboardingV2ActionKind {
78
78
  */
79
79
  DeviceTest = "device-test"
80
80
  }
81
+ /**
82
+ * What an {@link EnyoOnboardingV2InputBlock} asks the installer for. Drives the
83
+ * keyboard the app shows, the format check it applies and the seeded texts.
84
+ */
85
+ export declare enum EnyoOnboardingV2InputValueType {
86
+ /** Free text; any non-empty value is accepted. */
87
+ Text = "text",
88
+ /** An IPv4 address. This is the only type the host actually checks. */
89
+ IpAddress = "ip-address",
90
+ /** A number; `,` and `.` are both accepted as the decimal separator. */
91
+ Number = "number"
92
+ }
81
93
  /**
82
94
  * Which devices a {@link EnyoOnboardingV2ActionKind.DeviceTest} block hands to
83
95
  * the app.
@@ -111,7 +123,9 @@ export declare enum EnyoOnboardingV2BlockType {
111
123
  Hint = "hint",
112
124
  Dynamic = "dynamic",
113
125
  Choice = "choice",
114
- Action = "action"
126
+ Action = "action",
127
+ Link = "link",
128
+ Input = "input"
115
129
  }
116
130
  /** Fields shared by every content/interactive block. */
117
131
  export interface EnyoOnboardingV2BlockBase {
@@ -209,17 +223,139 @@ export interface EnyoOnboardingV2ActionBlock extends EnyoOnboardingV2BlockBase {
209
223
  */
210
224
  deviceSelection?: EnyoOnboardingV2DeviceSelection;
211
225
  }
226
+ /**
227
+ * A fixed URL the installer opens or copies — a vendor portal, a manual, a
228
+ * firmware download.
229
+ *
230
+ * Passive content: it produces no routing handle, so a step whose only
231
+ * non-content block is a link still routes through its single `continue`
232
+ * handle. Unlike {@link EnyoOnboardingV2DynamicBlock}, the URL is the same for
233
+ * every installer — nothing is resolved per device.
234
+ */
235
+ export interface EnyoOnboardingV2LinkBlock extends EnyoOnboardingV2BlockBase {
236
+ type: EnyoOnboardingV2BlockType.Link;
237
+ /**
238
+ * Absolute `http(s)` URL, not translated. Any other scheme is rejected by
239
+ * the validator and by the host: the installer app renders this as a tap
240
+ * target.
241
+ */
242
+ url: string;
243
+ /** Translated link text (de/en). Falls back to the raw URL when empty. */
244
+ label: EnyoOnboardingTranslatedContent[];
245
+ /** Optional translated context line below the link (de/en). */
246
+ description?: EnyoOnboardingTranslatedContent[];
247
+ /**
248
+ * Show a copy-to-clipboard button. Defaults to `true` — on a phone it is the
249
+ * more useful affordance of the two.
250
+ */
251
+ copyable?: boolean;
252
+ }
253
+ /**
254
+ * A possible verdict of an {@link EnyoOnboardingV2InputBlock}'s check; each
255
+ * outcome is a routing handle.
256
+ */
257
+ export interface EnyoOnboardingV2InputOutcome {
258
+ /** Stable id, unique within the block; referenced by a transition. */
259
+ id: string;
260
+ /**
261
+ * Semantic key, not translated. Either an {@link EnyoDeviceTestOutcomeEnum}
262
+ * member (routed exactly) or one of the binary keys the host collapses
263
+ * onto — `reachable` / `unreachable`. See {@link EnyoOnboardingV2InputBlock}
264
+ * for the full mapping.
265
+ */
266
+ value: string;
267
+ /** Translated display label for the verdict (de/en). */
268
+ label: EnyoOnboardingTranslatedContent[];
269
+ }
270
+ /**
271
+ * The installer types a value and the **host** checks it, producing the branch —
272
+ * unlike {@link EnyoOnboardingV2ChoiceBlock}, where the installer picks the
273
+ * branch directly.
274
+ *
275
+ * For {@link EnyoOnboardingV2InputValueType.IpAddress} the host pairs the typed
276
+ * address with this energy app (creating the network device when the scan never
277
+ * discovered it) and runs the app's registered {@link EnyoDeviceTestHandler}
278
+ * against it, so the verdict is the app's own — it confirms *your* device, not
279
+ * merely a live host. `Text` and `Number` are recorded and take the positive
280
+ * branch; there is nothing the host could check about them.
281
+ *
282
+ * Each outcome MUST have exactly one outgoing transition.
283
+ *
284
+ * **How a verdict picks an outcome**
285
+ *
286
+ * 1. *Exact match wins.* An outcome whose `value` is an
287
+ * {@link EnyoDeviceTestOutcomeEnum} member receives that verdict verbatim —
288
+ * so a guide can route `authentication-required` to a credentials step and
289
+ * `user-action-required` to a "press the pairing button" step.
290
+ * 2. *Otherwise it collapses to a binary pair.* `appliances-created`,
291
+ * `appliances-already-existed` and `device-confirmed-no-appliance` go to the
292
+ * first outcome valued `reachable` / `success` / `found`; everything else
293
+ * (`unreachable`, `not-supported`, `authentication-required`,
294
+ * `access-not-granted`, `user-action-required`, `failed`) goes to the first
295
+ * outcome valued `unreachable` / `failure` / `failed` / `not-found`.
296
+ * 3. `Text` and `Number` run no check: the value is recorded and the flow takes
297
+ * the positive outcome (first `reachable`/`success`/`found`, else the first
298
+ * outcome). Any further outcome on such a block can never fire.
299
+ * 4. An offline hub, a package that is not installed, or no energy app linked to
300
+ * the vendor/model all resolve to the **negative** branch — never an error.
301
+ * The guide always gets an outcome to route on.
302
+ *
303
+ * The typed value is persisted per block in the run state and handed back on
304
+ * resume/back, so a typo is corrected rather than retyped. Values may be secrets
305
+ * (a device password typed into a `Text` input): the host logs outcome keys
306
+ * only, never the value itself.
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * onboardingV2Block.input(
311
+ * 'b3',
312
+ * EnyoOnboardingV2InputValueType.IpAddress,
313
+ * t('IP-Adresse des Geräts', 'Device IP address'),
314
+ * t('Gerät prüfen', 'Check device'),
315
+ * [
316
+ * {id: 'ok', value: 'reachable', label: t('Gerät erreichbar', 'Device reachable')},
317
+ * {
318
+ * id: 'auth',
319
+ * value: EnyoDeviceTestOutcomeEnum.AuthenticationRequired,
320
+ * label: t('Passwort nötig', 'Password required'),
321
+ * },
322
+ * {id: 'no', value: 'unreachable', label: t('Nicht erreichbar', 'Not reachable')},
323
+ * ],
324
+ * {placeholder: t('z. B. 192.168.1.42', 'e.g. 192.168.1.42')},
325
+ * );
326
+ * ```
327
+ */
328
+ export interface EnyoOnboardingV2InputBlock extends EnyoOnboardingV2BlockBase {
329
+ type: EnyoOnboardingV2BlockType.Input;
330
+ /** What is asked for; drives keyboard, format check and seeded texts. */
331
+ valueType: EnyoOnboardingV2InputValueType;
332
+ /** Translated field label, e.g. "IP-Adresse des Geräts" (de/en). */
333
+ label: EnyoOnboardingTranslatedContent[];
334
+ /** Optional translated placeholder, e.g. "z. B. 192.168.1.42" (de/en). */
335
+ placeholder?: EnyoOnboardingTranslatedContent[];
336
+ /** Optional translated help text — where the installer finds the value (de/en). */
337
+ help?: EnyoOnboardingTranslatedContent[];
338
+ /** Translated submit button text, e.g. "Gerät prüfen" (de/en). */
339
+ submitLabel: EnyoOnboardingTranslatedContent[];
340
+ /** The possible verdicts; each is a routing handle. At least 2. */
341
+ outcomes: EnyoOnboardingV2InputOutcome[];
342
+ }
212
343
  /** Any block that can appear in a step's `blocks`. */
213
- export type EnyoOnboardingV2Block = EnyoOnboardingV2TextBlock | EnyoOnboardingV2HeadlineBlock | EnyoOnboardingV2BulletsBlock | EnyoOnboardingV2ImageBlock | EnyoOnboardingV2HintBlock | EnyoOnboardingV2DynamicBlock | EnyoOnboardingV2ChoiceBlock | EnyoOnboardingV2ActionBlock;
214
- /** Blocks that produce routing handles (a step's decision points). */
215
- export type EnyoOnboardingV2InteractiveBlock = EnyoOnboardingV2ChoiceBlock | EnyoOnboardingV2ActionBlock;
344
+ export type EnyoOnboardingV2Block = EnyoOnboardingV2TextBlock | EnyoOnboardingV2HeadlineBlock | EnyoOnboardingV2BulletsBlock | EnyoOnboardingV2ImageBlock | EnyoOnboardingV2HintBlock | EnyoOnboardingV2DynamicBlock | EnyoOnboardingV2ChoiceBlock | EnyoOnboardingV2ActionBlock | EnyoOnboardingV2LinkBlock | EnyoOnboardingV2InputBlock;
345
+ /**
346
+ * Blocks that produce routing handles (a step's decision points).
347
+ *
348
+ * A {@link EnyoOnboardingV2LinkBlock} is deliberately absent: it is passive
349
+ * content and routes nothing.
350
+ */
351
+ export type EnyoOnboardingV2InteractiveBlock = EnyoOnboardingV2ChoiceBlock | EnyoOnboardingV2ActionBlock | EnyoOnboardingV2InputBlock;
216
352
  /** Discriminator for the {@link EnyoOnboardingV2TransitionSource} union. */
217
353
  export declare enum EnyoOnboardingV2TransitionSourceKind {
218
354
  /** The step's plain "continue" button (steps with no interactive block). */
219
355
  Continue = "continue",
220
356
  /** A specific option of a `Choice` block was picked. */
221
357
  Choice = "choice",
222
- /** A specific outcome of an `Action` block fired. */
358
+ /** A specific outcome of an `Action` or `Input` block fired. */
223
359
  Outcome = "outcome"
224
360
  }
225
361
  /** Where a transition leaves from within a step. */
@@ -234,7 +370,7 @@ export type EnyoOnboardingV2TransitionSource =
234
370
  blockId: string;
235
371
  optionId: string;
236
372
  }
237
- /** A specific outcome of an `Action` block fired. */
373
+ /** A specific outcome of an `Action` or `Input` block fired. */
238
374
  | {
239
375
  kind: EnyoOnboardingV2TransitionSourceKind.Outcome;
240
376
  blockId: string;