@enyo-energy/energy-app-sdk 1.8.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.
- package/README.md +80 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/packages/energy-app-modbus.cjs +64 -0
- package/dist/cjs/packages/energy-app-modbus.d.cts +159 -0
- package/dist/cjs/types/enyo-appliance-command-forecast.cjs +9 -3
- package/dist/cjs/types/enyo-appliance-command-forecast.d.cts +15 -8
- package/dist/cjs/types/enyo-data-bus-value.cjs +14 -6
- package/dist/cjs/types/enyo-data-bus-value.d.cts +19 -10
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/packages/energy-app-modbus.d.ts +159 -0
- package/dist/packages/energy-app-modbus.js +62 -1
- package/dist/types/enyo-appliance-command-forecast.d.ts +15 -8
- package/dist/types/enyo-appliance-command-forecast.js +9 -3
- package/dist/types/enyo-data-bus-value.d.ts +19 -10
- package/dist/types/enyo-data-bus-value.js +14 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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:
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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);
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -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
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
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
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
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
|
|
209
|
-
* the
|
|
210
|
-
*
|
|
211
|
-
* {@link BatteryCommandForecastDirectionEnum.Idle}
|
|
212
|
-
*
|
|
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
|
}
|
|
@@ -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`
|
|
328
|
-
*
|
|
329
|
-
* `
|
|
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
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
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 = {}));
|
|
@@ -1637,9 +1637,11 @@ export declare enum EnyoStorageScheduleModeEnum {
|
|
|
1637
1637
|
* non-negative, and consumers never have to disambiguate `0` from
|
|
1638
1638
|
* "direction-of-zero" or interpret sign conventions per integration.
|
|
1639
1639
|
*
|
|
1640
|
-
* `Idle`
|
|
1641
|
-
*
|
|
1642
|
-
* `
|
|
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`.
|
|
1643
1645
|
*/
|
|
1644
1646
|
export declare enum EnyoStorageScheduleDirectionEnum {
|
|
1645
1647
|
/** Power should flow from the grid into the battery (charging). */
|
|
@@ -1647,9 +1649,15 @@ export declare enum EnyoStorageScheduleDirectionEnum {
|
|
|
1647
1649
|
/** Power should flow from the battery into the grid / home (discharging). */
|
|
1648
1650
|
Discharge = "discharge",
|
|
1649
1651
|
/**
|
|
1650
|
-
*
|
|
1651
|
-
*
|
|
1652
|
-
*
|
|
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`.
|
|
1653
1661
|
*/
|
|
1654
1662
|
Idle = "idle"
|
|
1655
1663
|
}
|
|
@@ -1679,10 +1687,11 @@ export interface EnyoStorageScheduleEntry {
|
|
|
1679
1687
|
* Target power for this setpoint in Watts. Always non-negative —
|
|
1680
1688
|
* direction is carried by {@link direction}, never by sign. A
|
|
1681
1689
|
* `powerW` of `0` together with a `Charge` or `Discharge` direction
|
|
1682
|
-
*
|
|
1683
|
-
* until the next entry
|
|
1684
|
-
* {@link EnyoStorageScheduleDirectionEnum.Idle}
|
|
1685
|
-
*
|
|
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`).
|
|
1686
1695
|
*/
|
|
1687
1696
|
powerW: number;
|
|
1688
1697
|
}
|
package/dist/cjs/version.cjs
CHANGED
package/dist/cjs/version.d.cts
CHANGED
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
|
-
|
|
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
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
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
|
|
209
|
-
* the
|
|
210
|
-
*
|
|
211
|
-
* {@link BatteryCommandForecastDirectionEnum.Idle}
|
|
212
|
-
*
|
|
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
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
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 = {}));
|
|
@@ -1637,9 +1637,11 @@ export declare enum EnyoStorageScheduleModeEnum {
|
|
|
1637
1637
|
* non-negative, and consumers never have to disambiguate `0` from
|
|
1638
1638
|
* "direction-of-zero" or interpret sign conventions per integration.
|
|
1639
1639
|
*
|
|
1640
|
-
* `Idle`
|
|
1641
|
-
*
|
|
1642
|
-
* `
|
|
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`.
|
|
1643
1645
|
*/
|
|
1644
1646
|
export declare enum EnyoStorageScheduleDirectionEnum {
|
|
1645
1647
|
/** Power should flow from the grid into the battery (charging). */
|
|
@@ -1647,9 +1649,15 @@ export declare enum EnyoStorageScheduleDirectionEnum {
|
|
|
1647
1649
|
/** Power should flow from the battery into the grid / home (discharging). */
|
|
1648
1650
|
Discharge = "discharge",
|
|
1649
1651
|
/**
|
|
1650
|
-
*
|
|
1651
|
-
*
|
|
1652
|
-
*
|
|
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`.
|
|
1653
1661
|
*/
|
|
1654
1662
|
Idle = "idle"
|
|
1655
1663
|
}
|
|
@@ -1679,10 +1687,11 @@ export interface EnyoStorageScheduleEntry {
|
|
|
1679
1687
|
* Target power for this setpoint in Watts. Always non-negative —
|
|
1680
1688
|
* direction is carried by {@link direction}, never by sign. A
|
|
1681
1689
|
* `powerW` of `0` together with a `Charge` or `Discharge` direction
|
|
1682
|
-
*
|
|
1683
|
-
* until the next entry
|
|
1684
|
-
* {@link EnyoStorageScheduleDirectionEnum.Idle}
|
|
1685
|
-
*
|
|
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`).
|
|
1686
1695
|
*/
|
|
1687
1696
|
powerW: number;
|
|
1688
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`
|
|
325
|
-
*
|
|
326
|
-
* `
|
|
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
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
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 = {}));
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED