@enyo-energy/sunspec-sdk 0.0.71 → 0.0.73

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 +302 -0
  2. package/dist/cjs/index.cjs +30 -2
  3. package/dist/cjs/index.d.cts +6 -1
  4. package/dist/cjs/sunspec-battery-calibration-driver.cjs +158 -0
  5. package/dist/cjs/sunspec-battery-calibration-driver.d.cts +63 -0
  6. package/dist/cjs/sunspec-battery-feature-calibrator.cjs +350 -0
  7. package/dist/cjs/sunspec-battery-feature-calibrator.d.cts +89 -0
  8. package/dist/cjs/sunspec-battery-schedule-handler.cjs +92 -0
  9. package/dist/cjs/sunspec-battery-schedule-handler.d.cts +67 -0
  10. package/dist/cjs/sunspec-calibration-storage.cjs +47 -0
  11. package/dist/cjs/sunspec-calibration-storage.d.cts +24 -0
  12. package/dist/cjs/sunspec-devices.cjs +305 -215
  13. package/dist/cjs/sunspec-devices.d.cts +129 -19
  14. package/dist/cjs/sunspec-interfaces.cjs +42 -1
  15. package/dist/cjs/sunspec-interfaces.d.cts +66 -0
  16. package/dist/cjs/version.cjs +1 -1
  17. package/dist/cjs/version.d.cts +1 -1
  18. package/dist/index.d.ts +6 -1
  19. package/dist/index.js +12 -1
  20. package/dist/sunspec-battery-calibration-driver.d.ts +63 -0
  21. package/dist/sunspec-battery-calibration-driver.js +154 -0
  22. package/dist/sunspec-battery-feature-calibrator.d.ts +89 -0
  23. package/dist/sunspec-battery-feature-calibrator.js +345 -0
  24. package/dist/sunspec-battery-schedule-handler.d.ts +67 -0
  25. package/dist/sunspec-battery-schedule-handler.js +88 -0
  26. package/dist/sunspec-calibration-storage.d.ts +24 -0
  27. package/dist/sunspec-calibration-storage.js +42 -0
  28. package/dist/sunspec-devices.d.ts +129 -19
  29. package/dist/sunspec-devices.js +304 -216
  30. package/dist/sunspec-interfaces.d.ts +66 -0
  31. package/dist/sunspec-interfaces.js +41 -0
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/package.json +7 -3
@@ -1,11 +1,12 @@
1
- import { type IRetryConfig, type SunspecBatteryBaseData, SunspecBatteryCapability, type SunspecBatteryControls, type SunspecInverterData, SunspecInverterCapability, SunspecMeterCapability, SunspecStorageMode } from "./sunspec-interfaces.js";
1
+ import { type IRetryConfig, type SunspecBatteryBaseData, SunspecBatteryCapability, type SunspecBatteryControls, type SunspecBatteryData, type SunspecBatteryFeatureMode, type SunspecInverterData, SunspecInverterCapability, SunspecMeterCapability, SunspecStorageMode } from "./sunspec-interfaces.js";
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.js";
6
6
  import { ConnectionRetryManager } from "./connection-retry-manager.js";
7
7
  import { EnyoCommandAcknowledgeAnswerEnum, EnyoDataBusMessage, EnyoDataBusMessageEnum, EnyoDataBusMessageResolution } from "@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js";
8
- import { type CalibrationDeviceType, type CalibrationRestoreReason, type CalibrationSnapshot, CalibrationSnapshotService } from "./calibration-snapshot-service.js";
8
+ import { BatteryCalibrator, type BatteryCalibratorConfig, type CalibrationResult, type CalibrationResultStore, type CalibrationSnapshot, type RestoreReason, SnapshotService } from "@enyo-energy/appliance-calibration";
9
+ import { EnyoBatteryFeature } from "@enyo-energy/energy-app-sdk/dist/types/enyo-battery-appliance.js";
9
10
  import { EnergyAppDataBus } from "@enyo-energy/energy-app-sdk/dist/packages/energy-app-data-bus.js";
10
11
  /**
11
12
  * Base abstract class for all Sunspec devices
@@ -26,7 +27,13 @@ export declare abstract class BaseSunspecDevice {
26
27
  protected dataBus?: EnergyAppDataBus;
27
28
  protected retryManager: ConnectionRetryManager;
28
29
  protected consecutiveReconnectFailures: number;
29
- protected calibrationService?: CalibrationSnapshotService;
30
+ /**
31
+ * Prefix used when persisting calibration snapshots via the library's
32
+ * {@link SnapshotService}. Kept identical to the key the SDK used before
33
+ * the migration to `@enyo-energy/appliance-calibration` so existing
34
+ * snapshots stored by older builds are still picked up on startup.
35
+ */
36
+ protected static readonly CALIBRATION_SNAPSHOT_KEY_PREFIX = "sunspec-calibration-snapshot-";
30
37
  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
38
  /**
32
39
  * Connect to the device and create/update the appliance
@@ -80,16 +87,11 @@ export declare abstract class BaseSunspecDevice {
80
87
  protected markOffline(): Promise<void>;
81
88
  protected sendCommandAcknowledge(messageId: string, acknowledgeMessage: EnyoDataBusMessageEnum | string, answer: EnyoCommandAcknowledgeAnswerEnum, rejectionReason?: string): void;
82
89
  /**
83
- * Lazily construct and initialize the per-appliance CalibrationSnapshotService. Reloads
84
- * any persisted snapshot from storage so an in-flight calibration survives restarts.
85
- * Idempotent subsequent calls are no-ops once the service exists.
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.
90
+ * Build a typed {@link SnapshotService} bound to this appliance's calibration storage,
91
+ * using the legacy SDK storage key prefix so prior installs upgrade seamlessly. Returns
92
+ * undefined if the appliance has not been registered yet.
91
93
  */
92
- protected restoreFromCalibrationSnapshot(_snapshot: CalibrationSnapshot, _reason: CalibrationRestoreReason): Promise<void>;
94
+ protected buildSnapshotService<T>(onRestore: (snapshot: CalibrationSnapshot<T>, reason: RestoreReason) => Promise<void>): SnapshotService<T> | undefined;
93
95
  }
94
96
  /**
95
97
  * Sunspec Inverter implementation using dynamic model discovery
@@ -100,6 +102,7 @@ export declare class SunspecInverter extends BaseSunspecDevice {
100
102
  private static readonly CONNECTION_FAULT_THRESHOLD;
101
103
  private storage?;
102
104
  private errorState;
105
+ private snapshotService?;
103
106
  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
107
  connect(): Promise<void>;
105
108
  disconnect(): Promise<void>;
@@ -169,16 +172,106 @@ export declare class SunspecInverter extends BaseSunspecDevice {
169
172
  stopDataBusListening(): void;
170
173
  private handleInverterCommand;
171
174
  private handleSetFeedInLimit;
175
+ /**
176
+ * Lazily construct the per-appliance {@link SnapshotService}. Reloads any
177
+ * persisted snapshot from storage so an in-flight calibration survives
178
+ * process restarts. Idempotent.
179
+ */
180
+ private initSnapshotService;
172
181
  private handleStartCalibration;
173
182
  private handleStopCalibration;
174
- protected restoreFromCalibrationSnapshot(snapshot: CalibrationSnapshot, reason: CalibrationRestoreReason): Promise<void>;
183
+ /**
184
+ * `onRestore` callback for the inverter's {@link SnapshotService}. Writes only the
185
+ * subset of writable inverter fields that other commands actually touched during the
186
+ * calibration. Catches and logs failures rather than throwing — the snapshot has
187
+ * already been removed from storage by the time this runs, so a throw is unrecoverable
188
+ * and is best logged for operator action.
189
+ */
190
+ private restoreInverterSnapshot;
175
191
  }
192
+ /**
193
+ * Pure register-presence detection — exported so it can be unit-tested
194
+ * without the full `SunspecBattery` scaffold. Maps each Model 124 writable
195
+ * register to the `EnyoBatteryFeature` it represents:
196
+ *
197
+ * - `chaGriSet` → `GridCharging` (charge source PV/GRID)
198
+ * - `wChaMax` → `ChargeLimitation` (max charge power)
199
+ * - `outWRte` → `DischargeLimitation` (discharge rate %)
200
+ * - `storCtlMod` → `GridDischarging` (closest signal that external discharge
201
+ * control is available; the inverter decides where it goes)
202
+ */
203
+ export declare function detectFeaturesFromRegisters(batteryData: SunspecBatteryData | null): EnyoBatteryFeature[];
204
+ /**
205
+ * Apply the per-feature calibration filter to a register-detected set.
206
+ * Exported for unit-test access to the legacy-result fallback branch.
207
+ *
208
+ * The contract:
209
+ * - No `result` or `result.state !== 'calibrated'` → strip every controllable
210
+ * feature (safe fallback while waiting for calibration).
211
+ * - `result.state === 'calibrated'` with a decodable per-feature payload →
212
+ * publish only the controllable features whose probes passed.
213
+ * - `result.state === 'calibrated'` with **no** decodable payload → legacy
214
+ * data (pre-feature-calibrator SDK). The new calibrator only marks
215
+ * `state=calibrated` when at least one probe passes, so an empty decoded
216
+ * set with `state=calibrated` unambiguously signals legacy results. Publish
217
+ * the full detected set to preserve the old all-or-nothing semantics on
218
+ * upgrade.
219
+ */
220
+ export declare function filterFeaturesByCalibrationResult(detected: EnyoBatteryFeature[], result: CalibrationResult | undefined, controllable: readonly EnyoBatteryFeature[]): EnyoBatteryFeature[];
176
221
  /**
177
222
  * Sunspec Battery implementation
178
223
  */
179
224
  export declare class SunspecBattery extends BaseSunspecDevice {
225
+ private readonly featureMode;
180
226
  private readonly capabilities;
181
- constructor(energyApp: EnergyApp, name: EnyoApplianceName[], networkDevice: EnyoNetworkDevice, sunspecClient: SunspecModbusClient, applianceManager: ApplianceManager, unitId?: number, port?: number, baseAddress?: number, capabilities?: SunspecBatteryCapability[], retryConfig?: IRetryConfig, appliance?: EnyoAppliance, useTls?: boolean);
227
+ private snapshotService?;
228
+ private calibrationDriver?;
229
+ private batteryCalibrator?;
230
+ private calibrationResultStore?;
231
+ private scheduleHandler;
232
+ /**
233
+ * Battery features that imply outbound control writes from the host action-taker.
234
+ * These are stripped from `appliance.battery.features` until the calibration result
235
+ * store says this battery has been successfully calibrated — see
236
+ * {@link computeAdvertisedFeatures}.
237
+ */
238
+ private static readonly CONTROLLABLE_FEATURES;
239
+ 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);
240
+ /**
241
+ * Wire the battery into `@enyo-energy/appliance-calibration`'s
242
+ * `BatteryCalibrator` test-charge flow. Call once after {@link connect}.
243
+ *
244
+ * Consumers own the `CalibrationResultStore` (shared across appliances so
245
+ * the persisted result map doesn't get clobbered) and the
246
+ * `CalibrationTrigger` that drives `runCalibration()` on whatever schedule
247
+ * makes sense for the host. Pass overrides for the calibrator's defaults
248
+ * (`testPowerW`, response thresholds, etc.) via `opts.config`.
249
+ */
250
+ configureCalibration(opts: {
251
+ resultStore: CalibrationResultStore;
252
+ config?: Partial<BatteryCalibratorConfig>;
253
+ }): BatteryCalibrator<SunspecBatteryControls>;
254
+ /**
255
+ * Detect which battery features the device's registers expose. This is the
256
+ * raw, register-only view; the {@link featureMode} configured at
257
+ * construction decides what is actually published — see
258
+ * {@link resolveAdvertisedFeatures}.
259
+ */
260
+ private detectFromRegisters;
261
+ /**
262
+ * Resolve `appliance.battery.features` for the configured mode. Called on
263
+ * every connect() and readData() cycle so that, in calibration-based mode,
264
+ * the controllable features appear as soon as the calibrator flips
265
+ * `isCalibrated` (no synchronous push).
266
+ */
267
+ private resolveAdvertisedFeatures;
268
+ /**
269
+ * Returns the per-battery `BatteryCalibrator` created by
270
+ * {@link configureCalibration}, or `undefined` if calibration was never
271
+ * configured. Consumers register the returned instance with their own
272
+ * `CalibrationTrigger`.
273
+ */
274
+ getBatteryCalibrator(): BatteryCalibrator<SunspecBatteryControls> | undefined;
182
275
  /**
183
276
  * Connect to the battery and create/update the appliance
184
277
  */
@@ -300,13 +393,30 @@ export declare class SunspecBattery extends BaseSunspecDevice {
300
393
  */
301
394
  stopDataBusListening(): void;
302
395
  private handleStorageCommand;
303
- private handleStartGridCharge;
304
- private handleStopGridCharge;
305
- private handleSetDischargeLimit;
306
- private handleSetChargeLimit;
396
+ /**
397
+ * Acknowledge a SetStorageScheduleV1 message. The actual schedule application
398
+ * is owned by `this.scheduleHandler` (subscribed independently via its own
399
+ * data-bus listener registered in its constructor). The ack reports "received
400
+ * & queued" rather than "applied" — schedule entries play out over time, and
401
+ * per-entry write failures land in `console.error` via the handler's
402
+ * onChange wrapper.
403
+ */
404
+ private handleSetStorageScheduleAck;
405
+ /**
406
+ * Lazily construct the per-appliance {@link SnapshotService}. Reloads any
407
+ * persisted snapshot from storage so an in-flight calibration survives
408
+ * process restarts. Idempotent.
409
+ */
410
+ private initSnapshotService;
307
411
  private handleStartCalibration;
308
412
  private handleStopCalibration;
309
- protected restoreFromCalibrationSnapshot(snapshot: CalibrationSnapshot, reason: CalibrationRestoreReason): Promise<void>;
413
+ /**
414
+ * `onRestore` callback for the battery's {@link SnapshotService}. Writes only the
415
+ * subset of writable battery fields touched during the calibration. Errors are
416
+ * caught and logged — by the time this fires the snapshot is gone from storage,
417
+ * so a throw would just propagate into the void.
418
+ */
419
+ private restoreBatterySnapshot;
310
420
  }
311
421
  /**
312
422
  * Sunspec Meter implementation