@enyo-energy/sunspec-sdk 0.0.71 → 0.0.72
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 +302 -0
- package/dist/cjs/index.cjs +30 -2
- package/dist/cjs/index.d.cts +6 -1
- package/dist/cjs/sunspec-battery-calibration-driver.cjs +158 -0
- package/dist/cjs/sunspec-battery-calibration-driver.d.cts +63 -0
- package/dist/cjs/sunspec-battery-feature-calibrator.cjs +350 -0
- package/dist/cjs/sunspec-battery-feature-calibrator.d.cts +89 -0
- package/dist/cjs/sunspec-battery-schedule-handler.cjs +92 -0
- package/dist/cjs/sunspec-battery-schedule-handler.d.cts +67 -0
- package/dist/cjs/sunspec-calibration-storage.cjs +47 -0
- package/dist/cjs/sunspec-calibration-storage.d.cts +24 -0
- package/dist/cjs/sunspec-devices.cjs +270 -215
- package/dist/cjs/sunspec-devices.d.cts +99 -19
- package/dist/cjs/sunspec-interfaces.cjs +42 -1
- package/dist/cjs/sunspec-interfaces.d.cts +66 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +12 -1
- package/dist/sunspec-battery-calibration-driver.d.ts +63 -0
- package/dist/sunspec-battery-calibration-driver.js +154 -0
- package/dist/sunspec-battery-feature-calibrator.d.ts +89 -0
- package/dist/sunspec-battery-feature-calibrator.js +345 -0
- package/dist/sunspec-battery-schedule-handler.d.ts +67 -0
- package/dist/sunspec-battery-schedule-handler.js +88 -0
- package/dist/sunspec-calibration-storage.d.ts +24 -0
- package/dist/sunspec-calibration-storage.js +42 -0
- package/dist/sunspec-devices.d.ts +99 -19
- package/dist/sunspec-devices.js +271 -216
- package/dist/sunspec-interfaces.d.ts +66 -0
- package/dist/sunspec-interfaces.js +41 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -3
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { type IRetryConfig, type SunspecBatteryBaseData, SunspecBatteryCapability, type SunspecBatteryControls, type SunspecInverterData, SunspecInverterCapability, SunspecMeterCapability, SunspecStorageMode } from "./sunspec-interfaces.cjs";
|
|
1
|
+
import { type IRetryConfig, type SunspecBatteryBaseData, SunspecBatteryCapability, type SunspecBatteryControls, type SunspecBatteryFeatureMode, type SunspecInverterData, SunspecInverterCapability, SunspecMeterCapability, SunspecStorageMode } from "./sunspec-interfaces.cjs";
|
|
2
2
|
import { ApplianceManager, EnergyApp } from "@enyo-energy/energy-app-sdk";
|
|
3
3
|
import { type EnyoAppliance, type EnyoApplianceErrorCode, EnyoApplianceName } from "@enyo-energy/energy-app-sdk/dist/types/enyo-appliance.js";
|
|
4
4
|
import { EnyoNetworkDevice } from "@enyo-energy/energy-app-sdk/dist/types/enyo-network-device.js";
|
|
5
5
|
import { SunspecModbusClient } from "./sunspec-modbus-client.cjs";
|
|
6
6
|
import { ConnectionRetryManager } from "./connection-retry-manager.cjs";
|
|
7
7
|
import { EnyoCommandAcknowledgeAnswerEnum, EnyoDataBusMessage, EnyoDataBusMessageEnum, EnyoDataBusMessageResolution } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
8
|
-
import { type
|
|
8
|
+
import { BatteryCalibrator, type BatteryCalibratorConfig, type CalibrationResultStore, type CalibrationSnapshot, type RestoreReason, SnapshotService } from "@enyo-energy/appliance-calibration";
|
|
9
9
|
import { EnergyAppDataBus } from "@enyo-energy/energy-app-sdk/dist/packages/energy-app-data-bus.js";
|
|
10
10
|
/**
|
|
11
11
|
* Base abstract class for all Sunspec devices
|
|
@@ -26,7 +26,13 @@ export declare abstract class BaseSunspecDevice {
|
|
|
26
26
|
protected dataBus?: EnergyAppDataBus;
|
|
27
27
|
protected retryManager: ConnectionRetryManager;
|
|
28
28
|
protected consecutiveReconnectFailures: number;
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Prefix used when persisting calibration snapshots via the library's
|
|
31
|
+
* {@link SnapshotService}. Kept identical to the key the SDK used before
|
|
32
|
+
* the migration to `@enyo-energy/appliance-calibration` so existing
|
|
33
|
+
* snapshots stored by older builds are still picked up on startup.
|
|
34
|
+
*/
|
|
35
|
+
protected static readonly CALIBRATION_SNAPSHOT_KEY_PREFIX = "sunspec-calibration-snapshot-";
|
|
30
36
|
constructor(energyApp: EnergyApp, name: EnyoApplianceName[], networkDevice: EnyoNetworkDevice, sunspecClient: SunspecModbusClient, applianceManager: ApplianceManager, unitId?: number, port?: number, baseAddress?: number, retryConfig?: IRetryConfig, appliance?: EnyoAppliance, useTls?: boolean | undefined);
|
|
31
37
|
/**
|
|
32
38
|
* Connect to the device and create/update the appliance
|
|
@@ -80,16 +86,11 @@ export declare abstract class BaseSunspecDevice {
|
|
|
80
86
|
protected markOffline(): Promise<void>;
|
|
81
87
|
protected sendCommandAcknowledge(messageId: string, acknowledgeMessage: EnyoDataBusMessageEnum | string, answer: EnyoCommandAcknowledgeAnswerEnum, rejectionReason?: string): void;
|
|
82
88
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*/
|
|
87
|
-
protected initCalibrationService(deviceType: CalibrationDeviceType): Promise<void>;
|
|
88
|
-
/**
|
|
89
|
-
* Subclasses implement how to write the modified fields of a snapshot back to the device.
|
|
90
|
-
* Called both on explicit StopCalibrationV1 and when the 5-minute auto-stop fires.
|
|
89
|
+
* Build a typed {@link SnapshotService} bound to this appliance's calibration storage,
|
|
90
|
+
* using the legacy SDK storage key prefix so prior installs upgrade seamlessly. Returns
|
|
91
|
+
* undefined if the appliance has not been registered yet.
|
|
91
92
|
*/
|
|
92
|
-
protected
|
|
93
|
+
protected buildSnapshotService<T>(onRestore: (snapshot: CalibrationSnapshot<T>, reason: RestoreReason) => Promise<void>): SnapshotService<T> | undefined;
|
|
93
94
|
}
|
|
94
95
|
/**
|
|
95
96
|
* Sunspec Inverter implementation using dynamic model discovery
|
|
@@ -100,6 +101,7 @@ export declare class SunspecInverter extends BaseSunspecDevice {
|
|
|
100
101
|
private static readonly CONNECTION_FAULT_THRESHOLD;
|
|
101
102
|
private storage?;
|
|
102
103
|
private errorState;
|
|
104
|
+
private snapshotService?;
|
|
103
105
|
constructor(energyApp: EnergyApp, name: EnyoApplianceName[], networkDevice: EnyoNetworkDevice, sunspecClient: SunspecModbusClient, applianceManager: ApplianceManager, unitId?: number, port?: number, baseAddress?: number, capabilities?: SunspecInverterCapability[], retryConfig?: IRetryConfig, appliance?: EnyoAppliance, useTls?: boolean);
|
|
104
106
|
connect(): Promise<void>;
|
|
105
107
|
disconnect(): Promise<void>;
|
|
@@ -169,16 +171,77 @@ export declare class SunspecInverter extends BaseSunspecDevice {
|
|
|
169
171
|
stopDataBusListening(): void;
|
|
170
172
|
private handleInverterCommand;
|
|
171
173
|
private handleSetFeedInLimit;
|
|
174
|
+
/**
|
|
175
|
+
* Lazily construct the per-appliance {@link SnapshotService}. Reloads any
|
|
176
|
+
* persisted snapshot from storage so an in-flight calibration survives
|
|
177
|
+
* process restarts. Idempotent.
|
|
178
|
+
*/
|
|
179
|
+
private initSnapshotService;
|
|
172
180
|
private handleStartCalibration;
|
|
173
181
|
private handleStopCalibration;
|
|
174
|
-
|
|
182
|
+
/**
|
|
183
|
+
* `onRestore` callback for the inverter's {@link SnapshotService}. Writes only the
|
|
184
|
+
* subset of writable inverter fields that other commands actually touched during the
|
|
185
|
+
* calibration. Catches and logs failures rather than throwing — the snapshot has
|
|
186
|
+
* already been removed from storage by the time this runs, so a throw is unrecoverable
|
|
187
|
+
* and is best logged for operator action.
|
|
188
|
+
*/
|
|
189
|
+
private restoreInverterSnapshot;
|
|
175
190
|
}
|
|
176
191
|
/**
|
|
177
192
|
* Sunspec Battery implementation
|
|
178
193
|
*/
|
|
179
194
|
export declare class SunspecBattery extends BaseSunspecDevice {
|
|
195
|
+
private readonly featureMode;
|
|
180
196
|
private readonly capabilities;
|
|
181
|
-
|
|
197
|
+
private snapshotService?;
|
|
198
|
+
private calibrationDriver?;
|
|
199
|
+
private batteryCalibrator?;
|
|
200
|
+
private calibrationResultStore?;
|
|
201
|
+
private scheduleHandler;
|
|
202
|
+
/**
|
|
203
|
+
* Battery features that imply outbound control writes from the host action-taker.
|
|
204
|
+
* These are stripped from `appliance.battery.features` until the calibration result
|
|
205
|
+
* store says this battery has been successfully calibrated — see
|
|
206
|
+
* {@link computeAdvertisedFeatures}.
|
|
207
|
+
*/
|
|
208
|
+
private static readonly CONTROLLABLE_FEATURES;
|
|
209
|
+
constructor(energyApp: EnergyApp, name: EnyoApplianceName[], networkDevice: EnyoNetworkDevice, sunspecClient: SunspecModbusClient, applianceManager: ApplianceManager, featureMode: SunspecBatteryFeatureMode, unitId?: number, port?: number, baseAddress?: number, capabilities?: SunspecBatteryCapability[], retryConfig?: IRetryConfig, appliance?: EnyoAppliance, useTls?: boolean);
|
|
210
|
+
/**
|
|
211
|
+
* Wire the battery into `@enyo-energy/appliance-calibration`'s
|
|
212
|
+
* `BatteryCalibrator` test-charge flow. Call once after {@link connect}.
|
|
213
|
+
*
|
|
214
|
+
* Consumers own the `CalibrationResultStore` (shared across appliances so
|
|
215
|
+
* the persisted result map doesn't get clobbered) and the
|
|
216
|
+
* `CalibrationTrigger` that drives `runCalibration()` on whatever schedule
|
|
217
|
+
* makes sense for the host. Pass overrides for the calibrator's defaults
|
|
218
|
+
* (`testPowerW`, response thresholds, etc.) via `opts.config`.
|
|
219
|
+
*/
|
|
220
|
+
configureCalibration(opts: {
|
|
221
|
+
resultStore: CalibrationResultStore;
|
|
222
|
+
config?: Partial<BatteryCalibratorConfig>;
|
|
223
|
+
}): BatteryCalibrator<SunspecBatteryControls>;
|
|
224
|
+
/**
|
|
225
|
+
* Detect which battery features the device's registers expose. This is the
|
|
226
|
+
* raw, register-only view; the {@link featureMode} configured at
|
|
227
|
+
* construction decides what is actually published — see
|
|
228
|
+
* {@link resolveAdvertisedFeatures}.
|
|
229
|
+
*/
|
|
230
|
+
private detectFromRegisters;
|
|
231
|
+
/**
|
|
232
|
+
* Resolve `appliance.battery.features` for the configured mode. Called on
|
|
233
|
+
* every connect() and readData() cycle so that, in calibration-based mode,
|
|
234
|
+
* the controllable features appear as soon as the calibrator flips
|
|
235
|
+
* `isCalibrated` (no synchronous push).
|
|
236
|
+
*/
|
|
237
|
+
private resolveAdvertisedFeatures;
|
|
238
|
+
/**
|
|
239
|
+
* Returns the per-battery `BatteryCalibrator` created by
|
|
240
|
+
* {@link configureCalibration}, or `undefined` if calibration was never
|
|
241
|
+
* configured. Consumers register the returned instance with their own
|
|
242
|
+
* `CalibrationTrigger`.
|
|
243
|
+
*/
|
|
244
|
+
getBatteryCalibrator(): BatteryCalibrator<SunspecBatteryControls> | undefined;
|
|
182
245
|
/**
|
|
183
246
|
* Connect to the battery and create/update the appliance
|
|
184
247
|
*/
|
|
@@ -300,13 +363,30 @@ export declare class SunspecBattery extends BaseSunspecDevice {
|
|
|
300
363
|
*/
|
|
301
364
|
stopDataBusListening(): void;
|
|
302
365
|
private handleStorageCommand;
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
366
|
+
/**
|
|
367
|
+
* Acknowledge a SetStorageScheduleV1 message. The actual schedule application
|
|
368
|
+
* is owned by `this.scheduleHandler` (subscribed independently via its own
|
|
369
|
+
* data-bus listener registered in its constructor). The ack reports "received
|
|
370
|
+
* & queued" rather than "applied" — schedule entries play out over time, and
|
|
371
|
+
* per-entry write failures land in `console.error` via the handler's
|
|
372
|
+
* onChange wrapper.
|
|
373
|
+
*/
|
|
374
|
+
private handleSetStorageScheduleAck;
|
|
375
|
+
/**
|
|
376
|
+
* Lazily construct the per-appliance {@link SnapshotService}. Reloads any
|
|
377
|
+
* persisted snapshot from storage so an in-flight calibration survives
|
|
378
|
+
* process restarts. Idempotent.
|
|
379
|
+
*/
|
|
380
|
+
private initSnapshotService;
|
|
307
381
|
private handleStartCalibration;
|
|
308
382
|
private handleStopCalibration;
|
|
309
|
-
|
|
383
|
+
/**
|
|
384
|
+
* `onRestore` callback for the battery's {@link SnapshotService}. Writes only the
|
|
385
|
+
* subset of writable battery fields touched during the calibration. Errors are
|
|
386
|
+
* caught and logged — by the time this fires the snapshot is gone from storage,
|
|
387
|
+
* so a throw would just propagate into the void.
|
|
388
|
+
*/
|
|
389
|
+
private restoreBatterySnapshot;
|
|
310
390
|
}
|
|
311
391
|
/**
|
|
312
392
|
* Sunspec Meter implementation
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* SunSpec block interfaces with block numbers
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SunspecMeterCapability = exports.SunspecBatteryCapability = exports.SunspecInverterCapability = exports.SunspecStorageMode = exports.SunspecChargeSource = exports.SunspecVArPctMode = exports.SunspecEnableControl = exports.SunspecConnectionControl = exports.SunspecStorageControlMode = exports.SunspecBatteryEvent1 = exports.SunspecBatteryBankState = exports.SunspecBatteryType = exports.SunspecBatteryControlMode = exports.SunspecBatteryChargeState = exports.SunspecMPPTOperatingState = exports.SUNSPEC_CONNECTION_LOST_CODE = exports.SunspecInverterEvent1 = exports.SunspecModelId = exports.DEFAULT_RETRY_CONFIG = void 0;
|
|
6
|
+
exports.SUNSPEC_CONTROLLABLE_FEATURES = exports.SunspecMeterCapability = exports.SunspecBatteryFeatureModeKind = exports.SunspecBatteryCapability = exports.SunspecInverterCapability = exports.SunspecStorageMode = exports.SunspecChargeSource = exports.SunspecVArPctMode = exports.SunspecEnableControl = exports.SunspecConnectionControl = exports.SunspecStorageControlMode = exports.SunspecBatteryEvent1 = exports.SunspecBatteryBankState = exports.SunspecBatteryType = exports.SunspecBatteryControlMode = exports.SunspecBatteryChargeState = exports.SunspecMPPTOperatingState = exports.SUNSPEC_CONNECTION_LOST_CODE = exports.SunspecInverterEvent1 = exports.SunspecModelId = exports.DEFAULT_RETRY_CONFIG = void 0;
|
|
7
|
+
const enyo_battery_appliance_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-battery-appliance.js");
|
|
7
8
|
exports.DEFAULT_RETRY_CONFIG = {
|
|
8
9
|
phases: [
|
|
9
10
|
{ intervalMs: 10_000, durationMs: 60_000 }, // Phase 1: every 10s for 1 minute
|
|
@@ -257,6 +258,46 @@ var SunspecBatteryCapability;
|
|
|
257
258
|
SunspecBatteryCapability["DischargeLimit"] = "discharge-limit";
|
|
258
259
|
SunspecBatteryCapability["ChargeLimit"] = "charge-limit";
|
|
259
260
|
})(SunspecBatteryCapability || (exports.SunspecBatteryCapability = SunspecBatteryCapability = {}));
|
|
261
|
+
/**
|
|
262
|
+
* Selects how `SunspecBattery` populates `appliance.battery.features`.
|
|
263
|
+
*
|
|
264
|
+
* - `Disabled`: ignore the SunSpec registers and ignore calibration. The SDK
|
|
265
|
+
* publishes whatever the consumer puts in `allowedFeatures` (omit or pass
|
|
266
|
+
* `[]` to advertise nothing). Useful when the host already knows what the
|
|
267
|
+
* battery can do and doesn't want the SDK to make any inference.
|
|
268
|
+
* - `RegisterBased`: publish whatever the device's registers expose. If
|
|
269
|
+
* `allowedFeatures` is set, the published list is the intersection — the
|
|
270
|
+
* register-detected set filtered to the allow-list. Lets the consumer hide
|
|
271
|
+
* features that the hardware exposes but they don't want to use.
|
|
272
|
+
* - `CalibrationBased`: same as `RegisterBased`, but controllable features
|
|
273
|
+
* (grid charging, grid discharging, charge limitation, discharge
|
|
274
|
+
* limitation) stay hidden until `CalibrationResultStore.isCalibrated(...)`
|
|
275
|
+
* returns true for this appliance. Requires `configureCalibration(...)` to
|
|
276
|
+
* actually run a calibration; without it the controllable features remain
|
|
277
|
+
* hidden forever (safe-fallback behaviour).
|
|
278
|
+
*/
|
|
279
|
+
var SunspecBatteryFeatureModeKind;
|
|
280
|
+
(function (SunspecBatteryFeatureModeKind) {
|
|
281
|
+
SunspecBatteryFeatureModeKind["Disabled"] = "disabled";
|
|
282
|
+
SunspecBatteryFeatureModeKind["RegisterBased"] = "register-based";
|
|
283
|
+
SunspecBatteryFeatureModeKind["CalibrationBased"] = "calibration-based";
|
|
284
|
+
})(SunspecBatteryFeatureModeKind || (exports.SunspecBatteryFeatureModeKind = SunspecBatteryFeatureModeKind = {}));
|
|
260
285
|
var SunspecMeterCapability;
|
|
261
286
|
(function (SunspecMeterCapability) {
|
|
262
287
|
})(SunspecMeterCapability || (exports.SunspecMeterCapability = SunspecMeterCapability = {}));
|
|
288
|
+
/**
|
|
289
|
+
* The four `EnyoBatteryFeature` values that imply outbound control writes from
|
|
290
|
+
* the host action-taker. `SunspecBattery` calibration probes each one
|
|
291
|
+
* individually so `appliance.battery.features` reflects only what the device
|
|
292
|
+
* actually honours.
|
|
293
|
+
*
|
|
294
|
+
* Exposed as `as const` (not a plain `EnyoBatteryFeature[]`) so consumers can
|
|
295
|
+
* iterate the tuple with full type narrowing — see the `WRITABLE_BATTERY_FIELDS`
|
|
296
|
+
* pattern in `sunspec-battery-calibration-driver.ts`.
|
|
297
|
+
*/
|
|
298
|
+
exports.SUNSPEC_CONTROLLABLE_FEATURES = [
|
|
299
|
+
enyo_battery_appliance_js_1.EnyoBatteryFeature.GridCharging,
|
|
300
|
+
enyo_battery_appliance_js_1.EnyoBatteryFeature.GridDischarging,
|
|
301
|
+
enyo_battery_appliance_js_1.EnyoBatteryFeature.ChargeLimitation,
|
|
302
|
+
enyo_battery_appliance_js_1.EnyoBatteryFeature.DischargeLimitation,
|
|
303
|
+
];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SunSpec block interfaces with block numbers
|
|
3
3
|
*/
|
|
4
|
+
import { EnyoBatteryFeature } from "@enyo-energy/energy-app-sdk/dist/types/enyo-battery-appliance.js";
|
|
4
5
|
/**
|
|
5
6
|
* A single phase in the tiered retry schedule
|
|
6
7
|
*/
|
|
@@ -616,6 +617,39 @@ export declare enum SunspecBatteryCapability {
|
|
|
616
617
|
DischargeLimit = "discharge-limit",
|
|
617
618
|
ChargeLimit = "charge-limit"
|
|
618
619
|
}
|
|
620
|
+
/**
|
|
621
|
+
* Selects how `SunspecBattery` populates `appliance.battery.features`.
|
|
622
|
+
*
|
|
623
|
+
* - `Disabled`: ignore the SunSpec registers and ignore calibration. The SDK
|
|
624
|
+
* publishes whatever the consumer puts in `allowedFeatures` (omit or pass
|
|
625
|
+
* `[]` to advertise nothing). Useful when the host already knows what the
|
|
626
|
+
* battery can do and doesn't want the SDK to make any inference.
|
|
627
|
+
* - `RegisterBased`: publish whatever the device's registers expose. If
|
|
628
|
+
* `allowedFeatures` is set, the published list is the intersection — the
|
|
629
|
+
* register-detected set filtered to the allow-list. Lets the consumer hide
|
|
630
|
+
* features that the hardware exposes but they don't want to use.
|
|
631
|
+
* - `CalibrationBased`: same as `RegisterBased`, but controllable features
|
|
632
|
+
* (grid charging, grid discharging, charge limitation, discharge
|
|
633
|
+
* limitation) stay hidden until `CalibrationResultStore.isCalibrated(...)`
|
|
634
|
+
* returns true for this appliance. Requires `configureCalibration(...)` to
|
|
635
|
+
* actually run a calibration; without it the controllable features remain
|
|
636
|
+
* hidden forever (safe-fallback behaviour).
|
|
637
|
+
*/
|
|
638
|
+
export declare enum SunspecBatteryFeatureModeKind {
|
|
639
|
+
Disabled = "disabled",
|
|
640
|
+
RegisterBased = "register-based",
|
|
641
|
+
CalibrationBased = "calibration-based"
|
|
642
|
+
}
|
|
643
|
+
export type SunspecBatteryFeatureMode = {
|
|
644
|
+
kind: SunspecBatteryFeatureModeKind.Disabled;
|
|
645
|
+
allowedFeatures?: EnyoBatteryFeature[];
|
|
646
|
+
} | {
|
|
647
|
+
kind: SunspecBatteryFeatureModeKind.RegisterBased;
|
|
648
|
+
allowedFeatures?: EnyoBatteryFeature[];
|
|
649
|
+
} | {
|
|
650
|
+
kind: SunspecBatteryFeatureModeKind.CalibrationBased;
|
|
651
|
+
allowedFeatures?: EnyoBatteryFeature[];
|
|
652
|
+
};
|
|
619
653
|
export declare enum SunspecMeterCapability {
|
|
620
654
|
}
|
|
621
655
|
export interface SunspecBatteryControls {
|
|
@@ -626,3 +660,35 @@ export interface SunspecBatteryControls {
|
|
|
626
660
|
outWRte?: number;
|
|
627
661
|
minRsvPct?: number;
|
|
628
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* The four `EnyoBatteryFeature` values that imply outbound control writes from
|
|
665
|
+
* the host action-taker. `SunspecBattery` calibration probes each one
|
|
666
|
+
* individually so `appliance.battery.features` reflects only what the device
|
|
667
|
+
* actually honours.
|
|
668
|
+
*
|
|
669
|
+
* Exposed as `as const` (not a plain `EnyoBatteryFeature[]`) so consumers can
|
|
670
|
+
* iterate the tuple with full type narrowing — see the `WRITABLE_BATTERY_FIELDS`
|
|
671
|
+
* pattern in `sunspec-battery-calibration-driver.ts`.
|
|
672
|
+
*/
|
|
673
|
+
export declare const SUNSPEC_CONTROLLABLE_FEATURES: readonly [EnyoBatteryFeature.GridCharging, EnyoBatteryFeature.GridDischarging, EnyoBatteryFeature.ChargeLimitation, EnyoBatteryFeature.DischargeLimitation];
|
|
674
|
+
export type SunspecControllableFeature = typeof SUNSPEC_CONTROLLABLE_FEATURES[number];
|
|
675
|
+
/**
|
|
676
|
+
* Outcome of a single per-feature calibration probe.
|
|
677
|
+
*
|
|
678
|
+
* - `passed`: the SDK observed the expected register/meter response.
|
|
679
|
+
* - `failed`: the SDK wrote the registers but the response was absent or wrong.
|
|
680
|
+
* - `not-supported`: a precondition stopped the probe (battery SoC out of
|
|
681
|
+
* range, missing telemetry, register not exposed). Distinct from `failed`
|
|
682
|
+
* so the host can distinguish "device won't" from "we couldn't even try".
|
|
683
|
+
*/
|
|
684
|
+
export type SunspecFeatureVerdict = 'passed' | 'failed' | 'not-supported';
|
|
685
|
+
/**
|
|
686
|
+
* Per-feature outcomes captured during one calibration session. Serialised
|
|
687
|
+
* into `CalibrationResult.notes` as JSON; decoded with runtime guards by
|
|
688
|
+
* `decodeFeatureResults` (no `as` casts at the use site).
|
|
689
|
+
*/
|
|
690
|
+
export interface SunspecCalibrationFeatureResults {
|
|
691
|
+
featureResults: Partial<Record<SunspecControllableFeature, SunspecFeatureVerdict>>;
|
|
692
|
+
/** Free-form per-probe diagnostics (SoC at start, observed deltas, timeouts). */
|
|
693
|
+
diagnostics?: Partial<Record<SunspecControllableFeature, string>>;
|
|
694
|
+
}
|
package/dist/cjs/version.cjs
CHANGED
|
@@ -9,7 +9,7 @@ exports.getSdkVersion = getSdkVersion;
|
|
|
9
9
|
/**
|
|
10
10
|
* Current version of the enyo Energy App SDK.
|
|
11
11
|
*/
|
|
12
|
-
exports.SDK_VERSION = '0.0.
|
|
12
|
+
exports.SDK_VERSION = '0.0.72';
|
|
13
13
|
/**
|
|
14
14
|
* Gets the current SDK version.
|
|
15
15
|
* @returns The semantic version string of the SDK
|
package/dist/cjs/version.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export * from './sunspec-interfaces.js';
|
|
2
2
|
export * from './sunspec-devices.js';
|
|
3
3
|
export * from './sunspec-modbus-client.js';
|
|
4
|
-
export * from './calibration-snapshot-service.js';
|
|
5
4
|
export { ConnectionRetryManager } from './connection-retry-manager.js';
|
|
6
5
|
export { SDK_VERSION, getSdkVersion } from './version.js';
|
|
6
|
+
export { SunspecCalibrationStorage, createSunspecCalibrationStorage } from './sunspec-calibration-storage.js';
|
|
7
|
+
export { SunspecBatteryCalibrationDriver } from './sunspec-battery-calibration-driver.js';
|
|
8
|
+
export { SunspecBatteryFeatureCalibrator, type SunspecBatteryFeatureCalibratorOptions, decodeFeatureResults, } from './sunspec-battery-feature-calibrator.js';
|
|
9
|
+
export { SunspecBatteryScheduleHandler, type SunspecScheduleRegisters, type SunspecBatteryScheduleHandlerOptions, } from './sunspec-battery-schedule-handler.js';
|
|
10
|
+
export { type EnyoDataBusSetStorageScheduleV1, type EnyoStorageScheduleEntry, EnyoStorageScheduleModeEnum, EnyoStorageScheduleDirectionEnum, } from '@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js';
|
|
11
|
+
export { AbstractCalibrationStorage, InMemoryCalibrationStorage, type AckResult, type CalibrationResult, type CalibrationState, type CalibrationSnapshot, type CalibrationRestoreCallback, type RestoreReason, SnapshotService, type SnapshotServiceOptions, DEFAULT_AUTO_STOP_MS, AbstractBatteryCalibrationDriver, BatteryCalibrator, type BatteryCalibratorOptions, type BatteryCalibratorConfig, DEFAULT_BATTERY_CALIBRATOR_CONFIG, CalibrationResultStore, type CalibrationResultStoreData, CalibrationTrigger, type CalibrationTriggerOptions, } from '@enyo-energy/appliance-calibration';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
export * from './sunspec-interfaces.js';
|
|
2
2
|
export * from './sunspec-devices.js';
|
|
3
3
|
export * from './sunspec-modbus-client.js';
|
|
4
|
-
export * from './calibration-snapshot-service.js';
|
|
5
4
|
export { ConnectionRetryManager } from './connection-retry-manager.js';
|
|
6
5
|
export { SDK_VERSION, getSdkVersion } from './version.js';
|
|
6
|
+
// New calibration integration with @enyo-energy/appliance-calibration
|
|
7
|
+
export { SunspecCalibrationStorage, createSunspecCalibrationStorage } from './sunspec-calibration-storage.js';
|
|
8
|
+
export { SunspecBatteryCalibrationDriver } from './sunspec-battery-calibration-driver.js';
|
|
9
|
+
export { SunspecBatteryFeatureCalibrator, decodeFeatureResults, } from './sunspec-battery-feature-calibrator.js';
|
|
10
|
+
export { SunspecBatteryScheduleHandler, } from './sunspec-battery-schedule-handler.js';
|
|
11
|
+
// Re-export the schedule message types from energy-app-sdk so consumers can
|
|
12
|
+
// build/inspect SetStorageScheduleV1 messages without a second import.
|
|
13
|
+
export { EnyoStorageScheduleModeEnum, EnyoStorageScheduleDirectionEnum, } from '@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js';
|
|
14
|
+
// Re-export the library symbols consumers need to wire up the trigger and gate
|
|
15
|
+
// commands. Avoids a second peer dependency on @enyo-energy/appliance-calibration
|
|
16
|
+
// while keeping the consumer wiring concise.
|
|
17
|
+
export { AbstractCalibrationStorage, InMemoryCalibrationStorage, SnapshotService, DEFAULT_AUTO_STOP_MS, AbstractBatteryCalibrationDriver, BatteryCalibrator, DEFAULT_BATTERY_CALIBRATOR_CONFIG, CalibrationResultStore, CalibrationTrigger, } from '@enyo-energy/appliance-calibration';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { AbstractBatteryCalibrationDriver, type AckResult, type RestoreReason } from "@enyo-energy/appliance-calibration";
|
|
2
|
+
import type { EnergyAppDataBus } from "@enyo-energy/energy-app-sdk/dist/packages/energy-app-data-bus.js";
|
|
3
|
+
import type { SunspecModbusClient } from "./sunspec-modbus-client.js";
|
|
4
|
+
import { type SunspecBatteryControls } from "./sunspec-interfaces.js";
|
|
5
|
+
/**
|
|
6
|
+
* Vendor seam between `@enyo-energy/appliance-calibration`'s `BatteryCalibrator`
|
|
7
|
+
* and a SunSpec battery exposed via {@link SunspecModbusClient}.
|
|
8
|
+
*
|
|
9
|
+
* Battery power is fed in by the owning {@link SunspecBattery} on every readData
|
|
10
|
+
* cycle (call {@link updateBatteryPowerCache}). Grid power is sourced from the
|
|
11
|
+
* data bus: the driver subscribes to `MeterValuesUpdateV1` for the lifetime of
|
|
12
|
+
* the calibrator and caches the latest reading. Call {@link stop} when the
|
|
13
|
+
* battery disconnects to unsubscribe.
|
|
14
|
+
*/
|
|
15
|
+
export declare class SunspecBatteryCalibrationDriver extends AbstractBatteryCalibrationDriver<SunspecBatteryControls> {
|
|
16
|
+
private readonly sunspecClient;
|
|
17
|
+
private readonly unitId;
|
|
18
|
+
private readonly dataBus;
|
|
19
|
+
private readonly batteryPowerCache;
|
|
20
|
+
private readonly gridPowerCache;
|
|
21
|
+
private meterListenerId?;
|
|
22
|
+
constructor(sunspecClient: SunspecModbusClient, unitId: number, dataBus: EnergyAppDataBus);
|
|
23
|
+
private subscribeMeterUpdates;
|
|
24
|
+
/**
|
|
25
|
+
* Tear down the meter subscription. Idempotent — call from
|
|
26
|
+
* `SunspecBattery.disconnect()`.
|
|
27
|
+
*/
|
|
28
|
+
stop(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Push the latest computed battery power (W, positive = charging into the
|
|
31
|
+
* battery) from the owning device. Called from the readData loop.
|
|
32
|
+
*/
|
|
33
|
+
updateBatteryPowerCache(powerW: number): void;
|
|
34
|
+
captureSnapshot(): Promise<SunspecBatteryControls>;
|
|
35
|
+
/**
|
|
36
|
+
* Write only the fields present in the snapshot AND in the writable whitelist.
|
|
37
|
+
* Per the library's contract this is best-effort: log on failure rather than
|
|
38
|
+
* throw, because the persisted snapshot has already been removed by the time
|
|
39
|
+
* we run and any throw would silently swallow what just happened.
|
|
40
|
+
*/
|
|
41
|
+
restoreFromSnapshot(snapshot: SunspecBatteryControls, reason: RestoreReason): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* SunSpec model 124 has no explicit calibration-mode register — the
|
|
44
|
+
* snapshot / restore + storCtlMod sequence *is* the calibration protocol.
|
|
45
|
+
* Resolve immediately so the orchestrator advances to the test charge.
|
|
46
|
+
*/
|
|
47
|
+
enterCalibrationMode(): Promise<AckResult>;
|
|
48
|
+
exitCalibrationMode(): Promise<AckResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Drive the same 3-step sequence proven by `handleStartGridCharge` in
|
|
51
|
+
* {@link SunspecBattery}: charge cap, grid-charging enable, storage mode.
|
|
52
|
+
* Throws on any step failure so the calibrator records a `failed` result.
|
|
53
|
+
*/
|
|
54
|
+
startTestCharge(powerW: number): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Hand the battery back to its normal AUTO / PV state. The snapshot
|
|
57
|
+
* service's restore takes care of returning wChaMax / inWRte / outWRte
|
|
58
|
+
* to baseline.
|
|
59
|
+
*/
|
|
60
|
+
stopTestCharge(): Promise<void>;
|
|
61
|
+
readBatteryPowerW(): number | undefined;
|
|
62
|
+
readGridPowerW(): number | undefined;
|
|
63
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { AbstractBatteryCalibrationDriver, LatestValueCache, } from "@enyo-energy/appliance-calibration";
|
|
2
|
+
import { EnyoDataBusMessageEnum, } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
|
|
3
|
+
import { SunspecStorageMode } from "./sunspec-interfaces.js";
|
|
4
|
+
/**
|
|
5
|
+
* Subset of {@link SunspecBatteryControls} that `writeBatteryControls` accepts.
|
|
6
|
+
* Used both as the calibrator's snapshot payload and as the restore whitelist.
|
|
7
|
+
*/
|
|
8
|
+
const WRITABLE_BATTERY_FIELDS = [
|
|
9
|
+
"storCtlMod",
|
|
10
|
+
"chaGriSet",
|
|
11
|
+
"wChaMax",
|
|
12
|
+
"inWRte",
|
|
13
|
+
"outWRte",
|
|
14
|
+
"minRsvPct",
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Vendor seam between `@enyo-energy/appliance-calibration`'s `BatteryCalibrator`
|
|
18
|
+
* and a SunSpec battery exposed via {@link SunspecModbusClient}.
|
|
19
|
+
*
|
|
20
|
+
* Battery power is fed in by the owning {@link SunspecBattery} on every readData
|
|
21
|
+
* cycle (call {@link updateBatteryPowerCache}). Grid power is sourced from the
|
|
22
|
+
* data bus: the driver subscribes to `MeterValuesUpdateV1` for the lifetime of
|
|
23
|
+
* the calibrator and caches the latest reading. Call {@link stop} when the
|
|
24
|
+
* battery disconnects to unsubscribe.
|
|
25
|
+
*/
|
|
26
|
+
export class SunspecBatteryCalibrationDriver extends AbstractBatteryCalibrationDriver {
|
|
27
|
+
sunspecClient;
|
|
28
|
+
unitId;
|
|
29
|
+
dataBus;
|
|
30
|
+
batteryPowerCache = new LatestValueCache();
|
|
31
|
+
gridPowerCache = new LatestValueCache();
|
|
32
|
+
meterListenerId;
|
|
33
|
+
constructor(sunspecClient, unitId, dataBus) {
|
|
34
|
+
super();
|
|
35
|
+
this.sunspecClient = sunspecClient;
|
|
36
|
+
this.unitId = unitId;
|
|
37
|
+
this.dataBus = dataBus;
|
|
38
|
+
this.subscribeMeterUpdates();
|
|
39
|
+
}
|
|
40
|
+
subscribeMeterUpdates() {
|
|
41
|
+
this.meterListenerId = this.dataBus.listenForMessages([EnyoDataBusMessageEnum.MeterValuesUpdateV1], (entry) => {
|
|
42
|
+
if (entry.message !== EnyoDataBusMessageEnum.MeterValuesUpdateV1) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const meter = entry;
|
|
46
|
+
if (meter.data.gridPowerW !== undefined) {
|
|
47
|
+
this.gridPowerCache.set(meter.data.gridPowerW);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Tear down the meter subscription. Idempotent — call from
|
|
53
|
+
* `SunspecBattery.disconnect()`.
|
|
54
|
+
*/
|
|
55
|
+
stop() {
|
|
56
|
+
if (this.meterListenerId) {
|
|
57
|
+
this.dataBus.unsubscribe(this.meterListenerId);
|
|
58
|
+
this.meterListenerId = undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Push the latest computed battery power (W, positive = charging into the
|
|
63
|
+
* battery) from the owning device. Called from the readData loop.
|
|
64
|
+
*/
|
|
65
|
+
updateBatteryPowerCache(powerW) {
|
|
66
|
+
this.batteryPowerCache.set(powerW);
|
|
67
|
+
}
|
|
68
|
+
async captureSnapshot() {
|
|
69
|
+
const controls = await this.sunspecClient.readBatteryControls(this.unitId);
|
|
70
|
+
if (!controls) {
|
|
71
|
+
throw new Error(`SunspecBatteryCalibrationDriver: readBatteryControls returned null for unit ${this.unitId}`);
|
|
72
|
+
}
|
|
73
|
+
return controls;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Write only the fields present in the snapshot AND in the writable whitelist.
|
|
77
|
+
* Per the library's contract this is best-effort: log on failure rather than
|
|
78
|
+
* throw, because the persisted snapshot has already been removed by the time
|
|
79
|
+
* we run and any throw would silently swallow what just happened.
|
|
80
|
+
*/
|
|
81
|
+
async restoreFromSnapshot(snapshot, reason) {
|
|
82
|
+
const partial = {};
|
|
83
|
+
for (const field of WRITABLE_BATTERY_FIELDS) {
|
|
84
|
+
const value = snapshot[field];
|
|
85
|
+
if (value !== undefined) {
|
|
86
|
+
partial[field] = value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (Object.keys(partial).length === 0) {
|
|
90
|
+
console.log(`SunspecBatteryCalibrationDriver ${this.unitId}: restore (${reason}) — nothing to write`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const ok = await this.sunspecClient.writeBatteryControls(this.unitId, partial);
|
|
95
|
+
if (!ok) {
|
|
96
|
+
console.error(`SunspecBatteryCalibrationDriver ${this.unitId}: restore (${reason}) writeBatteryControls returned false for [${Object.keys(partial).join(", ")}]`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.error(`SunspecBatteryCalibrationDriver ${this.unitId}: restore (${reason}) threw: ${error}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* SunSpec model 124 has no explicit calibration-mode register — the
|
|
105
|
+
* snapshot / restore + storCtlMod sequence *is* the calibration protocol.
|
|
106
|
+
* Resolve immediately so the orchestrator advances to the test charge.
|
|
107
|
+
*/
|
|
108
|
+
async enterCalibrationMode() {
|
|
109
|
+
return "accepted";
|
|
110
|
+
}
|
|
111
|
+
async exitCalibrationMode() {
|
|
112
|
+
return "accepted";
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Drive the same 3-step sequence proven by `handleStartGridCharge` in
|
|
116
|
+
* {@link SunspecBattery}: charge cap, grid-charging enable, storage mode.
|
|
117
|
+
* Throws on any step failure so the calibrator records a `failed` result.
|
|
118
|
+
*/
|
|
119
|
+
async startTestCharge(powerW) {
|
|
120
|
+
const enabledGrid = await this.sunspecClient.enableGridCharging(this.unitId, true);
|
|
121
|
+
if (!enabledGrid) {
|
|
122
|
+
throw new Error("startTestCharge: failed to enable grid charging");
|
|
123
|
+
}
|
|
124
|
+
const wroteChaMax = await this.sunspecClient.writeBatteryControls(this.unitId, { wChaMax: powerW });
|
|
125
|
+
if (!wroteChaMax) {
|
|
126
|
+
throw new Error("startTestCharge: failed to set wChaMax");
|
|
127
|
+
}
|
|
128
|
+
const setMode = await this.sunspecClient.setStorageMode(this.unitId, SunspecStorageMode.CHARGE);
|
|
129
|
+
if (!setMode) {
|
|
130
|
+
throw new Error("startTestCharge: failed to set storage mode CHARGE");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Hand the battery back to its normal AUTO / PV state. The snapshot
|
|
135
|
+
* service's restore takes care of returning wChaMax / inWRte / outWRte
|
|
136
|
+
* to baseline.
|
|
137
|
+
*/
|
|
138
|
+
async stopTestCharge() {
|
|
139
|
+
const setMode = await this.sunspecClient.setStorageMode(this.unitId, SunspecStorageMode.AUTO);
|
|
140
|
+
if (!setMode) {
|
|
141
|
+
throw new Error("stopTestCharge: failed to set storage mode AUTO");
|
|
142
|
+
}
|
|
143
|
+
const disabledGrid = await this.sunspecClient.enableGridCharging(this.unitId, false);
|
|
144
|
+
if (!disabledGrid) {
|
|
145
|
+
throw new Error("stopTestCharge: failed to disable grid charging");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
readBatteryPowerW() {
|
|
149
|
+
return this.batteryPowerCache.get();
|
|
150
|
+
}
|
|
151
|
+
readGridPowerW() {
|
|
152
|
+
return this.gridPowerCache.get();
|
|
153
|
+
}
|
|
154
|
+
}
|