@enyo-energy/energy-app-sdk 0.0.138 → 0.0.140
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/dist/cjs/implementations/storage/storage-schedule-handler.cjs +348 -0
- package/dist/cjs/implementations/storage/storage-schedule-handler.d.cts +300 -0
- package/dist/cjs/index.cjs +3 -0
- package/dist/cjs/index.d.cts +3 -0
- package/dist/cjs/integrations/storage-integration-energy-app.cjs +4 -12
- package/dist/cjs/integrations/storage-integration-energy-app.d.cts +16 -34
- package/dist/cjs/packages/eebus/eebus-cevc-client.cjs +2 -0
- package/dist/cjs/packages/eebus/eebus-cevc-client.d.cts +91 -0
- package/dist/cjs/packages/eebus/eebus-evcc-client.cjs +2 -0
- package/dist/cjs/packages/eebus/eebus-evcc-client.d.cts +84 -0
- package/dist/cjs/packages/eebus/eebus-evcem-client.cjs +2 -0
- package/dist/cjs/packages/eebus/eebus-evcem-client.d.cts +53 -0
- package/dist/cjs/packages/eebus/eebus-evsecc-client.cjs +2 -0
- package/dist/cjs/packages/eebus/eebus-evsecc-client.d.cts +70 -0
- package/dist/cjs/packages/eebus/eebus-evsoc-client.cjs +2 -0
- package/dist/cjs/packages/eebus/eebus-evsoc-client.d.cts +52 -0
- package/dist/cjs/packages/eebus/eebus-feature-catalog.d.cts +57 -1
- package/dist/cjs/packages/eebus/eebus-hvac-client.d.cts +36 -0
- package/dist/cjs/packages/eebus/eebus-lpc-client.d.cts +31 -0
- package/dist/cjs/packages/eebus/eebus-mpc-client.d.cts +53 -0
- package/dist/cjs/packages/eebus/eebus-ohpcf-client.d.cts +72 -2
- package/dist/cjs/packages/eebus/eebus-opev-client.cjs +2 -0
- package/dist/cjs/packages/eebus/eebus-opev-client.d.cts +78 -0
- package/dist/cjs/packages/eebus/eebus-oscev-client.cjs +2 -0
- package/dist/cjs/packages/eebus/eebus-oscev-client.d.cts +66 -0
- package/dist/cjs/packages/eebus/eebus-setpoint-client.d.cts +37 -0
- package/dist/cjs/packages/eebus/eebus-spine-low-level.d.cts +36 -4
- package/dist/cjs/packages/eebus/eebus-use-case-registry.d.cts +164 -10
- package/dist/cjs/packages/eebus/eebus-vabd-client.cjs +2 -0
- package/dist/cjs/packages/eebus/eebus-vabd-client.d.cts +70 -0
- package/dist/cjs/packages/eebus/eebus-vapd-client.cjs +2 -0
- package/dist/cjs/packages/eebus/eebus-vapd-client.d.cts +60 -0
- package/dist/cjs/packages/eebus/energy-app-eebus.d.cts +15 -6
- package/dist/cjs/types/enyo-data-bus-value.cjs +39 -1
- package/dist/cjs/types/enyo-data-bus-value.d.cts +150 -1
- package/dist/cjs/types/enyo-eebus-errors.cjs +140 -0
- package/dist/cjs/types/enyo-eebus-errors.d.cts +110 -0
- package/dist/cjs/types/enyo-eebus-features.cjs +0 -15
- package/dist/cjs/types/enyo-eebus-features.d.cts +20 -6
- package/dist/cjs/types/enyo-eebus-use-cases.cjs +22 -15
- package/dist/cjs/types/enyo-eebus-use-cases.d.cts +321 -0
- package/dist/cjs/types/enyo-eebus.d.cts +53 -2
- package/dist/cjs/types/enyo-spine.cjs +172 -0
- package/dist/cjs/types/enyo-spine.d.cts +158 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/implementations/storage/storage-schedule-handler.d.ts +300 -0
- package/dist/implementations/storage/storage-schedule-handler.js +344 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/integrations/storage-integration-energy-app.d.ts +16 -34
- package/dist/integrations/storage-integration-energy-app.js +4 -12
- package/dist/packages/eebus/eebus-cevc-client.d.ts +91 -0
- package/dist/packages/eebus/eebus-cevc-client.js +1 -0
- package/dist/packages/eebus/eebus-evcc-client.d.ts +84 -0
- package/dist/packages/eebus/eebus-evcc-client.js +1 -0
- package/dist/packages/eebus/eebus-evcem-client.d.ts +53 -0
- package/dist/packages/eebus/eebus-evcem-client.js +1 -0
- package/dist/packages/eebus/eebus-evsecc-client.d.ts +70 -0
- package/dist/packages/eebus/eebus-evsecc-client.js +1 -0
- package/dist/packages/eebus/eebus-evsoc-client.d.ts +52 -0
- package/dist/packages/eebus/eebus-evsoc-client.js +1 -0
- package/dist/packages/eebus/eebus-feature-catalog.d.ts +57 -1
- package/dist/packages/eebus/eebus-hvac-client.d.ts +36 -0
- package/dist/packages/eebus/eebus-lpc-client.d.ts +31 -0
- package/dist/packages/eebus/eebus-mpc-client.d.ts +53 -0
- package/dist/packages/eebus/eebus-ohpcf-client.d.ts +72 -2
- package/dist/packages/eebus/eebus-opev-client.d.ts +78 -0
- package/dist/packages/eebus/eebus-opev-client.js +1 -0
- package/dist/packages/eebus/eebus-oscev-client.d.ts +66 -0
- package/dist/packages/eebus/eebus-oscev-client.js +1 -0
- package/dist/packages/eebus/eebus-setpoint-client.d.ts +37 -0
- package/dist/packages/eebus/eebus-spine-low-level.d.ts +36 -4
- package/dist/packages/eebus/eebus-use-case-registry.d.ts +164 -10
- package/dist/packages/eebus/eebus-vabd-client.d.ts +70 -0
- package/dist/packages/eebus/eebus-vabd-client.js +1 -0
- package/dist/packages/eebus/eebus-vapd-client.d.ts +60 -0
- package/dist/packages/eebus/eebus-vapd-client.js +1 -0
- package/dist/packages/eebus/energy-app-eebus.d.ts +15 -6
- package/dist/types/enyo-data-bus-value.d.ts +150 -1
- package/dist/types/enyo-data-bus-value.js +38 -0
- package/dist/types/enyo-eebus-errors.d.ts +110 -0
- package/dist/types/enyo-eebus-errors.js +132 -0
- package/dist/types/enyo-eebus-features.d.ts +20 -6
- package/dist/types/enyo-eebus-features.js +0 -15
- package/dist/types/enyo-eebus-use-cases.d.ts +321 -0
- package/dist/types/enyo-eebus-use-cases.js +20 -15
- package/dist/types/enyo-eebus.d.ts +53 -2
- package/dist/types/enyo-spine.d.ts +158 -0
- package/dist/types/enyo-spine.js +169 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StorageScheduleHandler = void 0;
|
|
4
|
+
const enyo_data_bus_value_js_1 = require("../../types/enyo-data-bus-value.cjs");
|
|
5
|
+
/**
|
|
6
|
+
* Abstract base class that drives a battery / storage appliance
|
|
7
|
+
* through an {@link EnyoDataBusSetStorageScheduleV1}-style relative
|
|
8
|
+
* schedule on a 1-second tick.
|
|
9
|
+
*
|
|
10
|
+
* Subclasses **must** implement three lifecycle hooks; the handler
|
|
11
|
+
* cannot operate without them, so they are declared `abstract` rather
|
|
12
|
+
* than optional callbacks on the options bag:
|
|
13
|
+
*
|
|
14
|
+
* - {@link onInit} — an `async` setup hook called once per schedule
|
|
15
|
+
* install. Returns a `TRegisters` payload (a snapshot of the
|
|
16
|
+
* pre-schedule device state, the initial register values, anything
|
|
17
|
+
* the subclass needs to restore later) or throws to abort the
|
|
18
|
+
* install. The returned registers are persisted to
|
|
19
|
+
* {@link StorageScheduleHandlerOptions.storage} so a process that
|
|
20
|
+
* dies mid-schedule still gets a rollback next time it starts.
|
|
21
|
+
* - {@link onChange} — fires for every active-entry transition,
|
|
22
|
+
* including the first one (the entry at index 0, with
|
|
23
|
+
* `previous === undefined`). This is where subclasses apply the
|
|
24
|
+
* setpoint to the underlying battery driver.
|
|
25
|
+
* - {@link onRollback} — fires when a previously-running schedule is
|
|
26
|
+
* released (mode switched to auto, replaced, invalid input from the
|
|
27
|
+
* running state, handler disposed, or restart recovery). Receives
|
|
28
|
+
* the `TRegisters` originally returned by `onInit` so the subclass
|
|
29
|
+
* can restore the pre-schedule state.
|
|
30
|
+
*
|
|
31
|
+
* Subclasses **may** override {@link onError} to observe invalid
|
|
32
|
+
* input; the default implementation is a no-op.
|
|
33
|
+
*
|
|
34
|
+
* Wall-clock time is the source of truth for entry advancement; the
|
|
35
|
+
* `'1s'` interval is the sampling rate. Tests pump time
|
|
36
|
+
* deterministically via the injected `now` seam without
|
|
37
|
+
* `vi.useFakeTimers`.
|
|
38
|
+
*
|
|
39
|
+
* All public mutating methods serialise through an internal operation
|
|
40
|
+
* chain, so a rapid sequence of `applySchedule` calls observes them
|
|
41
|
+
* in order without races even though `onInit` and storage I/O are
|
|
42
|
+
* async.
|
|
43
|
+
*
|
|
44
|
+
* @typeParam TRegisters - The shape the subclass uses to remember
|
|
45
|
+
* pre-schedule state. Must be JSON-serialisable
|
|
46
|
+
* since it is round-tripped through
|
|
47
|
+
* {@link EnergyAppStorage}.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* interface MyRegisters { previousMode: string; previousLimitW: number; }
|
|
52
|
+
*
|
|
53
|
+
* class MyStorage extends StorageScheduleHandler<MyRegisters> {
|
|
54
|
+
* protected async onInit(): Promise<MyRegisters> {
|
|
55
|
+
* return {
|
|
56
|
+
* previousMode: await this.driver.readMode(),
|
|
57
|
+
* previousLimitW: await this.driver.readLimit(),
|
|
58
|
+
* };
|
|
59
|
+
* }
|
|
60
|
+
* protected onChange(active: ActiveStorageScheduleEntry): void {
|
|
61
|
+
* this.driver.applySetpoint(active.entry.direction, active.entry.powerW);
|
|
62
|
+
* }
|
|
63
|
+
* protected onRollback(registers: MyRegisters): void {
|
|
64
|
+
* this.driver.applyMode(registers.previousMode);
|
|
65
|
+
* this.driver.applyLimit(registers.previousLimitW);
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
class StorageScheduleHandler {
|
|
71
|
+
options;
|
|
72
|
+
now;
|
|
73
|
+
storageKey;
|
|
74
|
+
listenerId;
|
|
75
|
+
state = { kind: 'idle' };
|
|
76
|
+
disposed = false;
|
|
77
|
+
operationChain = Promise.resolve();
|
|
78
|
+
/**
|
|
79
|
+
* @param options - {@link StorageScheduleHandlerOptions}. Required:
|
|
80
|
+
* `dataBus`, `interval`, `storage`. Optional:
|
|
81
|
+
* `applianceId`, `now`.
|
|
82
|
+
*/
|
|
83
|
+
constructor(options) {
|
|
84
|
+
this.options = options;
|
|
85
|
+
this.now = options.now ?? (() => Date.now());
|
|
86
|
+
this.storageKey = `storage-schedule-handler:${options.applianceId ?? 'null'}`;
|
|
87
|
+
this.listenerId = options.dataBus.listenForMessages([enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.SetStorageScheduleV1],
|
|
88
|
+
// Returns the in-flight promise so a runtime / test that
|
|
89
|
+
// wants to await message processing can; the SDK contract
|
|
90
|
+
// is fire-and-forget so this is purely additive (TypeScript
|
|
91
|
+
// allows returning a Promise from a void-returning callback).
|
|
92
|
+
(msg) => this.applyMessage(msg).catch((err) => {
|
|
93
|
+
this.onError(err instanceof Error ? err : new Error(String(err)));
|
|
94
|
+
}));
|
|
95
|
+
this.enqueue(() => this.tryRecoverFromStorage());
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Invoked when {@link applySchedule} receives invalid input
|
|
99
|
+
* (empty schedule while `mode === 'schedule'`, unsorted /
|
|
100
|
+
* duplicate / negative `seconds`, non-finite `powerW`, missing
|
|
101
|
+
* direction) **or** when {@link onInit} throws. The default
|
|
102
|
+
* implementation is a no-op; subclasses may override to log or
|
|
103
|
+
* surface the error.
|
|
104
|
+
*
|
|
105
|
+
* After `onError`, the handler always lands in a defined state
|
|
106
|
+
* (idle), regardless of whether `onError` is observed.
|
|
107
|
+
*
|
|
108
|
+
* @param _err - Description of what went wrong.
|
|
109
|
+
*/
|
|
110
|
+
onError(_err) {
|
|
111
|
+
// Default: no-op. Subclasses may override to surface errors.
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Apply a schedule directly, bypassing the data bus. Useful when
|
|
115
|
+
* the schedule comes from a UI control, an optimizer, or a replay
|
|
116
|
+
* of stored plans. Equivalent to receiving an
|
|
117
|
+
* {@link EnyoDataBusSetStorageScheduleV1} whose `data` matches
|
|
118
|
+
* `input`.
|
|
119
|
+
*
|
|
120
|
+
* Disposed handlers silently ignore the call. Concurrent calls
|
|
121
|
+
* are serialised internally — the returned promise resolves once
|
|
122
|
+
* the call's effects (including any preceding rollback and any
|
|
123
|
+
* `onInit` await) are complete.
|
|
124
|
+
*
|
|
125
|
+
* @param input - {@link StorageScheduleInput} carrying mode and
|
|
126
|
+
* optional schedule.
|
|
127
|
+
*/
|
|
128
|
+
applySchedule(input) {
|
|
129
|
+
return this.enqueue(() => this.doApplySchedule(input));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Apply an incoming {@link EnyoDataBusSetStorageScheduleV1} message.
|
|
133
|
+
* Messages whose `applianceId` does not match the handler's bound
|
|
134
|
+
* appliance are silently ignored — the data-bus subscription
|
|
135
|
+
* registered in the constructor routes through this method.
|
|
136
|
+
*
|
|
137
|
+
* @param msg - The data-bus message.
|
|
138
|
+
*/
|
|
139
|
+
applyMessage(msg) {
|
|
140
|
+
if (this.options.applianceId !== undefined && msg.applianceId !== this.options.applianceId) {
|
|
141
|
+
return Promise.resolve();
|
|
142
|
+
}
|
|
143
|
+
return this.applySchedule({
|
|
144
|
+
mode: msg.data.mode,
|
|
145
|
+
relativeSchedule: msg.data.relativeSchedule,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Snapshot of the currently-active entry, or `undefined` when the
|
|
150
|
+
* handler is idle (no schedule installed, or
|
|
151
|
+
* {@link EnyoStorageScheduleModeEnum.Auto} has been applied).
|
|
152
|
+
*/
|
|
153
|
+
getActiveEntry() {
|
|
154
|
+
if (this.state.kind !== 'running') {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
return this.makeActive(this.state, this.state.activeIndex);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Stop the interval, unsubscribe from the data bus, and — if a
|
|
161
|
+
* schedule was active — fire {@link onRollback} with the
|
|
162
|
+
* in-memory registers exactly once. The persisted copy is removed
|
|
163
|
+
* best-effort (fire-and-forget); restart recovery still works
|
|
164
|
+
* even if the removal does not land.
|
|
165
|
+
*
|
|
166
|
+
* Idempotent: subsequent calls are no-ops, and further
|
|
167
|
+
* `applySchedule` / `applyMessage` calls are silently ignored
|
|
168
|
+
* (they do not restart the interval).
|
|
169
|
+
*/
|
|
170
|
+
dispose() {
|
|
171
|
+
if (this.disposed) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.disposed = true;
|
|
175
|
+
this.options.dataBus.unsubscribe(this.listenerId);
|
|
176
|
+
if (this.state.kind === 'running') {
|
|
177
|
+
const state = this.state;
|
|
178
|
+
this.options.interval.stopInterval(state.intervalId);
|
|
179
|
+
const registers = state.registers;
|
|
180
|
+
this.state = { kind: 'idle' };
|
|
181
|
+
this.onRollback(registers);
|
|
182
|
+
// Best-effort persisted clear; rollback already fired in-process.
|
|
183
|
+
void this.options.storage.remove(this.storageKey).catch(() => { });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
enqueue(op) {
|
|
187
|
+
const next = this.operationChain.then(() => op());
|
|
188
|
+
// The chain swallows errors so one failure does not freeze subsequent ops;
|
|
189
|
+
// the returned promise still propagates them to the caller.
|
|
190
|
+
this.operationChain = next.catch(() => { });
|
|
191
|
+
return next;
|
|
192
|
+
}
|
|
193
|
+
async doApplySchedule(input) {
|
|
194
|
+
if (this.disposed) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (input.mode === enyo_data_bus_value_js_1.EnyoStorageScheduleModeEnum.Auto) {
|
|
198
|
+
await this.doReleaseSchedule();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const validation = this.validate(input.relativeSchedule);
|
|
202
|
+
if (!validation.ok) {
|
|
203
|
+
this.onError(validation.error);
|
|
204
|
+
await this.doReleaseSchedule();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
await this.doInstallSchedule(validation.entries);
|
|
208
|
+
}
|
|
209
|
+
async doReleaseSchedule() {
|
|
210
|
+
if (this.state.kind !== 'running') {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const state = this.state;
|
|
214
|
+
this.options.interval.stopInterval(state.intervalId);
|
|
215
|
+
const registers = state.registers;
|
|
216
|
+
this.state = { kind: 'idle' };
|
|
217
|
+
this.onRollback(registers);
|
|
218
|
+
await this.options.storage.remove(this.storageKey);
|
|
219
|
+
}
|
|
220
|
+
async doInstallSchedule(entries) {
|
|
221
|
+
if (this.state.kind === 'running') {
|
|
222
|
+
await this.doReleaseSchedule();
|
|
223
|
+
if (this.disposed) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const scheduleStartedAtMs = this.now();
|
|
228
|
+
const provisional = {
|
|
229
|
+
entry: entries[0],
|
|
230
|
+
indexInSchedule: 0,
|
|
231
|
+
totalEntries: entries.length,
|
|
232
|
+
scheduleStartedAtMs,
|
|
233
|
+
entryStartedAtMs: scheduleStartedAtMs,
|
|
234
|
+
};
|
|
235
|
+
let registers;
|
|
236
|
+
try {
|
|
237
|
+
registers = await this.onInit(provisional);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
if (this.disposed) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
this.onError(err instanceof Error ? err : new Error(String(err)));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (this.disposed) {
|
|
247
|
+
// We received registers but the handler was disposed during the await.
|
|
248
|
+
// Roll back immediately so the appliance is not left in a half-set state.
|
|
249
|
+
this.onRollback(registers);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const payload = { registers };
|
|
253
|
+
await this.options.storage.save(this.storageKey, payload);
|
|
254
|
+
if (this.disposed) {
|
|
255
|
+
this.onRollback(registers);
|
|
256
|
+
void this.options.storage.remove(this.storageKey).catch(() => { });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const intervalId = this.options.interval.createInterval('1s', () => this.onTick());
|
|
260
|
+
const running = {
|
|
261
|
+
kind: 'running',
|
|
262
|
+
schedule: entries,
|
|
263
|
+
scheduleStartedAtMs,
|
|
264
|
+
activeIndex: 0,
|
|
265
|
+
entryStartedAtMs: scheduleStartedAtMs,
|
|
266
|
+
intervalId,
|
|
267
|
+
registers,
|
|
268
|
+
};
|
|
269
|
+
this.state = running;
|
|
270
|
+
this.onChange(this.makeActive(running, 0), undefined);
|
|
271
|
+
}
|
|
272
|
+
async tryRecoverFromStorage() {
|
|
273
|
+
if (this.disposed || this.state.kind !== 'idle') {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const stored = await this.options.storage.load(this.storageKey);
|
|
277
|
+
if (!stored || this.disposed || this.state.kind !== 'idle') {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
this.onRollback(stored.registers);
|
|
281
|
+
await this.options.storage.remove(this.storageKey);
|
|
282
|
+
}
|
|
283
|
+
onTick() {
|
|
284
|
+
if (this.state.kind !== 'running') {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const state = this.state;
|
|
288
|
+
const elapsedMs = this.now() - state.scheduleStartedAtMs;
|
|
289
|
+
const targetIndex = this.findTargetIndex(state.schedule, elapsedMs);
|
|
290
|
+
while (state.activeIndex < targetIndex) {
|
|
291
|
+
const previous = this.makeActive(state, state.activeIndex);
|
|
292
|
+
state.activeIndex += 1;
|
|
293
|
+
state.entryStartedAtMs = state.scheduleStartedAtMs
|
|
294
|
+
+ state.schedule[state.activeIndex].seconds * 1000;
|
|
295
|
+
const active = this.makeActive(state, state.activeIndex);
|
|
296
|
+
this.onChange(active, previous);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
findTargetIndex(entries, elapsedMs) {
|
|
300
|
+
let target = 0;
|
|
301
|
+
for (let i = 1; i < entries.length; i += 1) {
|
|
302
|
+
if (entries[i].seconds * 1000 <= elapsedMs) {
|
|
303
|
+
target = i;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return target;
|
|
310
|
+
}
|
|
311
|
+
makeActive(state, index) {
|
|
312
|
+
const entryStartedAtMs = index === state.activeIndex
|
|
313
|
+
? state.entryStartedAtMs
|
|
314
|
+
: state.scheduleStartedAtMs + state.schedule[index].seconds * 1000;
|
|
315
|
+
return {
|
|
316
|
+
entry: state.schedule[index],
|
|
317
|
+
indexInSchedule: index,
|
|
318
|
+
totalEntries: state.schedule.length,
|
|
319
|
+
scheduleStartedAtMs: state.scheduleStartedAtMs,
|
|
320
|
+
entryStartedAtMs,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
validate(entries) {
|
|
324
|
+
if (!entries || entries.length === 0) {
|
|
325
|
+
return { ok: false, error: new Error('relativeSchedule must contain at least one entry when mode is "schedule"') };
|
|
326
|
+
}
|
|
327
|
+
let previousSeconds = -Infinity;
|
|
328
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
329
|
+
const e = entries[i];
|
|
330
|
+
if (!Number.isFinite(e.seconds) || e.seconds < 0) {
|
|
331
|
+
return { ok: false, error: new Error(`relativeSchedule[${i}].seconds must be a non-negative finite number`) };
|
|
332
|
+
}
|
|
333
|
+
if (e.seconds <= previousSeconds) {
|
|
334
|
+
return { ok: false, error: new Error(`relativeSchedule must be strictly increasing by seconds (entry ${i} = ${e.seconds}, previous = ${previousSeconds})`) };
|
|
335
|
+
}
|
|
336
|
+
if (!Number.isFinite(e.powerW) || e.powerW < 0) {
|
|
337
|
+
return { ok: false, error: new Error(`relativeSchedule[${i}].powerW must be a non-negative finite number`) };
|
|
338
|
+
}
|
|
339
|
+
if (e.direction !== enyo_data_bus_value_js_1.EnyoStorageScheduleDirectionEnum.Charge
|
|
340
|
+
&& e.direction !== enyo_data_bus_value_js_1.EnyoStorageScheduleDirectionEnum.Discharge) {
|
|
341
|
+
return { ok: false, error: new Error(`relativeSchedule[${i}].direction must be 'charge' or 'discharge'`) };
|
|
342
|
+
}
|
|
343
|
+
previousSeconds = e.seconds;
|
|
344
|
+
}
|
|
345
|
+
return { ok: true, entries };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
exports.StorageScheduleHandler = StorageScheduleHandler;
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { EnergyAppDataBus } from '../../packages/energy-app-data-bus.cjs';
|
|
2
|
+
import { EnergyAppInterval } from '../../packages/energy-app-interval.cjs';
|
|
3
|
+
import { EnergyAppStorage } from '../../packages/energy-app-storage.cjs';
|
|
4
|
+
import { EnyoDataBusSetStorageScheduleV1, EnyoStorageScheduleEntry, EnyoStorageScheduleModeEnum } from '../../types/enyo-data-bus-value.cjs';
|
|
5
|
+
/**
|
|
6
|
+
* Snapshot of the currently-active entry within an installed storage
|
|
7
|
+
* schedule. Handed to {@link StorageScheduleHandler.onChange} every
|
|
8
|
+
* time the active entry changes — including the very first entry
|
|
9
|
+
* (which receives `previous === undefined`).
|
|
10
|
+
*
|
|
11
|
+
* The enriched timing fields let subclasses render UI ("entry 3 of 8,
|
|
12
|
+
* started 12 s ago") and compute remaining seconds against the next
|
|
13
|
+
* entry without re-deriving the timing context themselves.
|
|
14
|
+
*/
|
|
15
|
+
export interface ActiveStorageScheduleEntry {
|
|
16
|
+
/** The raw schedule entry — direction, powerW, and the entry's own `seconds` offset. */
|
|
17
|
+
entry: EnyoStorageScheduleEntry;
|
|
18
|
+
/** Index of this entry within the installed schedule (0-based). */
|
|
19
|
+
indexInSchedule: number;
|
|
20
|
+
/** Total number of entries in the installed schedule. */
|
|
21
|
+
totalEntries: number;
|
|
22
|
+
/** Wall-clock milliseconds at which the carrying schedule was installed. */
|
|
23
|
+
scheduleStartedAtMs: number;
|
|
24
|
+
/** Wall-clock milliseconds at which this entry became the active one. */
|
|
25
|
+
entryStartedAtMs: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Unwrapped input accepted by
|
|
29
|
+
* {@link StorageScheduleHandler.applySchedule} — the same shape as
|
|
30
|
+
* {@link EnyoDataBusSetStorageScheduleV1.data} minus the optional
|
|
31
|
+
* `reason` field, so callers driving the handler from non-data-bus
|
|
32
|
+
* sources (a UI control, an optimizer output, a replay of stored plans)
|
|
33
|
+
* do not have to fabricate a reason.
|
|
34
|
+
*/
|
|
35
|
+
export interface StorageScheduleInput {
|
|
36
|
+
/** Control mode: hand control back to the appliance, or follow a schedule. */
|
|
37
|
+
mode: EnyoStorageScheduleModeEnum;
|
|
38
|
+
/**
|
|
39
|
+
* Relative schedule the appliance should follow when {@link mode} is
|
|
40
|
+
* {@link EnyoStorageScheduleModeEnum.Schedule}. Sorted ascending by
|
|
41
|
+
* `seconds`; the first entry should be at `seconds = 0`. Omitted (or
|
|
42
|
+
* empty) when `mode` is {@link EnyoStorageScheduleModeEnum.Auto}.
|
|
43
|
+
*/
|
|
44
|
+
relativeSchedule?: EnyoStorageScheduleEntry[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Construction options for {@link StorageScheduleHandler}.
|
|
48
|
+
*
|
|
49
|
+
* The handler only takes runtime wiring — data bus, interval source,
|
|
50
|
+
* storage (for restart-safe persistence of the rollback registers),
|
|
51
|
+
* the appliance id it is bound to, and an optional `now` test seam.
|
|
52
|
+
* Lifecycle reactions (`onInit`, `onChange`, `onRollback`) are
|
|
53
|
+
* mandatory and live on the subclass as abstract methods.
|
|
54
|
+
*/
|
|
55
|
+
export interface StorageScheduleHandlerOptions {
|
|
56
|
+
/**
|
|
57
|
+
* Data-bus service the handler subscribes to. On construction the
|
|
58
|
+
* handler registers a single listener for
|
|
59
|
+
* {@link EnyoDataBusMessageEnum.SetStorageScheduleV1} messages; the
|
|
60
|
+
* listener is removed in {@link StorageScheduleHandler.dispose}.
|
|
61
|
+
*/
|
|
62
|
+
dataBus: EnergyAppDataBus;
|
|
63
|
+
/**
|
|
64
|
+
* Interval service driving the 1-second tick that advances the
|
|
65
|
+
* active entry. Inject a fake in tests; the production
|
|
66
|
+
* implementation uses `EnergyApp.useInterval()`.
|
|
67
|
+
*/
|
|
68
|
+
interval: EnergyAppInterval;
|
|
69
|
+
/**
|
|
70
|
+
* Persistent key-value store the handler uses to make rollback
|
|
71
|
+
* restart-safe. The result of {@link StorageScheduleHandler.onInit}
|
|
72
|
+
* is written here when a schedule starts running and cleared after
|
|
73
|
+
* the matching {@link StorageScheduleHandler.onRollback}. On
|
|
74
|
+
* construction, the handler checks for a previously-persisted
|
|
75
|
+
* payload and invokes `onRollback` immediately if one is found —
|
|
76
|
+
* so a process that died mid-schedule still gets its rollback the
|
|
77
|
+
* next time around.
|
|
78
|
+
*/
|
|
79
|
+
storage: EnergyAppStorage;
|
|
80
|
+
/**
|
|
81
|
+
* Optional appliance id this handler is bound to.
|
|
82
|
+
*
|
|
83
|
+
* When set, inbound `SetStorageScheduleV1` messages whose
|
|
84
|
+
* `applianceId` does not match are silently ignored — so multiple
|
|
85
|
+
* handlers can safely share one data bus.
|
|
86
|
+
*
|
|
87
|
+
* When omitted, the handler accepts every `SetStorageScheduleV1`
|
|
88
|
+
* message it sees. Useful for singletons that own the only
|
|
89
|
+
* battery / storage appliance on the device.
|
|
90
|
+
*
|
|
91
|
+
* The storage key under which the rollback registers are persisted
|
|
92
|
+
* incorporates the appliance id; an omitted id keys against the
|
|
93
|
+
* literal string `"null"`. This keeps per-appliance handlers and a
|
|
94
|
+
* single unbound handler from colliding in storage.
|
|
95
|
+
*/
|
|
96
|
+
applianceId?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Wall-clock source. Defaults to `() => Date.now()`. Override in
|
|
99
|
+
* tests to advance time deterministically without
|
|
100
|
+
* `vi.useFakeTimers`.
|
|
101
|
+
*/
|
|
102
|
+
now?: () => number;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Abstract base class that drives a battery / storage appliance
|
|
106
|
+
* through an {@link EnyoDataBusSetStorageScheduleV1}-style relative
|
|
107
|
+
* schedule on a 1-second tick.
|
|
108
|
+
*
|
|
109
|
+
* Subclasses **must** implement three lifecycle hooks; the handler
|
|
110
|
+
* cannot operate without them, so they are declared `abstract` rather
|
|
111
|
+
* than optional callbacks on the options bag:
|
|
112
|
+
*
|
|
113
|
+
* - {@link onInit} — an `async` setup hook called once per schedule
|
|
114
|
+
* install. Returns a `TRegisters` payload (a snapshot of the
|
|
115
|
+
* pre-schedule device state, the initial register values, anything
|
|
116
|
+
* the subclass needs to restore later) or throws to abort the
|
|
117
|
+
* install. The returned registers are persisted to
|
|
118
|
+
* {@link StorageScheduleHandlerOptions.storage} so a process that
|
|
119
|
+
* dies mid-schedule still gets a rollback next time it starts.
|
|
120
|
+
* - {@link onChange} — fires for every active-entry transition,
|
|
121
|
+
* including the first one (the entry at index 0, with
|
|
122
|
+
* `previous === undefined`). This is where subclasses apply the
|
|
123
|
+
* setpoint to the underlying battery driver.
|
|
124
|
+
* - {@link onRollback} — fires when a previously-running schedule is
|
|
125
|
+
* released (mode switched to auto, replaced, invalid input from the
|
|
126
|
+
* running state, handler disposed, or restart recovery). Receives
|
|
127
|
+
* the `TRegisters` originally returned by `onInit` so the subclass
|
|
128
|
+
* can restore the pre-schedule state.
|
|
129
|
+
*
|
|
130
|
+
* Subclasses **may** override {@link onError} to observe invalid
|
|
131
|
+
* input; the default implementation is a no-op.
|
|
132
|
+
*
|
|
133
|
+
* Wall-clock time is the source of truth for entry advancement; the
|
|
134
|
+
* `'1s'` interval is the sampling rate. Tests pump time
|
|
135
|
+
* deterministically via the injected `now` seam without
|
|
136
|
+
* `vi.useFakeTimers`.
|
|
137
|
+
*
|
|
138
|
+
* All public mutating methods serialise through an internal operation
|
|
139
|
+
* chain, so a rapid sequence of `applySchedule` calls observes them
|
|
140
|
+
* in order without races even though `onInit` and storage I/O are
|
|
141
|
+
* async.
|
|
142
|
+
*
|
|
143
|
+
* @typeParam TRegisters - The shape the subclass uses to remember
|
|
144
|
+
* pre-schedule state. Must be JSON-serialisable
|
|
145
|
+
* since it is round-tripped through
|
|
146
|
+
* {@link EnergyAppStorage}.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* interface MyRegisters { previousMode: string; previousLimitW: number; }
|
|
151
|
+
*
|
|
152
|
+
* class MyStorage extends StorageScheduleHandler<MyRegisters> {
|
|
153
|
+
* protected async onInit(): Promise<MyRegisters> {
|
|
154
|
+
* return {
|
|
155
|
+
* previousMode: await this.driver.readMode(),
|
|
156
|
+
* previousLimitW: await this.driver.readLimit(),
|
|
157
|
+
* };
|
|
158
|
+
* }
|
|
159
|
+
* protected onChange(active: ActiveStorageScheduleEntry): void {
|
|
160
|
+
* this.driver.applySetpoint(active.entry.direction, active.entry.powerW);
|
|
161
|
+
* }
|
|
162
|
+
* protected onRollback(registers: MyRegisters): void {
|
|
163
|
+
* this.driver.applyMode(registers.previousMode);
|
|
164
|
+
* this.driver.applyLimit(registers.previousLimitW);
|
|
165
|
+
* }
|
|
166
|
+
* }
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export declare abstract class StorageScheduleHandler<TRegisters> {
|
|
170
|
+
private readonly options;
|
|
171
|
+
private readonly now;
|
|
172
|
+
private readonly storageKey;
|
|
173
|
+
private readonly listenerId;
|
|
174
|
+
private state;
|
|
175
|
+
private disposed;
|
|
176
|
+
private operationChain;
|
|
177
|
+
/**
|
|
178
|
+
* @param options - {@link StorageScheduleHandlerOptions}. Required:
|
|
179
|
+
* `dataBus`, `interval`, `storage`. Optional:
|
|
180
|
+
* `applianceId`, `now`.
|
|
181
|
+
*/
|
|
182
|
+
protected constructor(options: StorageScheduleHandlerOptions);
|
|
183
|
+
/**
|
|
184
|
+
* Snapshot the pre-schedule state of the underlying appliance and
|
|
185
|
+
* return it as `TRegisters`. Called once per schedule install,
|
|
186
|
+
* before any {@link onChange} fires.
|
|
187
|
+
*
|
|
188
|
+
* The returned value is persisted to storage and handed back to
|
|
189
|
+
* {@link onRollback} when the schedule is released. Throwing from
|
|
190
|
+
* `onInit` aborts the install — no schedule is started, no
|
|
191
|
+
* registers are persisted, and {@link onError} is invoked with
|
|
192
|
+
* the thrown error.
|
|
193
|
+
*
|
|
194
|
+
* @param active - Snapshot of the entry that *will* become active
|
|
195
|
+
* if `onInit` succeeds. Subclasses do not need to
|
|
196
|
+
* apply this setpoint here — `onChange` fires
|
|
197
|
+
* immediately after a successful `onInit` with the
|
|
198
|
+
* same active entry.
|
|
199
|
+
* @returns Registers describing how to restore the pre-schedule
|
|
200
|
+
* state. Must be JSON-serialisable.
|
|
201
|
+
*/
|
|
202
|
+
protected abstract onInit(active: ActiveStorageScheduleEntry): Promise<TRegisters>;
|
|
203
|
+
/**
|
|
204
|
+
* Apply a schedule setpoint to the underlying appliance. Fires:
|
|
205
|
+
*
|
|
206
|
+
* - Immediately after a successful {@link onInit} with
|
|
207
|
+
* `active = entries[0]` and `previous = undefined`.
|
|
208
|
+
* - On every subsequent active-entry transition with
|
|
209
|
+
* `previous` set to the entry that was active immediately before.
|
|
210
|
+
*
|
|
211
|
+
* Errors thrown from `onChange` are not caught by the handler —
|
|
212
|
+
* subclasses are responsible for their own error handling here.
|
|
213
|
+
*
|
|
214
|
+
* @param active - Snapshot of the now-active entry.
|
|
215
|
+
* @param previous - Snapshot of the previously-active entry, or
|
|
216
|
+
* `undefined` on the first call of a schedule.
|
|
217
|
+
*/
|
|
218
|
+
protected abstract onChange(active: ActiveStorageScheduleEntry, previous: ActiveStorageScheduleEntry | undefined): void;
|
|
219
|
+
/**
|
|
220
|
+
* Restore the appliance to its pre-schedule state using the
|
|
221
|
+
* registers originally returned by {@link onInit}. Fires when a
|
|
222
|
+
* running schedule is released (mode switched to auto, replaced
|
|
223
|
+
* by another schedule, invalid input from the running state,
|
|
224
|
+
* handler disposed) **and** during restart recovery (a previous
|
|
225
|
+
* process saved registers but exited without rolling back).
|
|
226
|
+
*
|
|
227
|
+
* Errors thrown from `onRollback` are not caught by the handler.
|
|
228
|
+
*
|
|
229
|
+
* @param registers - The registers returned by the matching
|
|
230
|
+
* `onInit`, or — on restart recovery — the
|
|
231
|
+
* registers persisted by the previous process.
|
|
232
|
+
*/
|
|
233
|
+
protected abstract onRollback(registers: TRegisters): void;
|
|
234
|
+
/**
|
|
235
|
+
* Invoked when {@link applySchedule} receives invalid input
|
|
236
|
+
* (empty schedule while `mode === 'schedule'`, unsorted /
|
|
237
|
+
* duplicate / negative `seconds`, non-finite `powerW`, missing
|
|
238
|
+
* direction) **or** when {@link onInit} throws. The default
|
|
239
|
+
* implementation is a no-op; subclasses may override to log or
|
|
240
|
+
* surface the error.
|
|
241
|
+
*
|
|
242
|
+
* After `onError`, the handler always lands in a defined state
|
|
243
|
+
* (idle), regardless of whether `onError` is observed.
|
|
244
|
+
*
|
|
245
|
+
* @param _err - Description of what went wrong.
|
|
246
|
+
*/
|
|
247
|
+
protected onError(_err: Error): void;
|
|
248
|
+
/**
|
|
249
|
+
* Apply a schedule directly, bypassing the data bus. Useful when
|
|
250
|
+
* the schedule comes from a UI control, an optimizer, or a replay
|
|
251
|
+
* of stored plans. Equivalent to receiving an
|
|
252
|
+
* {@link EnyoDataBusSetStorageScheduleV1} whose `data` matches
|
|
253
|
+
* `input`.
|
|
254
|
+
*
|
|
255
|
+
* Disposed handlers silently ignore the call. Concurrent calls
|
|
256
|
+
* are serialised internally — the returned promise resolves once
|
|
257
|
+
* the call's effects (including any preceding rollback and any
|
|
258
|
+
* `onInit` await) are complete.
|
|
259
|
+
*
|
|
260
|
+
* @param input - {@link StorageScheduleInput} carrying mode and
|
|
261
|
+
* optional schedule.
|
|
262
|
+
*/
|
|
263
|
+
applySchedule(input: StorageScheduleInput): Promise<void>;
|
|
264
|
+
/**
|
|
265
|
+
* Apply an incoming {@link EnyoDataBusSetStorageScheduleV1} message.
|
|
266
|
+
* Messages whose `applianceId` does not match the handler's bound
|
|
267
|
+
* appliance are silently ignored — the data-bus subscription
|
|
268
|
+
* registered in the constructor routes through this method.
|
|
269
|
+
*
|
|
270
|
+
* @param msg - The data-bus message.
|
|
271
|
+
*/
|
|
272
|
+
applyMessage(msg: EnyoDataBusSetStorageScheduleV1): Promise<void>;
|
|
273
|
+
/**
|
|
274
|
+
* Snapshot of the currently-active entry, or `undefined` when the
|
|
275
|
+
* handler is idle (no schedule installed, or
|
|
276
|
+
* {@link EnyoStorageScheduleModeEnum.Auto} has been applied).
|
|
277
|
+
*/
|
|
278
|
+
getActiveEntry(): ActiveStorageScheduleEntry | undefined;
|
|
279
|
+
/**
|
|
280
|
+
* Stop the interval, unsubscribe from the data bus, and — if a
|
|
281
|
+
* schedule was active — fire {@link onRollback} with the
|
|
282
|
+
* in-memory registers exactly once. The persisted copy is removed
|
|
283
|
+
* best-effort (fire-and-forget); restart recovery still works
|
|
284
|
+
* even if the removal does not land.
|
|
285
|
+
*
|
|
286
|
+
* Idempotent: subsequent calls are no-ops, and further
|
|
287
|
+
* `applySchedule` / `applyMessage` calls are silently ignored
|
|
288
|
+
* (they do not restart the interval).
|
|
289
|
+
*/
|
|
290
|
+
dispose(): void;
|
|
291
|
+
private enqueue;
|
|
292
|
+
private doApplySchedule;
|
|
293
|
+
private doReleaseSchedule;
|
|
294
|
+
private doInstallSchedule;
|
|
295
|
+
private tryRecoverFromStorage;
|
|
296
|
+
private onTick;
|
|
297
|
+
private findTargetIndex;
|
|
298
|
+
private makeActive;
|
|
299
|
+
private validate;
|
|
300
|
+
}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ __exportStar(require("./implementations/appliances/appliance-manager.cjs"), expo
|
|
|
32
32
|
__exportStar(require("./implementations/appliances/identifier-strategies.cjs"), exports);
|
|
33
33
|
__exportStar(require("./implementations/network-devices/network-access-guard.cjs"), exports);
|
|
34
34
|
__exportStar(require("./implementations/network-devices/network-device-manager.cjs"), exports);
|
|
35
|
+
__exportStar(require("./implementations/storage/storage-schedule-handler.cjs"), exports);
|
|
35
36
|
__exportStar(require("./enyo-package-channel.cjs"), exports);
|
|
36
37
|
__exportStar(require("./types/enyo-timeseries.cjs"), exports);
|
|
37
38
|
__exportStar(require("./types/enyo-energy-manager.cjs"), exports);
|
|
@@ -50,9 +51,11 @@ __exportStar(require("./types/enyo-currency.cjs"), exports);
|
|
|
50
51
|
__exportStar(require("./packages/energy-app-sequence-generator.cjs"), exports);
|
|
51
52
|
__exportStar(require("./packages/energy-app-energy-prices.cjs"), exports);
|
|
52
53
|
__exportStar(require("./packages/energy-app-modbus-rtu.cjs"), exports);
|
|
54
|
+
__exportStar(require("./types/enyo-spine.cjs"), exports);
|
|
53
55
|
__exportStar(require("./types/enyo-eebus.cjs"), exports);
|
|
54
56
|
__exportStar(require("./types/enyo-eebus-use-cases.cjs"), exports);
|
|
55
57
|
__exportStar(require("./types/enyo-eebus-features.cjs"), exports);
|
|
58
|
+
__exportStar(require("./types/enyo-eebus-errors.cjs"), exports);
|
|
56
59
|
__exportStar(require("./packages/energy-app-eebus.cjs"), exports);
|
|
57
60
|
__exportStar(require("./types/enyo-mqtt.cjs"), exports);
|
|
58
61
|
__exportStar(require("./packages/energy-app-mqtt.cjs"), exports);
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -16,6 +16,7 @@ export * from './implementations/appliances/appliance-manager.cjs';
|
|
|
16
16
|
export * from './implementations/appliances/identifier-strategies.cjs';
|
|
17
17
|
export * from './implementations/network-devices/network-access-guard.cjs';
|
|
18
18
|
export * from './implementations/network-devices/network-device-manager.cjs';
|
|
19
|
+
export * from './implementations/storage/storage-schedule-handler.cjs';
|
|
19
20
|
export * from './enyo-package-channel.cjs';
|
|
20
21
|
export * from './types/enyo-timeseries.cjs';
|
|
21
22
|
export * from './types/enyo-energy-manager.cjs';
|
|
@@ -34,9 +35,11 @@ export * from './types/enyo-currency.cjs';
|
|
|
34
35
|
export * from './packages/energy-app-sequence-generator.cjs';
|
|
35
36
|
export * from './packages/energy-app-energy-prices.cjs';
|
|
36
37
|
export * from './packages/energy-app-modbus-rtu.cjs';
|
|
38
|
+
export * from './types/enyo-spine.cjs';
|
|
37
39
|
export * from './types/enyo-eebus.cjs';
|
|
38
40
|
export * from './types/enyo-eebus-use-cases.cjs';
|
|
39
41
|
export * from './types/enyo-eebus-features.cjs';
|
|
42
|
+
export * from './types/enyo-eebus-errors.cjs';
|
|
40
43
|
export * from './packages/energy-app-eebus.cjs';
|
|
41
44
|
export * from './types/enyo-mqtt.cjs';
|
|
42
45
|
export * from './packages/energy-app-mqtt.cjs';
|