@enyo-energy/energy-app-sdk 1.7.0 → 1.9.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.
Files changed (34) hide show
  1. package/README.md +80 -0
  2. package/dist/cjs/energy-app-package-definition.d.cts +12 -0
  3. package/dist/cjs/index.cjs +1 -0
  4. package/dist/cjs/index.d.cts +1 -0
  5. package/dist/cjs/packages/energy-app-modbus.cjs +64 -0
  6. package/dist/cjs/packages/energy-app-modbus.d.cts +159 -0
  7. package/dist/cjs/types/enyo-appliance-command-forecast.cjs +9 -3
  8. package/dist/cjs/types/enyo-appliance-command-forecast.d.cts +15 -8
  9. package/dist/cjs/types/enyo-appliance.cjs +9 -1
  10. package/dist/cjs/types/enyo-appliance.d.cts +11 -3
  11. package/dist/cjs/types/enyo-data-bus-value.cjs +14 -6
  12. package/dist/cjs/types/enyo-data-bus-value.d.cts +21 -11
  13. package/dist/cjs/types/enyo-onboarding-v2.d.cts +1 -16
  14. package/dist/cjs/types/enyo-onboarding.cjs +3 -5
  15. package/dist/cjs/types/enyo-onboarding.d.cts +3 -5
  16. package/dist/cjs/version.cjs +1 -1
  17. package/dist/cjs/version.d.cts +1 -1
  18. package/dist/energy-app-package-definition.d.ts +12 -0
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.js +1 -0
  21. package/dist/packages/energy-app-modbus.d.ts +159 -0
  22. package/dist/packages/energy-app-modbus.js +62 -1
  23. package/dist/types/enyo-appliance-command-forecast.d.ts +15 -8
  24. package/dist/types/enyo-appliance-command-forecast.js +9 -3
  25. package/dist/types/enyo-appliance.d.ts +11 -3
  26. package/dist/types/enyo-appliance.js +9 -1
  27. package/dist/types/enyo-data-bus-value.d.ts +21 -11
  28. package/dist/types/enyo-data-bus-value.js +14 -6
  29. package/dist/types/enyo-onboarding-v2.d.ts +1 -16
  30. package/dist/types/enyo-onboarding.d.ts +3 -5
  31. package/dist/types/enyo-onboarding.js +3 -5
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -571,6 +571,86 @@ const registers = await client.readHoldingRegisters(1001, 10);
571
571
  await client.writeSingleRegister(2001, 500);
572
572
  ```
573
573
 
574
+ ##### Exceptions are data, not just failures
575
+
576
+ When a device *answers* but refuses an operation, the call rejects with a `ModbusExceptionError`
577
+ carrying the device's raw exception code. That is different from a timeout or a dropped socket, and
578
+ the distinction is the whole point: a permission probe is a write you expect to be refused.
579
+
580
+ ```typescript
581
+ import {ModbusExceptionError} from '@enyo-energy/energy-app-sdk';
582
+
583
+ // May this connection write at all? Write a register back its own value and see.
584
+ const current = (await client.readHoldingRegisters(43006, 1)).readUInt16BE(0);
585
+ try {
586
+ await client.writeSingleRegister(43006, current);
587
+ controlAllowed = true; // the write landed
588
+ } catch (error) {
589
+ if (error instanceof ModbusExceptionError) {
590
+ controlAllowed = false; // refused by the device, e.g. code 0x80
591
+ } else {
592
+ throw error; // the link is broken — a different problem
593
+ }
594
+ }
595
+ ```
596
+
597
+ Vendor-specific codes are passed through unvalidated (Huawei answers `0x80` for "permission
598
+ authentication failure or permission expiration", which is outside the standard `0x01`…`0x0B`
599
+ range). An exception response never recycles the socket.
600
+
601
+ ##### Vendor function codes: `sendRawPdu()`
602
+
603
+ Some devices reserve a function code of their own for things the standard eight codes cannot
604
+ express — most commonly an installer login that must succeed before any control write is accepted.
605
+ `sendRawPdu()` puts one PDU on the wire verbatim and hands back the raw answer, on the connection
606
+ the SDK already owns:
607
+
608
+ ```typescript
609
+ const response = await client.sendRawPdu(0x41, Buffer.from([0x24, ...challengeBytes]));
610
+
611
+ if (response.exceptionCode !== undefined) {
612
+ // The device refused. Expected often enough that it is a result, not a throw.
613
+ console.warn(`Vendor command refused with 0x${response.exceptionCode.toString(16)}`);
614
+ } else {
615
+ parseVendorReply(response.payload);
616
+ }
617
+ ```
618
+
619
+ - The SDK knows nothing about the vendor. Sub-commands, digests and keepalives stay in your app.
620
+ - The call queues on the same per-connection chain as every other request, so `noParallelRequests`
621
+ and `waitBetweenMessagesMs` hold — a login can never interleave with a block read that is already
622
+ in flight.
623
+ - Request payloads are **never logged**; only the function code and the payload length are. Vendor
624
+ handshakes carry credential digests.
625
+ - Payloads are capped at `MODBUS_MAX_PDU_PAYLOAD_BYTES` (252), keeping the frame inside the 253-byte
626
+ Modbus PDU limit. Function codes must be 1…127.
627
+ - It needs the same `Modbus` permission as the rest of the Modbus surface — an app that can already
628
+ call `writeMultipleRegisters` can write any holding register, so this grants reach, not privilege.
629
+
630
+ ##### Knowing when the socket was replaced
631
+
632
+ Anything the device tracks per *connection* rather than per device — a vendor login above all — dies
633
+ silently when the socket is recycled. The next write simply comes back refused. `onReconnect()` is
634
+ the signal that lets you re-establish it beforehand:
635
+
636
+ ```typescript
637
+ const stopListening = client.onReconnect(() => {
638
+ // Permission was granted to the old socket and is gone with it.
639
+ authenticated = false;
640
+ void loginAgain();
641
+ });
642
+
643
+ // Or check after the fact, without keeping a listener alive:
644
+ const before = client.connectionGeneration();
645
+ await doSomething();
646
+ if (client.connectionGeneration() !== before) {
647
+ // The socket was replaced in the meantime; connection-scoped state is void.
648
+ }
649
+ ```
650
+
651
+ Listeners are dropped automatically on `disconnect()`, and `onReconnect()` returns a function that
652
+ removes just yours.
653
+
574
654
  #### `useOcpp(): EnergyAppOcpp`
575
655
 
576
656
  Handle OCPP charging station communication:
@@ -261,6 +261,18 @@ export interface EnergyAppPackageCompatibilityVendor {
261
261
  vendorName: string;
262
262
  /** Models from this vendor that the package supports */
263
263
  models: EnergyAppPackageCompatibilityModel[];
264
+ /**
265
+ * Marks this package as the default Energy App for the vendor when no
266
+ * concrete model has been selected.
267
+ *
268
+ * During onboarding a user may only know the manufacturer of their device,
269
+ * not its exact model. When several packages declare compatibility with the
270
+ * same vendor, the one flagged with `default: true` is the app the enyo
271
+ * Store and onboarding flows pick in that case. Set it on at most one
272
+ * package per vendor; omit it (or set `false`) when the package should only
273
+ * be offered for an explicitly selected model.
274
+ */
275
+ defaultEnergyApp?: boolean;
264
276
  }
265
277
  /**
266
278
  * A file published together with an Energy App package and served publicly
@@ -56,6 +56,7 @@ __exportStar(require("./implementations/data-bus/data-bus-command-handler.cjs"),
56
56
  __exportStar(require("./types/enyo-currency.cjs"), exports);
57
57
  __exportStar(require("./packages/energy-app-sequence-generator.cjs"), exports);
58
58
  __exportStar(require("./packages/energy-app-energy-prices.cjs"), exports);
59
+ __exportStar(require("./packages/energy-app-modbus.cjs"), exports);
59
60
  __exportStar(require("./packages/energy-app-modbus-rtu.cjs"), exports);
60
61
  __exportStar(require("./types/enyo-modbus-server.cjs"), exports);
61
62
  __exportStar(require("./packages/energy-app-modbus-server.cjs"), exports);
@@ -40,6 +40,7 @@ export * from './implementations/data-bus/data-bus-command-handler.cjs';
40
40
  export * from './types/enyo-currency.cjs';
41
41
  export * from './packages/energy-app-sequence-generator.cjs';
42
42
  export * from './packages/energy-app-energy-prices.cjs';
43
+ export * from './packages/energy-app-modbus.cjs';
43
44
  export * from './packages/energy-app-modbus-rtu.cjs';
44
45
  export * from './types/enyo-modbus-server.cjs';
45
46
  export * from './packages/energy-app-modbus-server.cjs';
@@ -1,2 +1,66 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ModbusExceptionError = exports.MODBUS_MAX_PDU_PAYLOAD_BYTES = void 0;
4
+ /**
5
+ * The largest payload {@link EnergyAppModbusInstance.sendRawPdu} accepts, in bytes.
6
+ *
7
+ * A Modbus PDU is capped at 253 bytes; one of those is the function code, leaving 252 for
8
+ * the payload.
9
+ */
10
+ exports.MODBUS_MAX_PDU_PAYLOAD_BYTES = 252;
11
+ /**
12
+ * Thrown when a Modbus device answered a request with an exception response — the device is
13
+ * reachable and the frame was well-formed, it simply refused the operation.
14
+ *
15
+ * This is deliberately distinct from a transport failure. An app probing whether it is
16
+ * allowed to write (write a register back its own value and see what happens) needs
17
+ * "refused with code 0x80" to be reliably distinguishable from "timed out" or "socket
18
+ * dropped", and a generic `Error` with the code buried in its message string is not a
19
+ * contract anything can be built on.
20
+ *
21
+ * The socket is left intact when this is thrown: an exception response is a protocol answer,
22
+ * not a broken link.
23
+ *
24
+ * Note that {@link EnergyAppModbusInstance.sendRawPdu} does *not* throw this — a raw PDU
25
+ * reports its exception as data on {@link ModbusRawPduResponse.exceptionCode}, because there
26
+ * the exception is frequently the expected outcome rather than a failure.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * try {
31
+ * await instance.writeSingleRegister(43006, currentValue);
32
+ * // The write landed — this connection may write.
33
+ * } catch (error) {
34
+ * if (error instanceof ModbusExceptionError && error.exceptionCode === 0x80) {
35
+ * // Refused for lack of permission — a login is required.
36
+ * }
37
+ * throw error;
38
+ * }
39
+ * ```
40
+ */
41
+ class ModbusExceptionError extends Error {
42
+ exceptionCode;
43
+ functionCode;
44
+ address;
45
+ /**
46
+ * @param exceptionCode The raw exception code the device answered with. Standard codes
47
+ * are `0x01`…`0x0B`; vendor-specific codes are passed through
48
+ * unvalidated.
49
+ * @param functionCode The function code of the request that was refused (the request's
50
+ * own code, without the exception high bit).
51
+ * @param message Human-readable description, including the operation that failed.
52
+ * @param address The register or coil address involved, when the operation had
53
+ * one.
54
+ */
55
+ constructor(exceptionCode, functionCode, message, address) {
56
+ super(message);
57
+ this.exceptionCode = exceptionCode;
58
+ this.functionCode = functionCode;
59
+ this.address = address;
60
+ this.name = 'ModbusExceptionError';
61
+ // Restores the prototype chain so `instanceof` holds when this package is consumed
62
+ // from code compiled down to ES5.
63
+ Object.setPrototypeOf(this, ModbusExceptionError.prototype);
64
+ }
65
+ }
66
+ exports.ModbusExceptionError = ModbusExceptionError;
@@ -98,4 +98,163 @@ export interface EnergyAppModbusInstance {
98
98
  writeMultipleRegisters: (address: number, values: number[]) => Promise<void>;
99
99
  /** Read holding register string value */
100
100
  readRegisterStringValue: (address: number, quantity: number) => Promise<string>;
101
+ /**
102
+ * Sends one Modbus PDU verbatim on this connection and returns the raw response.
103
+ *
104
+ * This is the escape hatch for vendor function codes — the sub-commands a manufacturer
105
+ * reserves for itself outside the eight standard codes, such as the installer login a
106
+ * Huawei SUN2000 requires before it accepts a single control write. The transport owns
107
+ * framing (MBAP header, transaction id, unit id) exactly as it does for every other
108
+ * request; `payload` is put on the wire untouched and the response bytes come back
109
+ * untouched.
110
+ *
111
+ * The call is queued on the same per-connection chain as the standard reads and writes,
112
+ * so it honours {@link ModbusOptions.noParallelRequests} and
113
+ * {@link ModbusOptions.waitBetweenMessagesMs} and can never be interleaved with a block
114
+ * read that is already in flight.
115
+ *
116
+ * A device that answers with an exception resolves rather than rejects: the exception
117
+ * code is data (see {@link ModbusRawPduResponse.exceptionCode}), because for a
118
+ * permission probe "not allowed" is the expected answer and has to be distinguishable
119
+ * from a timeout or a dropped socket. Only transport failures reject.
120
+ *
121
+ * Request payloads are never logged — a vendor handshake typically carries a credential
122
+ * digest. Only the function code and the payload length appear in the logs.
123
+ *
124
+ * @param functionCode Modbus function code, 1…127. The exception space (`>= 0x80`) and
125
+ * `0` are rejected; the eight standard codes are allowed but the
126
+ * dedicated methods above are the better way to reach them.
127
+ * @param payload PDU bytes after the function code. At most
128
+ * {@link MODBUS_MAX_PDU_PAYLOAD_BYTES} bytes, so the frame stays
129
+ * within the 253-byte Modbus PDU limit. May be empty.
130
+ * @param options Optional per-call overrides.
131
+ * @throws Error if the parameters are out of range, if the connection is gone, or if the
132
+ * device does not answer within the deadline.
133
+ */
134
+ sendRawPdu: (functionCode: number, payload: Buffer, options?: ModbusRawPduOptions) => Promise<ModbusRawPduResponse>;
135
+ /**
136
+ * Registers a listener that fires whenever the underlying socket for this unit has been
137
+ * replaced — a reconnect after a dropped link, a recycled half-open socket, or a stale
138
+ * socket torn down by the transport.
139
+ *
140
+ * This matters for anything the device tracks per connection rather than per device.
141
+ * A vendor login, for instance, grants permission to the *socket* that authenticated:
142
+ * once that socket is gone the permission is gone with it, silently — the next write
143
+ * simply comes back with an exception. The listener is the point at which an app can
144
+ * re-authenticate before the next command instead of after a failed one.
145
+ *
146
+ * The listener fires once per socket replacement, after the old socket is gone and
147
+ * before the next operation is served. It is not called for the initial connect.
148
+ * Listeners must not throw; a throwing listener is logged and ignored.
149
+ *
150
+ * @param listener Called with no arguments once per socket replacement.
151
+ * @returns A function that removes the listener. Every listener is dropped automatically
152
+ * on {@link EnergyAppModbusInstance.disconnect}.
153
+ */
154
+ onReconnect: (listener: () => void) => () => void;
155
+ /**
156
+ * Monotonic counter of how many sockets this unit has been bound to, starting at `1` for
157
+ * the first live socket.
158
+ *
159
+ * The pull-based counterpart to {@link EnergyAppModbusInstance.onReconnect}: read it
160
+ * before and after an operation to tell whether connection-scoped state (a vendor login,
161
+ * a session token) survived, without having to keep a listener alive. A value that
162
+ * changed means the socket was replaced and anything scoped to it is void.
163
+ *
164
+ * Returns `0` while the instance has never had a live socket.
165
+ */
166
+ connectionGeneration: () => number;
167
+ }
168
+ /**
169
+ * Per-call options for {@link EnergyAppModbusInstance.sendRawPdu}.
170
+ */
171
+ export interface ModbusRawPduOptions {
172
+ /**
173
+ * Deadline for this single request in milliseconds. Defaults to the connection's
174
+ * {@link ModbusOptions.readTimeoutMs}. As with every other operation, exceeding it is
175
+ * treated as a dead link and recycles the socket.
176
+ */
177
+ timeoutMs?: number;
178
+ }
179
+ /**
180
+ * The raw answer to a {@link EnergyAppModbusInstance.sendRawPdu} call.
181
+ *
182
+ * Both a normal response and a device-level exception arrive here; only transport failures
183
+ * are thrown. Check {@link ModbusRawPduResponse.exceptionCode} first — it is `undefined`
184
+ * exactly when the device accepted the request.
185
+ */
186
+ export interface ModbusRawPduResponse {
187
+ /**
188
+ * The function code the device echoed. On an exception this is the requested code with
189
+ * the high bit set (`functionCode | 0x80`), which is what the device actually put on the
190
+ * wire.
191
+ */
192
+ functionCode: number;
193
+ /**
194
+ * The response bytes that followed the function code, with vendor framing intact.
195
+ * Empty on an exception — the exception code is reported separately rather than left in
196
+ * the payload.
197
+ */
198
+ payload: Buffer;
199
+ /**
200
+ * The device's exception code when it rejected the request, otherwise `undefined`.
201
+ *
202
+ * Standard Modbus defines `0x01`…`0x0B`, but vendors add their own — Huawei answers
203
+ * `0x80` for "permission authentication failure or permission expiration" — so this is
204
+ * the raw byte, passed through without validation against the standard set.
205
+ */
206
+ exceptionCode?: number;
207
+ }
208
+ /**
209
+ * The largest payload {@link EnergyAppModbusInstance.sendRawPdu} accepts, in bytes.
210
+ *
211
+ * A Modbus PDU is capped at 253 bytes; one of those is the function code, leaving 252 for
212
+ * the payload.
213
+ */
214
+ export declare const MODBUS_MAX_PDU_PAYLOAD_BYTES = 252;
215
+ /**
216
+ * Thrown when a Modbus device answered a request with an exception response — the device is
217
+ * reachable and the frame was well-formed, it simply refused the operation.
218
+ *
219
+ * This is deliberately distinct from a transport failure. An app probing whether it is
220
+ * allowed to write (write a register back its own value and see what happens) needs
221
+ * "refused with code 0x80" to be reliably distinguishable from "timed out" or "socket
222
+ * dropped", and a generic `Error` with the code buried in its message string is not a
223
+ * contract anything can be built on.
224
+ *
225
+ * The socket is left intact when this is thrown: an exception response is a protocol answer,
226
+ * not a broken link.
227
+ *
228
+ * Note that {@link EnergyAppModbusInstance.sendRawPdu} does *not* throw this — a raw PDU
229
+ * reports its exception as data on {@link ModbusRawPduResponse.exceptionCode}, because there
230
+ * the exception is frequently the expected outcome rather than a failure.
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * try {
235
+ * await instance.writeSingleRegister(43006, currentValue);
236
+ * // The write landed — this connection may write.
237
+ * } catch (error) {
238
+ * if (error instanceof ModbusExceptionError && error.exceptionCode === 0x80) {
239
+ * // Refused for lack of permission — a login is required.
240
+ * }
241
+ * throw error;
242
+ * }
243
+ * ```
244
+ */
245
+ export declare class ModbusExceptionError extends Error {
246
+ readonly exceptionCode: number;
247
+ readonly functionCode: number;
248
+ readonly address?: number | undefined;
249
+ /**
250
+ * @param exceptionCode The raw exception code the device answered with. Standard codes
251
+ * are `0x01`…`0x0B`; vendor-specific codes are passed through
252
+ * unvalidated.
253
+ * @param functionCode The function code of the request that was refused (the request's
254
+ * own code, without the exception high bit).
255
+ * @param message Human-readable description, including the operation that failed.
256
+ * @param address The register or coil address involved, when the operation had
257
+ * one.
258
+ */
259
+ constructor(exceptionCode: number, functionCode: number, message: string, address?: number | undefined);
101
260
  }
@@ -90,9 +90,15 @@ var BatteryCommandForecastDirectionEnum;
90
90
  /** Power should flow from the battery into the home / grid (discharging). */
91
91
  BatteryCommandForecastDirectionEnum["Discharge"] = "discharge";
92
92
  /**
93
- * No energy flow the battery holds its current state-of-charge.
94
- * Use to mark idle periods between charge / discharge entries. The
95
- * entry's `powerW` must be `0`.
93
+ * Leave the slot to the battery's own logic for the duration of
94
+ * the entry the appliance decides itself whether to charge,
95
+ * discharge or stand still (auto mode).
96
+ *
97
+ * This is *not* a "no energy flow" marker: to block charging or
98
+ * discharging, use {@link BatteryCommandForecastDirectionEnum.Charge}
99
+ * respectively {@link BatteryCommandForecastDirectionEnum.Discharge}
100
+ * with `powerW = 0`. An `Idle` entry prescribes no setpoint, so its
101
+ * `powerW` must be `0`.
96
102
  */
97
103
  BatteryCommandForecastDirectionEnum["Idle"] = "idle";
98
104
  })(BatteryCommandForecastDirectionEnum || (exports.BatteryCommandForecastDirectionEnum = BatteryCommandForecastDirectionEnum = {}));
@@ -180,9 +180,15 @@ export declare enum BatteryCommandForecastDirectionEnum {
180
180
  /** Power should flow from the battery into the home / grid (discharging). */
181
181
  Discharge = "discharge",
182
182
  /**
183
- * No energy flow the battery holds its current state-of-charge.
184
- * Use to mark idle periods between charge / discharge entries. The
185
- * entry's `powerW` must be `0`.
183
+ * Leave the slot to the battery's own logic for the duration of
184
+ * the entry the appliance decides itself whether to charge,
185
+ * discharge or stand still (auto mode).
186
+ *
187
+ * This is *not* a "no energy flow" marker: to block charging or
188
+ * discharging, use {@link BatteryCommandForecastDirectionEnum.Charge}
189
+ * respectively {@link BatteryCommandForecastDirectionEnum.Discharge}
190
+ * with `powerW = 0`. An `Idle` entry prescribes no setpoint, so its
191
+ * `powerW` must be `0`.
186
192
  */
187
193
  Idle = "idle"
188
194
  }
@@ -205,11 +211,12 @@ export interface BatteryCommandForecastScheduleEntry {
205
211
  /**
206
212
  * Target power in Watts. Always non-negative — direction is carried
207
213
  * by {@link direction}, never by sign. A `powerW` of `0` together
208
- * with a `Charge` or `Discharge` direction means "hold at zero in
209
- * the named direction" (effectively idle until the next entry);
210
- * prefer the explicit
211
- * {@link BatteryCommandForecastDirectionEnum.Idle} direction for
212
- * unambiguous idle slots, in which case `powerW` must also be `0`.
214
+ * with a `Charge` or `Discharge` direction blocks that direction
215
+ * the battery must not charge respectively discharge until the next
216
+ * entry. Use that to keep the battery still, *not*
217
+ * {@link BatteryCommandForecastDirectionEnum.Idle}, which hands
218
+ * control back to the appliance's own logic and prescribes no
219
+ * setpoint at all (its `powerW` must be `0`).
213
220
  */
214
221
  powerW: number;
215
222
  }
@@ -24,7 +24,9 @@ var EnyoApplianceStateEnum;
24
24
  * which describes connectivity. `Healthy` means the appliance is operating
25
25
  * normally; `Warning` means a non-blocking issue has been reported (the
26
26
  * appliance is still functional but should be inspected); `Faulted` means it
27
- * has reported an internal error and may need attention. Vendor- or
27
+ * has reported an internal error and may need attention; `Deactivated` means
28
+ * the appliance has been intentionally switched off from energy management and
29
+ * is neither monitored nor controlled until it is reactivated. Vendor- or
28
30
  * protocol-specific details should be conveyed via accompanying error codes.
29
31
  */
30
32
  var EnyoApplianceStatusEnum;
@@ -35,6 +37,12 @@ var EnyoApplianceStatusEnum;
35
37
  EnyoApplianceStatusEnum["Warning"] = "warning";
36
38
  /** Appliance has reported an internal fault */
37
39
  EnyoApplianceStatusEnum["Faulted"] = "faulted";
40
+ /**
41
+ * Appliance has been intentionally deactivated and is excluded from energy
42
+ * management. It is not controlled and its health is not evaluated until it
43
+ * is reactivated.
44
+ */
45
+ EnyoApplianceStatusEnum["Deactivated"] = "deactivated";
38
46
  })(EnyoApplianceStatusEnum || (exports.EnyoApplianceStatusEnum = EnyoApplianceStatusEnum = {}));
39
47
  var EnyoApplianceConnectionType;
40
48
  (function (EnyoApplianceConnectionType) {
@@ -33,7 +33,9 @@ export declare enum EnyoApplianceStateEnum {
33
33
  * which describes connectivity. `Healthy` means the appliance is operating
34
34
  * normally; `Warning` means a non-blocking issue has been reported (the
35
35
  * appliance is still functional but should be inspected); `Faulted` means it
36
- * has reported an internal error and may need attention. Vendor- or
36
+ * has reported an internal error and may need attention; `Deactivated` means
37
+ * the appliance has been intentionally switched off from energy management and
38
+ * is neither monitored nor controlled until it is reactivated. Vendor- or
37
39
  * protocol-specific details should be conveyed via accompanying error codes.
38
40
  */
39
41
  export declare enum EnyoApplianceStatusEnum {
@@ -42,7 +44,13 @@ export declare enum EnyoApplianceStatusEnum {
42
44
  /** Appliance is operating but has reported a non-blocking issue that should be inspected */
43
45
  Warning = "warning",
44
46
  /** Appliance has reported an internal fault */
45
- Faulted = "faulted"
47
+ Faulted = "faulted",
48
+ /**
49
+ * Appliance has been intentionally deactivated and is excluded from energy
50
+ * management. It is not controlled and its health is not evaluated until it
51
+ * is reactivated.
52
+ */
53
+ Deactivated = "deactivated"
46
54
  }
47
55
  /**
48
56
  * Severity classification for an {@link EnyoApplianceErrorCode}.
@@ -175,7 +183,7 @@ export interface EnyoApplianceMetadata {
175
183
  ipAddress?: string;
176
184
  /** Connection state */
177
185
  state?: EnyoApplianceStateEnum;
178
- /** Health status of the appliance (e.g. healthy or faulted) */
186
+ /** Health status of the appliance (e.g. healthy, faulted or deactivated) */
179
187
  status?: EnyoApplianceStatusEnum;
180
188
  network?: EnyoApplianceNetworkMetadata;
181
189
  modbus?: EnyoApplianceModbusMetadata;
@@ -324,9 +324,11 @@ var EnyoStorageScheduleModeEnum;
324
324
  * non-negative, and consumers never have to disambiguate `0` from
325
325
  * "direction-of-zero" or interpret sign conventions per integration.
326
326
  *
327
- * `Idle` is the explicit "no energy flow" markeruse it to insert
328
- * idle slots between charge / discharge periods. An entry with
329
- * `direction = Idle` must carry `powerW = 0`.
327
+ * `Idle` hands the slot back to the battery's own logic — it does *not*
328
+ * mean "no energy flow". To keep the battery still, use `Charge` or
329
+ * `Discharge` with `powerW = 0`, which blocks that direction for the
330
+ * duration of the entry. An `Idle` entry prescribes no setpoint, so it
331
+ * must carry `powerW = 0`.
330
332
  */
331
333
  var EnyoStorageScheduleDirectionEnum;
332
334
  (function (EnyoStorageScheduleDirectionEnum) {
@@ -335,9 +337,15 @@ var EnyoStorageScheduleDirectionEnum;
335
337
  /** Power should flow from the battery into the grid / home (discharging). */
336
338
  EnyoStorageScheduleDirectionEnum["Discharge"] = "discharge";
337
339
  /**
338
- * No energy flow the battery holds its current state-of-charge.
339
- * Use to mark idle periods between charge / discharge entries. The
340
- * entry's `powerW` must be `0`.
340
+ * Leave the slot to the battery's own logic for the duration of
341
+ * the entry the appliance decides itself whether to charge,
342
+ * discharge or stand still (auto mode).
343
+ *
344
+ * This is *not* a "no energy flow" marker: to block charging or
345
+ * discharging, use {@link EnyoStorageScheduleDirectionEnum.Charge}
346
+ * respectively {@link EnyoStorageScheduleDirectionEnum.Discharge}
347
+ * with `powerW = 0`. An `Idle` entry prescribes no setpoint, so its
348
+ * `powerW` must be `0`.
341
349
  */
342
350
  EnyoStorageScheduleDirectionEnum["Idle"] = "idle";
343
351
  })(EnyoStorageScheduleDirectionEnum || (exports.EnyoStorageScheduleDirectionEnum = EnyoStorageScheduleDirectionEnum = {}));
@@ -593,7 +593,8 @@ export interface EnyoDataBusApplianceFlexibilityAnnouncementV1 extends EnyoDataB
593
593
  * provided together if they change in the same event. `errorCodes` carries
594
594
  * vendor- or protocol-specific codes that explain a transition into a
595
595
  * `warning` or `faulted` status; each entry's `severity` field indicates
596
- * which.
596
+ * which. A transition into `deactivated` is intentional and normally carries
597
+ * no error codes.
597
598
  */
598
599
  export interface EnyoDataBusApplianceStateUpdateV1 extends EnyoDataBusMessage {
599
600
  type: 'message';
@@ -1636,9 +1637,11 @@ export declare enum EnyoStorageScheduleModeEnum {
1636
1637
  * non-negative, and consumers never have to disambiguate `0` from
1637
1638
  * "direction-of-zero" or interpret sign conventions per integration.
1638
1639
  *
1639
- * `Idle` is the explicit "no energy flow" markeruse it to insert
1640
- * idle slots between charge / discharge periods. An entry with
1641
- * `direction = Idle` must carry `powerW = 0`.
1640
+ * `Idle` hands the slot back to the battery's own logic — it does *not*
1641
+ * mean "no energy flow". To keep the battery still, use `Charge` or
1642
+ * `Discharge` with `powerW = 0`, which blocks that direction for the
1643
+ * duration of the entry. An `Idle` entry prescribes no setpoint, so it
1644
+ * must carry `powerW = 0`.
1642
1645
  */
1643
1646
  export declare enum EnyoStorageScheduleDirectionEnum {
1644
1647
  /** Power should flow from the grid into the battery (charging). */
@@ -1646,9 +1649,15 @@ export declare enum EnyoStorageScheduleDirectionEnum {
1646
1649
  /** Power should flow from the battery into the grid / home (discharging). */
1647
1650
  Discharge = "discharge",
1648
1651
  /**
1649
- * No energy flow the battery holds its current state-of-charge.
1650
- * Use to mark idle periods between charge / discharge entries. The
1651
- * entry's `powerW` must be `0`.
1652
+ * Leave the slot to the battery's own logic for the duration of
1653
+ * the entry the appliance decides itself whether to charge,
1654
+ * discharge or stand still (auto mode).
1655
+ *
1656
+ * This is *not* a "no energy flow" marker: to block charging or
1657
+ * discharging, use {@link EnyoStorageScheduleDirectionEnum.Charge}
1658
+ * respectively {@link EnyoStorageScheduleDirectionEnum.Discharge}
1659
+ * with `powerW = 0`. An `Idle` entry prescribes no setpoint, so its
1660
+ * `powerW` must be `0`.
1652
1661
  */
1653
1662
  Idle = "idle"
1654
1663
  }
@@ -1678,10 +1687,11 @@ export interface EnyoStorageScheduleEntry {
1678
1687
  * Target power for this setpoint in Watts. Always non-negative —
1679
1688
  * direction is carried by {@link direction}, never by sign. A
1680
1689
  * `powerW` of `0` together with a `Charge` or `Discharge` direction
1681
- * means "hold at zero in the named direction" (effectively idle
1682
- * until the next entry); prefer the explicit
1683
- * {@link EnyoStorageScheduleDirectionEnum.Idle} direction for
1684
- * unambiguous idle slots, in which case `powerW` must also be `0`.
1690
+ * blocks that direction the battery must not charge respectively
1691
+ * discharge until the next entry. Use that to keep the battery
1692
+ * still, *not* {@link EnyoStorageScheduleDirectionEnum.Idle}, which
1693
+ * hands control back to the appliance's own logic and prescribes no
1694
+ * setpoint at all (its `powerW` must be `0`).
1685
1695
  */
1686
1696
  powerW: number;
1687
1697
  }
@@ -20,7 +20,7 @@
20
20
  * (`../implementations/onboarding-v2/onboarding-v2-validators.ts`) to fail fast
21
21
  * before publishing.
22
22
  */
23
- import type { EnyoOnboardingGuideCategory, EnyoOnboardingTranslatedContent } from './enyo-onboarding.cjs';
23
+ import type { EnyoOnboardingTranslatedContent } from './enyo-onboarding.cjs';
24
24
  /**
25
25
  * The situation a flow starts from. A vendor/model can have up to one guide per
26
26
  * variant; a branch can jump into another variant's flow
@@ -1067,21 +1067,6 @@ export interface EnyoOnboardingV2Guide {
1067
1067
  title: EnyoOnboardingTranslatedContent[];
1068
1068
  /** Which start situation this guide covers. */
1069
1069
  startVariant: EnyoOnboardingV2StartVariant;
1070
- /**
1071
- * The lifecycle role this guide plays, which decides where the host offers
1072
- * it (an "add new device" entry point, the configuration-required prompt).
1073
- *
1074
- * Distinct from {@link startVariant}, which describes the *situation the
1075
- * flow starts from* (device not found, found but unconfigured, …). This says
1076
- * *why the installer is here at all*: configuring the package for the first
1077
- * time is a different entry point from adding a second device to a package
1078
- * that already works, even when both start from `device-not-found`.
1079
- *
1080
- * Shared with the v1 model rather than restated as a v2 enum — the category
1081
- * is a property of a guide's role, not of the authoring model. Defaults to
1082
- * {@link EnyoOnboardingGuideCategory.InitialSetup} semantics when omitted.
1083
- */
1084
- category?: EnyoOnboardingGuideCategory;
1085
1070
  /**
1086
1071
  * Whether the host runs its local network scan before entering this guide.
1087
1072
  *
@@ -6,11 +6,9 @@ exports.EnyoOnboardingSectionType = exports.EnyoOnboardingGuideCategory = void 0
6
6
  * Used to distinguish guides that perform different roles, e.g. initial
7
7
  * configuration of a package vs. adding or reconnecting a single device.
8
8
  *
9
- * Shared by both onboarding models v2 guides set it through
10
- * {@link EnyoOnboardingV2Guide.category} in the same way both reuse
11
- * {@link EnyoOnboardingTranslatedContent}. Not deprecated with the rest of v1:
12
- * the category is a property of a guide's role, not of the authoring model it
13
- * was written in.
9
+ * @deprecated Superseded by the v2 graph model. Author guides with
10
+ * {@link EnyoOnboardingV2Guide} via `defineOnboardingGuideV2()`. v1 is retained
11
+ * for backward compatibility and will be removed in a future major.
14
12
  */
15
13
  var EnyoOnboardingGuideCategory;
16
14
  (function (EnyoOnboardingGuideCategory) {
@@ -14,11 +14,9 @@ export interface EnyoOnboardingTranslatedContent {
14
14
  * Used to distinguish guides that perform different roles, e.g. initial
15
15
  * configuration of a package vs. adding or reconnecting a single device.
16
16
  *
17
- * Shared by both onboarding models v2 guides set it through
18
- * {@link EnyoOnboardingV2Guide.category} in the same way both reuse
19
- * {@link EnyoOnboardingTranslatedContent}. Not deprecated with the rest of v1:
20
- * the category is a property of a guide's role, not of the authoring model it
21
- * was written in.
17
+ * @deprecated Superseded by the v2 graph model. Author guides with
18
+ * {@link EnyoOnboardingV2Guide} via `defineOnboardingGuideV2()`. v1 is retained
19
+ * for backward compatibility and will be removed in a future major.
22
20
  */
23
21
  export declare enum EnyoOnboardingGuideCategory {
24
22
  /** Initial package configuration — shown when EnergyAppStateEnum is 'configuration-required' */
@@ -9,7 +9,7 @@ exports.getSdkVersion = getSdkVersion;
9
9
  /**
10
10
  * Current version of the enyo Energy App SDK.
11
11
  */
12
- exports.SDK_VERSION = '1.7.0';
12
+ exports.SDK_VERSION = '1.9.0';
13
13
  /**
14
14
  * Gets the current SDK version.
15
15
  * @returns The semantic version string of the SDK
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "1.7.0";
8
+ export declare const SDK_VERSION = "1.9.0";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -261,6 +261,18 @@ export interface EnergyAppPackageCompatibilityVendor {
261
261
  vendorName: string;
262
262
  /** Models from this vendor that the package supports */
263
263
  models: EnergyAppPackageCompatibilityModel[];
264
+ /**
265
+ * Marks this package as the default Energy App for the vendor when no
266
+ * concrete model has been selected.
267
+ *
268
+ * During onboarding a user may only know the manufacturer of their device,
269
+ * not its exact model. When several packages declare compatibility with the
270
+ * same vendor, the one flagged with `default: true` is the app the enyo
271
+ * Store and onboarding flows pick in that case. Set it on at most one
272
+ * package per vendor; omit it (or set `false`) when the package should only
273
+ * be offered for an explicitly selected model.
274
+ */
275
+ defaultEnergyApp?: boolean;
264
276
  }
265
277
  /**
266
278
  * A file published together with an Energy App package and served publicly
package/dist/index.d.ts CHANGED
@@ -40,6 +40,7 @@ export * from './implementations/data-bus/data-bus-command-handler.js';
40
40
  export * from './types/enyo-currency.js';
41
41
  export * from './packages/energy-app-sequence-generator.js';
42
42
  export * from './packages/energy-app-energy-prices.js';
43
+ export * from './packages/energy-app-modbus.js';
43
44
  export * from './packages/energy-app-modbus-rtu.js';
44
45
  export * from './types/enyo-modbus-server.js';
45
46
  export * from './packages/energy-app-modbus-server.js';
package/dist/index.js CHANGED
@@ -40,6 +40,7 @@ export * from './implementations/data-bus/data-bus-command-handler.js';
40
40
  export * from './types/enyo-currency.js';
41
41
  export * from './packages/energy-app-sequence-generator.js';
42
42
  export * from './packages/energy-app-energy-prices.js';
43
+ export * from './packages/energy-app-modbus.js';
43
44
  export * from './packages/energy-app-modbus-rtu.js';
44
45
  export * from './types/enyo-modbus-server.js';
45
46
  export * from './packages/energy-app-modbus-server.js';
@@ -98,4 +98,163 @@ export interface EnergyAppModbusInstance {
98
98
  writeMultipleRegisters: (address: number, values: number[]) => Promise<void>;
99
99
  /** Read holding register string value */
100
100
  readRegisterStringValue: (address: number, quantity: number) => Promise<string>;
101
+ /**
102
+ * Sends one Modbus PDU verbatim on this connection and returns the raw response.
103
+ *
104
+ * This is the escape hatch for vendor function codes — the sub-commands a manufacturer
105
+ * reserves for itself outside the eight standard codes, such as the installer login a
106
+ * Huawei SUN2000 requires before it accepts a single control write. The transport owns
107
+ * framing (MBAP header, transaction id, unit id) exactly as it does for every other
108
+ * request; `payload` is put on the wire untouched and the response bytes come back
109
+ * untouched.
110
+ *
111
+ * The call is queued on the same per-connection chain as the standard reads and writes,
112
+ * so it honours {@link ModbusOptions.noParallelRequests} and
113
+ * {@link ModbusOptions.waitBetweenMessagesMs} and can never be interleaved with a block
114
+ * read that is already in flight.
115
+ *
116
+ * A device that answers with an exception resolves rather than rejects: the exception
117
+ * code is data (see {@link ModbusRawPduResponse.exceptionCode}), because for a
118
+ * permission probe "not allowed" is the expected answer and has to be distinguishable
119
+ * from a timeout or a dropped socket. Only transport failures reject.
120
+ *
121
+ * Request payloads are never logged — a vendor handshake typically carries a credential
122
+ * digest. Only the function code and the payload length appear in the logs.
123
+ *
124
+ * @param functionCode Modbus function code, 1…127. The exception space (`>= 0x80`) and
125
+ * `0` are rejected; the eight standard codes are allowed but the
126
+ * dedicated methods above are the better way to reach them.
127
+ * @param payload PDU bytes after the function code. At most
128
+ * {@link MODBUS_MAX_PDU_PAYLOAD_BYTES} bytes, so the frame stays
129
+ * within the 253-byte Modbus PDU limit. May be empty.
130
+ * @param options Optional per-call overrides.
131
+ * @throws Error if the parameters are out of range, if the connection is gone, or if the
132
+ * device does not answer within the deadline.
133
+ */
134
+ sendRawPdu: (functionCode: number, payload: Buffer, options?: ModbusRawPduOptions) => Promise<ModbusRawPduResponse>;
135
+ /**
136
+ * Registers a listener that fires whenever the underlying socket for this unit has been
137
+ * replaced — a reconnect after a dropped link, a recycled half-open socket, or a stale
138
+ * socket torn down by the transport.
139
+ *
140
+ * This matters for anything the device tracks per connection rather than per device.
141
+ * A vendor login, for instance, grants permission to the *socket* that authenticated:
142
+ * once that socket is gone the permission is gone with it, silently — the next write
143
+ * simply comes back with an exception. The listener is the point at which an app can
144
+ * re-authenticate before the next command instead of after a failed one.
145
+ *
146
+ * The listener fires once per socket replacement, after the old socket is gone and
147
+ * before the next operation is served. It is not called for the initial connect.
148
+ * Listeners must not throw; a throwing listener is logged and ignored.
149
+ *
150
+ * @param listener Called with no arguments once per socket replacement.
151
+ * @returns A function that removes the listener. Every listener is dropped automatically
152
+ * on {@link EnergyAppModbusInstance.disconnect}.
153
+ */
154
+ onReconnect: (listener: () => void) => () => void;
155
+ /**
156
+ * Monotonic counter of how many sockets this unit has been bound to, starting at `1` for
157
+ * the first live socket.
158
+ *
159
+ * The pull-based counterpart to {@link EnergyAppModbusInstance.onReconnect}: read it
160
+ * before and after an operation to tell whether connection-scoped state (a vendor login,
161
+ * a session token) survived, without having to keep a listener alive. A value that
162
+ * changed means the socket was replaced and anything scoped to it is void.
163
+ *
164
+ * Returns `0` while the instance has never had a live socket.
165
+ */
166
+ connectionGeneration: () => number;
167
+ }
168
+ /**
169
+ * Per-call options for {@link EnergyAppModbusInstance.sendRawPdu}.
170
+ */
171
+ export interface ModbusRawPduOptions {
172
+ /**
173
+ * Deadline for this single request in milliseconds. Defaults to the connection's
174
+ * {@link ModbusOptions.readTimeoutMs}. As with every other operation, exceeding it is
175
+ * treated as a dead link and recycles the socket.
176
+ */
177
+ timeoutMs?: number;
178
+ }
179
+ /**
180
+ * The raw answer to a {@link EnergyAppModbusInstance.sendRawPdu} call.
181
+ *
182
+ * Both a normal response and a device-level exception arrive here; only transport failures
183
+ * are thrown. Check {@link ModbusRawPduResponse.exceptionCode} first — it is `undefined`
184
+ * exactly when the device accepted the request.
185
+ */
186
+ export interface ModbusRawPduResponse {
187
+ /**
188
+ * The function code the device echoed. On an exception this is the requested code with
189
+ * the high bit set (`functionCode | 0x80`), which is what the device actually put on the
190
+ * wire.
191
+ */
192
+ functionCode: number;
193
+ /**
194
+ * The response bytes that followed the function code, with vendor framing intact.
195
+ * Empty on an exception — the exception code is reported separately rather than left in
196
+ * the payload.
197
+ */
198
+ payload: Buffer;
199
+ /**
200
+ * The device's exception code when it rejected the request, otherwise `undefined`.
201
+ *
202
+ * Standard Modbus defines `0x01`…`0x0B`, but vendors add their own — Huawei answers
203
+ * `0x80` for "permission authentication failure or permission expiration" — so this is
204
+ * the raw byte, passed through without validation against the standard set.
205
+ */
206
+ exceptionCode?: number;
207
+ }
208
+ /**
209
+ * The largest payload {@link EnergyAppModbusInstance.sendRawPdu} accepts, in bytes.
210
+ *
211
+ * A Modbus PDU is capped at 253 bytes; one of those is the function code, leaving 252 for
212
+ * the payload.
213
+ */
214
+ export declare const MODBUS_MAX_PDU_PAYLOAD_BYTES = 252;
215
+ /**
216
+ * Thrown when a Modbus device answered a request with an exception response — the device is
217
+ * reachable and the frame was well-formed, it simply refused the operation.
218
+ *
219
+ * This is deliberately distinct from a transport failure. An app probing whether it is
220
+ * allowed to write (write a register back its own value and see what happens) needs
221
+ * "refused with code 0x80" to be reliably distinguishable from "timed out" or "socket
222
+ * dropped", and a generic `Error` with the code buried in its message string is not a
223
+ * contract anything can be built on.
224
+ *
225
+ * The socket is left intact when this is thrown: an exception response is a protocol answer,
226
+ * not a broken link.
227
+ *
228
+ * Note that {@link EnergyAppModbusInstance.sendRawPdu} does *not* throw this — a raw PDU
229
+ * reports its exception as data on {@link ModbusRawPduResponse.exceptionCode}, because there
230
+ * the exception is frequently the expected outcome rather than a failure.
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * try {
235
+ * await instance.writeSingleRegister(43006, currentValue);
236
+ * // The write landed — this connection may write.
237
+ * } catch (error) {
238
+ * if (error instanceof ModbusExceptionError && error.exceptionCode === 0x80) {
239
+ * // Refused for lack of permission — a login is required.
240
+ * }
241
+ * throw error;
242
+ * }
243
+ * ```
244
+ */
245
+ export declare class ModbusExceptionError extends Error {
246
+ readonly exceptionCode: number;
247
+ readonly functionCode: number;
248
+ readonly address?: number | undefined;
249
+ /**
250
+ * @param exceptionCode The raw exception code the device answered with. Standard codes
251
+ * are `0x01`…`0x0B`; vendor-specific codes are passed through
252
+ * unvalidated.
253
+ * @param functionCode The function code of the request that was refused (the request's
254
+ * own code, without the exception high bit).
255
+ * @param message Human-readable description, including the operation that failed.
256
+ * @param address The register or coil address involved, when the operation had
257
+ * one.
258
+ */
259
+ constructor(exceptionCode: number, functionCode: number, message: string, address?: number | undefined);
101
260
  }
@@ -1 +1,62 @@
1
- export {};
1
+ /**
2
+ * The largest payload {@link EnergyAppModbusInstance.sendRawPdu} accepts, in bytes.
3
+ *
4
+ * A Modbus PDU is capped at 253 bytes; one of those is the function code, leaving 252 for
5
+ * the payload.
6
+ */
7
+ export const MODBUS_MAX_PDU_PAYLOAD_BYTES = 252;
8
+ /**
9
+ * Thrown when a Modbus device answered a request with an exception response — the device is
10
+ * reachable and the frame was well-formed, it simply refused the operation.
11
+ *
12
+ * This is deliberately distinct from a transport failure. An app probing whether it is
13
+ * allowed to write (write a register back its own value and see what happens) needs
14
+ * "refused with code 0x80" to be reliably distinguishable from "timed out" or "socket
15
+ * dropped", and a generic `Error` with the code buried in its message string is not a
16
+ * contract anything can be built on.
17
+ *
18
+ * The socket is left intact when this is thrown: an exception response is a protocol answer,
19
+ * not a broken link.
20
+ *
21
+ * Note that {@link EnergyAppModbusInstance.sendRawPdu} does *not* throw this — a raw PDU
22
+ * reports its exception as data on {@link ModbusRawPduResponse.exceptionCode}, because there
23
+ * the exception is frequently the expected outcome rather than a failure.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * try {
28
+ * await instance.writeSingleRegister(43006, currentValue);
29
+ * // The write landed — this connection may write.
30
+ * } catch (error) {
31
+ * if (error instanceof ModbusExceptionError && error.exceptionCode === 0x80) {
32
+ * // Refused for lack of permission — a login is required.
33
+ * }
34
+ * throw error;
35
+ * }
36
+ * ```
37
+ */
38
+ export class ModbusExceptionError extends Error {
39
+ exceptionCode;
40
+ functionCode;
41
+ address;
42
+ /**
43
+ * @param exceptionCode The raw exception code the device answered with. Standard codes
44
+ * are `0x01`…`0x0B`; vendor-specific codes are passed through
45
+ * unvalidated.
46
+ * @param functionCode The function code of the request that was refused (the request's
47
+ * own code, without the exception high bit).
48
+ * @param message Human-readable description, including the operation that failed.
49
+ * @param address The register or coil address involved, when the operation had
50
+ * one.
51
+ */
52
+ constructor(exceptionCode, functionCode, message, address) {
53
+ super(message);
54
+ this.exceptionCode = exceptionCode;
55
+ this.functionCode = functionCode;
56
+ this.address = address;
57
+ this.name = 'ModbusExceptionError';
58
+ // Restores the prototype chain so `instanceof` holds when this package is consumed
59
+ // from code compiled down to ES5.
60
+ Object.setPrototypeOf(this, ModbusExceptionError.prototype);
61
+ }
62
+ }
@@ -180,9 +180,15 @@ export declare enum BatteryCommandForecastDirectionEnum {
180
180
  /** Power should flow from the battery into the home / grid (discharging). */
181
181
  Discharge = "discharge",
182
182
  /**
183
- * No energy flow the battery holds its current state-of-charge.
184
- * Use to mark idle periods between charge / discharge entries. The
185
- * entry's `powerW` must be `0`.
183
+ * Leave the slot to the battery's own logic for the duration of
184
+ * the entry the appliance decides itself whether to charge,
185
+ * discharge or stand still (auto mode).
186
+ *
187
+ * This is *not* a "no energy flow" marker: to block charging or
188
+ * discharging, use {@link BatteryCommandForecastDirectionEnum.Charge}
189
+ * respectively {@link BatteryCommandForecastDirectionEnum.Discharge}
190
+ * with `powerW = 0`. An `Idle` entry prescribes no setpoint, so its
191
+ * `powerW` must be `0`.
186
192
  */
187
193
  Idle = "idle"
188
194
  }
@@ -205,11 +211,12 @@ export interface BatteryCommandForecastScheduleEntry {
205
211
  /**
206
212
  * Target power in Watts. Always non-negative — direction is carried
207
213
  * by {@link direction}, never by sign. A `powerW` of `0` together
208
- * with a `Charge` or `Discharge` direction means "hold at zero in
209
- * the named direction" (effectively idle until the next entry);
210
- * prefer the explicit
211
- * {@link BatteryCommandForecastDirectionEnum.Idle} direction for
212
- * unambiguous idle slots, in which case `powerW` must also be `0`.
214
+ * with a `Charge` or `Discharge` direction blocks that direction
215
+ * the battery must not charge respectively discharge until the next
216
+ * entry. Use that to keep the battery still, *not*
217
+ * {@link BatteryCommandForecastDirectionEnum.Idle}, which hands
218
+ * control back to the appliance's own logic and prescribes no
219
+ * setpoint at all (its `powerW` must be `0`).
213
220
  */
214
221
  powerW: number;
215
222
  }
@@ -87,9 +87,15 @@ export var BatteryCommandForecastDirectionEnum;
87
87
  /** Power should flow from the battery into the home / grid (discharging). */
88
88
  BatteryCommandForecastDirectionEnum["Discharge"] = "discharge";
89
89
  /**
90
- * No energy flow the battery holds its current state-of-charge.
91
- * Use to mark idle periods between charge / discharge entries. The
92
- * entry's `powerW` must be `0`.
90
+ * Leave the slot to the battery's own logic for the duration of
91
+ * the entry the appliance decides itself whether to charge,
92
+ * discharge or stand still (auto mode).
93
+ *
94
+ * This is *not* a "no energy flow" marker: to block charging or
95
+ * discharging, use {@link BatteryCommandForecastDirectionEnum.Charge}
96
+ * respectively {@link BatteryCommandForecastDirectionEnum.Discharge}
97
+ * with `powerW = 0`. An `Idle` entry prescribes no setpoint, so its
98
+ * `powerW` must be `0`.
93
99
  */
94
100
  BatteryCommandForecastDirectionEnum["Idle"] = "idle";
95
101
  })(BatteryCommandForecastDirectionEnum || (BatteryCommandForecastDirectionEnum = {}));
@@ -33,7 +33,9 @@ export declare enum EnyoApplianceStateEnum {
33
33
  * which describes connectivity. `Healthy` means the appliance is operating
34
34
  * normally; `Warning` means a non-blocking issue has been reported (the
35
35
  * appliance is still functional but should be inspected); `Faulted` means it
36
- * has reported an internal error and may need attention. Vendor- or
36
+ * has reported an internal error and may need attention; `Deactivated` means
37
+ * the appliance has been intentionally switched off from energy management and
38
+ * is neither monitored nor controlled until it is reactivated. Vendor- or
37
39
  * protocol-specific details should be conveyed via accompanying error codes.
38
40
  */
39
41
  export declare enum EnyoApplianceStatusEnum {
@@ -42,7 +44,13 @@ export declare enum EnyoApplianceStatusEnum {
42
44
  /** Appliance is operating but has reported a non-blocking issue that should be inspected */
43
45
  Warning = "warning",
44
46
  /** Appliance has reported an internal fault */
45
- Faulted = "faulted"
47
+ Faulted = "faulted",
48
+ /**
49
+ * Appliance has been intentionally deactivated and is excluded from energy
50
+ * management. It is not controlled and its health is not evaluated until it
51
+ * is reactivated.
52
+ */
53
+ Deactivated = "deactivated"
46
54
  }
47
55
  /**
48
56
  * Severity classification for an {@link EnyoApplianceErrorCode}.
@@ -175,7 +183,7 @@ export interface EnyoApplianceMetadata {
175
183
  ipAddress?: string;
176
184
  /** Connection state */
177
185
  state?: EnyoApplianceStateEnum;
178
- /** Health status of the appliance (e.g. healthy or faulted) */
186
+ /** Health status of the appliance (e.g. healthy, faulted or deactivated) */
179
187
  status?: EnyoApplianceStatusEnum;
180
188
  network?: EnyoApplianceNetworkMetadata;
181
189
  modbus?: EnyoApplianceModbusMetadata;
@@ -21,7 +21,9 @@ export var EnyoApplianceStateEnum;
21
21
  * which describes connectivity. `Healthy` means the appliance is operating
22
22
  * normally; `Warning` means a non-blocking issue has been reported (the
23
23
  * appliance is still functional but should be inspected); `Faulted` means it
24
- * has reported an internal error and may need attention. Vendor- or
24
+ * has reported an internal error and may need attention; `Deactivated` means
25
+ * the appliance has been intentionally switched off from energy management and
26
+ * is neither monitored nor controlled until it is reactivated. Vendor- or
25
27
  * protocol-specific details should be conveyed via accompanying error codes.
26
28
  */
27
29
  export var EnyoApplianceStatusEnum;
@@ -32,6 +34,12 @@ export var EnyoApplianceStatusEnum;
32
34
  EnyoApplianceStatusEnum["Warning"] = "warning";
33
35
  /** Appliance has reported an internal fault */
34
36
  EnyoApplianceStatusEnum["Faulted"] = "faulted";
37
+ /**
38
+ * Appliance has been intentionally deactivated and is excluded from energy
39
+ * management. It is not controlled and its health is not evaluated until it
40
+ * is reactivated.
41
+ */
42
+ EnyoApplianceStatusEnum["Deactivated"] = "deactivated";
35
43
  })(EnyoApplianceStatusEnum || (EnyoApplianceStatusEnum = {}));
36
44
  export var EnyoApplianceConnectionType;
37
45
  (function (EnyoApplianceConnectionType) {
@@ -593,7 +593,8 @@ export interface EnyoDataBusApplianceFlexibilityAnnouncementV1 extends EnyoDataB
593
593
  * provided together if they change in the same event. `errorCodes` carries
594
594
  * vendor- or protocol-specific codes that explain a transition into a
595
595
  * `warning` or `faulted` status; each entry's `severity` field indicates
596
- * which.
596
+ * which. A transition into `deactivated` is intentional and normally carries
597
+ * no error codes.
597
598
  */
598
599
  export interface EnyoDataBusApplianceStateUpdateV1 extends EnyoDataBusMessage {
599
600
  type: 'message';
@@ -1636,9 +1637,11 @@ export declare enum EnyoStorageScheduleModeEnum {
1636
1637
  * non-negative, and consumers never have to disambiguate `0` from
1637
1638
  * "direction-of-zero" or interpret sign conventions per integration.
1638
1639
  *
1639
- * `Idle` is the explicit "no energy flow" markeruse it to insert
1640
- * idle slots between charge / discharge periods. An entry with
1641
- * `direction = Idle` must carry `powerW = 0`.
1640
+ * `Idle` hands the slot back to the battery's own logic — it does *not*
1641
+ * mean "no energy flow". To keep the battery still, use `Charge` or
1642
+ * `Discharge` with `powerW = 0`, which blocks that direction for the
1643
+ * duration of the entry. An `Idle` entry prescribes no setpoint, so it
1644
+ * must carry `powerW = 0`.
1642
1645
  */
1643
1646
  export declare enum EnyoStorageScheduleDirectionEnum {
1644
1647
  /** Power should flow from the grid into the battery (charging). */
@@ -1646,9 +1649,15 @@ export declare enum EnyoStorageScheduleDirectionEnum {
1646
1649
  /** Power should flow from the battery into the grid / home (discharging). */
1647
1650
  Discharge = "discharge",
1648
1651
  /**
1649
- * No energy flow the battery holds its current state-of-charge.
1650
- * Use to mark idle periods between charge / discharge entries. The
1651
- * entry's `powerW` must be `0`.
1652
+ * Leave the slot to the battery's own logic for the duration of
1653
+ * the entry the appliance decides itself whether to charge,
1654
+ * discharge or stand still (auto mode).
1655
+ *
1656
+ * This is *not* a "no energy flow" marker: to block charging or
1657
+ * discharging, use {@link EnyoStorageScheduleDirectionEnum.Charge}
1658
+ * respectively {@link EnyoStorageScheduleDirectionEnum.Discharge}
1659
+ * with `powerW = 0`. An `Idle` entry prescribes no setpoint, so its
1660
+ * `powerW` must be `0`.
1652
1661
  */
1653
1662
  Idle = "idle"
1654
1663
  }
@@ -1678,10 +1687,11 @@ export interface EnyoStorageScheduleEntry {
1678
1687
  * Target power for this setpoint in Watts. Always non-negative —
1679
1688
  * direction is carried by {@link direction}, never by sign. A
1680
1689
  * `powerW` of `0` together with a `Charge` or `Discharge` direction
1681
- * means "hold at zero in the named direction" (effectively idle
1682
- * until the next entry); prefer the explicit
1683
- * {@link EnyoStorageScheduleDirectionEnum.Idle} direction for
1684
- * unambiguous idle slots, in which case `powerW` must also be `0`.
1690
+ * blocks that direction the battery must not charge respectively
1691
+ * discharge until the next entry. Use that to keep the battery
1692
+ * still, *not* {@link EnyoStorageScheduleDirectionEnum.Idle}, which
1693
+ * hands control back to the appliance's own logic and prescribes no
1694
+ * setpoint at all (its `powerW` must be `0`).
1685
1695
  */
1686
1696
  powerW: number;
1687
1697
  }
@@ -321,9 +321,11 @@ export var EnyoStorageScheduleModeEnum;
321
321
  * non-negative, and consumers never have to disambiguate `0` from
322
322
  * "direction-of-zero" or interpret sign conventions per integration.
323
323
  *
324
- * `Idle` is the explicit "no energy flow" markeruse it to insert
325
- * idle slots between charge / discharge periods. An entry with
326
- * `direction = Idle` must carry `powerW = 0`.
324
+ * `Idle` hands the slot back to the battery's own logic — it does *not*
325
+ * mean "no energy flow". To keep the battery still, use `Charge` or
326
+ * `Discharge` with `powerW = 0`, which blocks that direction for the
327
+ * duration of the entry. An `Idle` entry prescribes no setpoint, so it
328
+ * must carry `powerW = 0`.
327
329
  */
328
330
  export var EnyoStorageScheduleDirectionEnum;
329
331
  (function (EnyoStorageScheduleDirectionEnum) {
@@ -332,9 +334,15 @@ export var EnyoStorageScheduleDirectionEnum;
332
334
  /** Power should flow from the battery into the grid / home (discharging). */
333
335
  EnyoStorageScheduleDirectionEnum["Discharge"] = "discharge";
334
336
  /**
335
- * No energy flow the battery holds its current state-of-charge.
336
- * Use to mark idle periods between charge / discharge entries. The
337
- * entry's `powerW` must be `0`.
337
+ * Leave the slot to the battery's own logic for the duration of
338
+ * the entry the appliance decides itself whether to charge,
339
+ * discharge or stand still (auto mode).
340
+ *
341
+ * This is *not* a "no energy flow" marker: to block charging or
342
+ * discharging, use {@link EnyoStorageScheduleDirectionEnum.Charge}
343
+ * respectively {@link EnyoStorageScheduleDirectionEnum.Discharge}
344
+ * with `powerW = 0`. An `Idle` entry prescribes no setpoint, so its
345
+ * `powerW` must be `0`.
338
346
  */
339
347
  EnyoStorageScheduleDirectionEnum["Idle"] = "idle";
340
348
  })(EnyoStorageScheduleDirectionEnum || (EnyoStorageScheduleDirectionEnum = {}));
@@ -20,7 +20,7 @@
20
20
  * (`../implementations/onboarding-v2/onboarding-v2-validators.ts`) to fail fast
21
21
  * before publishing.
22
22
  */
23
- import type { EnyoOnboardingGuideCategory, EnyoOnboardingTranslatedContent } from './enyo-onboarding.js';
23
+ import type { EnyoOnboardingTranslatedContent } from './enyo-onboarding.js';
24
24
  /**
25
25
  * The situation a flow starts from. A vendor/model can have up to one guide per
26
26
  * variant; a branch can jump into another variant's flow
@@ -1067,21 +1067,6 @@ export interface EnyoOnboardingV2Guide {
1067
1067
  title: EnyoOnboardingTranslatedContent[];
1068
1068
  /** Which start situation this guide covers. */
1069
1069
  startVariant: EnyoOnboardingV2StartVariant;
1070
- /**
1071
- * The lifecycle role this guide plays, which decides where the host offers
1072
- * it (an "add new device" entry point, the configuration-required prompt).
1073
- *
1074
- * Distinct from {@link startVariant}, which describes the *situation the
1075
- * flow starts from* (device not found, found but unconfigured, …). This says
1076
- * *why the installer is here at all*: configuring the package for the first
1077
- * time is a different entry point from adding a second device to a package
1078
- * that already works, even when both start from `device-not-found`.
1079
- *
1080
- * Shared with the v1 model rather than restated as a v2 enum — the category
1081
- * is a property of a guide's role, not of the authoring model. Defaults to
1082
- * {@link EnyoOnboardingGuideCategory.InitialSetup} semantics when omitted.
1083
- */
1084
- category?: EnyoOnboardingGuideCategory;
1085
1070
  /**
1086
1071
  * Whether the host runs its local network scan before entering this guide.
1087
1072
  *
@@ -14,11 +14,9 @@ export interface EnyoOnboardingTranslatedContent {
14
14
  * Used to distinguish guides that perform different roles, e.g. initial
15
15
  * configuration of a package vs. adding or reconnecting a single device.
16
16
  *
17
- * Shared by both onboarding models v2 guides set it through
18
- * {@link EnyoOnboardingV2Guide.category} in the same way both reuse
19
- * {@link EnyoOnboardingTranslatedContent}. Not deprecated with the rest of v1:
20
- * the category is a property of a guide's role, not of the authoring model it
21
- * was written in.
17
+ * @deprecated Superseded by the v2 graph model. Author guides with
18
+ * {@link EnyoOnboardingV2Guide} via `defineOnboardingGuideV2()`. v1 is retained
19
+ * for backward compatibility and will be removed in a future major.
22
20
  */
23
21
  export declare enum EnyoOnboardingGuideCategory {
24
22
  /** Initial package configuration — shown when EnergyAppStateEnum is 'configuration-required' */
@@ -3,11 +3,9 @@
3
3
  * Used to distinguish guides that perform different roles, e.g. initial
4
4
  * configuration of a package vs. adding or reconnecting a single device.
5
5
  *
6
- * Shared by both onboarding models v2 guides set it through
7
- * {@link EnyoOnboardingV2Guide.category} in the same way both reuse
8
- * {@link EnyoOnboardingTranslatedContent}. Not deprecated with the rest of v1:
9
- * the category is a property of a guide's role, not of the authoring model it
10
- * was written in.
6
+ * @deprecated Superseded by the v2 graph model. Author guides with
7
+ * {@link EnyoOnboardingV2Guide} via `defineOnboardingGuideV2()`. v1 is retained
8
+ * for backward compatibility and will be removed in a future major.
11
9
  */
12
10
  export var EnyoOnboardingGuideCategory;
13
11
  (function (EnyoOnboardingGuideCategory) {
package/dist/version.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "1.7.0";
8
+ export declare const SDK_VERSION = "1.9.0";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/dist/version.js CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export const SDK_VERSION = '1.7.0';
8
+ export const SDK_VERSION = '1.9.0';
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enyo-energy/energy-app-sdk",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "enyo Energy App SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",