@enyo-energy/energy-app-sdk 1.8.0 → 1.10.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/implementations/appliances/appliance-manager.cjs +2 -0
  3. package/dist/cjs/implementations/appliances/appliance-manager.d.cts +4 -0
  4. package/dist/cjs/index.cjs +2 -0
  5. package/dist/cjs/index.d.cts +2 -0
  6. package/dist/cjs/packages/energy-app-modbus.cjs +64 -0
  7. package/dist/cjs/packages/energy-app-modbus.d.cts +159 -0
  8. package/dist/cjs/types/enyo-appliance-command-forecast.cjs +9 -3
  9. package/dist/cjs/types/enyo-appliance-command-forecast.d.cts +15 -8
  10. package/dist/cjs/types/enyo-appliance.cjs +2 -0
  11. package/dist/cjs/types/enyo-appliance.d.cts +6 -1
  12. package/dist/cjs/types/enyo-data-bus-value.cjs +18 -6
  13. package/dist/cjs/types/enyo-data-bus-value.d.cts +89 -10
  14. package/dist/cjs/types/enyo-smart-plug-appliance.cjs +74 -0
  15. package/dist/cjs/types/enyo-smart-plug-appliance.d.cts +173 -0
  16. package/dist/cjs/version.cjs +1 -1
  17. package/dist/cjs/version.d.cts +1 -1
  18. package/dist/implementations/appliances/appliance-manager.d.ts +4 -0
  19. package/dist/implementations/appliances/appliance-manager.js +2 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.js +2 -0
  22. package/dist/packages/energy-app-modbus.d.ts +159 -0
  23. package/dist/packages/energy-app-modbus.js +62 -1
  24. package/dist/types/enyo-appliance-command-forecast.d.ts +15 -8
  25. package/dist/types/enyo-appliance-command-forecast.js +9 -3
  26. package/dist/types/enyo-appliance.d.ts +6 -1
  27. package/dist/types/enyo-appliance.js +2 -0
  28. package/dist/types/enyo-data-bus-value.d.ts +89 -10
  29. package/dist/types/enyo-data-bus-value.js +18 -6
  30. package/dist/types/enyo-smart-plug-appliance.d.ts +173 -0
  31. package/dist/types/enyo-smart-plug-appliance.js +71 -0
  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:
@@ -60,6 +60,7 @@ const MERGEABLE_METADATA_KEYS = [
60
60
  'temperatureSensor',
61
61
  'airConditioning',
62
62
  'heatingRod',
63
+ 'smartPlug',
63
64
  ];
64
65
  /**
65
66
  * Manages appliances in the energy system with configurable identification strategies.
@@ -209,6 +210,7 @@ class ApplianceManager {
209
210
  temperatureSensor: appliance.temperatureSensor,
210
211
  airConditioning: appliance.airConditioning,
211
212
  heatingRod: appliance.heatingRod,
213
+ smartPlug: appliance.smartPlug,
212
214
  // Conditionally spread the two optional top-level fields that are NOT
213
215
  // covered by MERGEABLE_METADATA_KEYS. If they were always materialized
214
216
  // as explicit keys, an omitted (undefined) value would clobber the
@@ -10,6 +10,7 @@ import type { EnyoMeterAppliance } from "../../types/enyo-meter-appliance.cjs";
10
10
  import type { EnyoTemperatureSensorApplianceMetadata } from "../../types/enyo-temperature-sensor-appliance.cjs";
11
11
  import type { EnyoAirConditioningApplianceMetadata } from "../../types/enyo-air-conditioning-appliance.cjs";
12
12
  import type { EnyoHeatingRodApplianceMetadata } from "../../types/enyo-heating-rod-appliance.cjs";
13
+ import type { EnyoSmartPlugApplianceMetadata } from "../../types/enyo-smart-plug-appliance.cjs";
13
14
  import { IdentifierStrategy } from "./identifier-strategies.cjs";
14
15
  /**
15
16
  * Thrown when {@link ApplianceManager.createOrUpdateAppliance} is called with
@@ -62,6 +63,7 @@ export interface ApplianceConfig {
62
63
  temperatureSensor?: EnyoTemperatureSensorApplianceMetadata;
63
64
  airConditioning?: EnyoAirConditioningApplianceMetadata;
64
65
  heatingRod?: EnyoHeatingRodApplianceMetadata;
66
+ smartPlug?: EnyoSmartPlugApplianceMetadata;
65
67
  availableFeatures?: EnyoApplianceAvailableFeaturesEnum[];
66
68
  /**
67
69
  * Optional identifier of the cloud-deployed energy app package that manages
@@ -437,6 +439,8 @@ export interface PartialEnyoAppliance {
437
439
  airConditioning?: Partial<EnyoAirConditioningApplianceMetadata>;
438
440
  /** Optional Metadata of the Appliance if of type HeatingRod */
439
441
  heatingRod?: Partial<EnyoHeatingRodApplianceMetadata>;
442
+ /** Optional Metadata of the Appliance if of type SmartPlug */
443
+ smartPlug?: Partial<EnyoSmartPlugApplianceMetadata>;
440
444
  /** Optional custom name for the appliance, defined by the user */
441
445
  customName?: string;
442
446
  /**
@@ -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);
@@ -85,6 +86,7 @@ __exportStar(require("./types/enyo-configuration-manager.cjs"), exports);
85
86
  __exportStar(require("./packages/energy-app-configuration-manager.cjs"), exports);
86
87
  __exportStar(require("./types/enyo-air-conditioning-appliance.cjs"), exports);
87
88
  __exportStar(require("./types/enyo-heating-rod-appliance.cjs"), exports);
89
+ __exportStar(require("./types/enyo-smart-plug-appliance.cjs"), exports);
88
90
  __exportStar(require("./types/enyo-charger-appliance.cjs"), exports);
89
91
  __exportStar(require("./types/enyo-charging-card.cjs"), exports);
90
92
  __exportStar(require("./packages/energy-app-charging-card.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';
@@ -69,6 +70,7 @@ export * from './types/enyo-configuration-manager.cjs';
69
70
  export * from './packages/energy-app-configuration-manager.cjs';
70
71
  export * from './types/enyo-air-conditioning-appliance.cjs';
71
72
  export * from './types/enyo-heating-rod-appliance.cjs';
73
+ export * from './types/enyo-smart-plug-appliance.cjs';
72
74
  export * from './types/enyo-charger-appliance.cjs';
73
75
  export * from './types/enyo-charging-card.cjs';
74
76
  export * from './packages/energy-app-charging-card.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
  }
@@ -11,6 +11,8 @@ var EnyoApplianceTypeEnum;
11
11
  EnyoApplianceTypeEnum["AirConditioning"] = "AirConditioning";
12
12
  EnyoApplianceTypeEnum["TemperatureSensor"] = "TemperatureSensor";
13
13
  EnyoApplianceTypeEnum["HeatingRod"] = "HeatingRod";
14
+ /** Switchable socket / relay channel powering an arbitrary load (e.g. a Shelly channel) */
15
+ EnyoApplianceTypeEnum["SmartPlug"] = "SmartPlug";
14
16
  })(EnyoApplianceTypeEnum || (exports.EnyoApplianceTypeEnum = EnyoApplianceTypeEnum = {}));
15
17
  var EnyoApplianceStateEnum;
16
18
  (function (EnyoApplianceStateEnum) {
@@ -8,6 +8,7 @@ import { EnyoMeterAppliance } from "./enyo-meter-appliance.cjs";
8
8
  import { EnyoTemperatureSensorApplianceMetadata } from "./enyo-temperature-sensor-appliance.cjs";
9
9
  import { EnyoAirConditioningApplianceMetadata } from "./enyo-air-conditioning-appliance.cjs";
10
10
  import { EnyoHeatingRodApplianceMetadata } from "./enyo-heating-rod-appliance.cjs";
11
+ import { EnyoSmartPlugApplianceMetadata } from "./enyo-smart-plug-appliance.cjs";
11
12
  export declare enum EnyoApplianceTypeEnum {
12
13
  Inverter = "Inverter",
13
14
  Charger = "Charger",
@@ -16,7 +17,9 @@ export declare enum EnyoApplianceTypeEnum {
16
17
  Heatpump = "Heatpump",
17
18
  AirConditioning = "AirConditioning",
18
19
  TemperatureSensor = "TemperatureSensor",
19
- HeatingRod = "HeatingRod"
20
+ HeatingRod = "HeatingRod",
21
+ /** Switchable socket / relay channel powering an arbitrary load (e.g. a Shelly channel) */
22
+ SmartPlug = "SmartPlug"
20
23
  }
21
24
  export interface EnyoApplianceName {
22
25
  language: EnergyAppPackageLanguage;
@@ -270,6 +273,8 @@ export interface EnyoAppliance {
270
273
  airConditioning?: EnyoAirConditioningApplianceMetadata;
271
274
  /** Optional Metadata of the Appliance if of type HeatingRod */
272
275
  heatingRod?: EnyoHeatingRodApplianceMetadata;
276
+ /** Optional Metadata of the Appliance if of type SmartPlug */
277
+ smartPlug?: EnyoSmartPlugApplianceMetadata;
273
278
  /** Optional custom name for the appliance, defined by the user */
274
279
  customName?: string;
275
280
  /**
@@ -280,6 +280,10 @@ var EnyoDataBusMessageEnum;
280
280
  EnyoDataBusMessageEnum["SetHeatingRodAvailablePowerV2"] = "SetHeatingRodAvailablePowerV2";
281
281
  /** V2 control command: prescribe a single-setpoint control (mode + direction + power) to a battery/storage appliance. */
282
282
  EnyoDataBusMessageEnum["SetStorageControlV2"] = "SetStorageControlV2";
283
+ /** Live values of a smart plug: relay state, power draw and energy meter reading. */
284
+ EnyoDataBusMessageEnum["SmartPlugValuesUpdateV1"] = "SmartPlugValuesUpdateV1";
285
+ /** Control command: switch a smart plug / relay channel on or off. */
286
+ EnyoDataBusMessageEnum["SetSmartPlugSwitchV1"] = "SetSmartPlugSwitchV1";
283
287
  EnyoDataBusMessageEnum["EnergyAppStartedV1"] = "EnergyAppStartedV1";
284
288
  })(EnyoDataBusMessageEnum || (exports.EnyoDataBusMessageEnum = EnyoDataBusMessageEnum = {}));
285
289
  /**
@@ -324,9 +328,11 @@ var EnyoStorageScheduleModeEnum;
324
328
  * non-negative, and consumers never have to disambiguate `0` from
325
329
  * "direction-of-zero" or interpret sign conventions per integration.
326
330
  *
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`.
331
+ * `Idle` hands the slot back to the battery's own logic — it does *not*
332
+ * mean "no energy flow". To keep the battery still, use `Charge` or
333
+ * `Discharge` with `powerW = 0`, which blocks that direction for the
334
+ * duration of the entry. An `Idle` entry prescribes no setpoint, so it
335
+ * must carry `powerW = 0`.
330
336
  */
331
337
  var EnyoStorageScheduleDirectionEnum;
332
338
  (function (EnyoStorageScheduleDirectionEnum) {
@@ -335,9 +341,15 @@ var EnyoStorageScheduleDirectionEnum;
335
341
  /** Power should flow from the battery into the grid / home (discharging). */
336
342
  EnyoStorageScheduleDirectionEnum["Discharge"] = "discharge";
337
343
  /**
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`.
344
+ * Leave the slot to the battery's own logic for the duration of
345
+ * the entry the appliance decides itself whether to charge,
346
+ * discharge or stand still (auto mode).
347
+ *
348
+ * This is *not* a "no energy flow" marker: to block charging or
349
+ * discharging, use {@link EnyoStorageScheduleDirectionEnum.Charge}
350
+ * respectively {@link EnyoStorageScheduleDirectionEnum.Discharge}
351
+ * with `powerW = 0`. An `Idle` entry prescribes no setpoint, so its
352
+ * `powerW` must be `0`.
341
353
  */
342
354
  EnyoStorageScheduleDirectionEnum["Idle"] = "idle";
343
355
  })(EnyoStorageScheduleDirectionEnum || (exports.EnyoStorageScheduleDirectionEnum = EnyoStorageScheduleDirectionEnum = {}));