@enyo-energy/sunspec-sdk 0.0.70 → 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.
Files changed (38) hide show
  1. package/README.md +302 -0
  2. package/dist/calibration-snapshot-service.d.ts +67 -0
  3. package/dist/calibration-snapshot-service.js +160 -0
  4. package/dist/cjs/calibration-snapshot-service.cjs +164 -0
  5. package/dist/cjs/calibration-snapshot-service.d.cts +67 -0
  6. package/dist/cjs/index.cjs +30 -1
  7. package/dist/cjs/index.d.cts +6 -0
  8. package/dist/cjs/sunspec-battery-calibration-driver.cjs +158 -0
  9. package/dist/cjs/sunspec-battery-calibration-driver.d.cts +63 -0
  10. package/dist/cjs/sunspec-battery-feature-calibrator.cjs +350 -0
  11. package/dist/cjs/sunspec-battery-feature-calibrator.d.cts +89 -0
  12. package/dist/cjs/sunspec-battery-schedule-handler.cjs +92 -0
  13. package/dist/cjs/sunspec-battery-schedule-handler.d.cts +67 -0
  14. package/dist/cjs/sunspec-calibration-storage.cjs +47 -0
  15. package/dist/cjs/sunspec-calibration-storage.d.cts +24 -0
  16. package/dist/cjs/sunspec-devices.cjs +407 -104
  17. package/dist/cjs/sunspec-devices.d.cts +112 -6
  18. package/dist/cjs/sunspec-interfaces.cjs +42 -1
  19. package/dist/cjs/sunspec-interfaces.d.cts +66 -0
  20. package/dist/cjs/version.cjs +1 -1
  21. package/dist/cjs/version.d.cts +1 -1
  22. package/dist/index.d.ts +6 -0
  23. package/dist/index.js +12 -0
  24. package/dist/sunspec-battery-calibration-driver.d.ts +63 -0
  25. package/dist/sunspec-battery-calibration-driver.js +154 -0
  26. package/dist/sunspec-battery-feature-calibrator.d.ts +89 -0
  27. package/dist/sunspec-battery-feature-calibrator.js +345 -0
  28. package/dist/sunspec-battery-schedule-handler.d.ts +67 -0
  29. package/dist/sunspec-battery-schedule-handler.js +88 -0
  30. package/dist/sunspec-calibration-storage.d.ts +24 -0
  31. package/dist/sunspec-calibration-storage.js +42 -0
  32. package/dist/sunspec-devices.d.ts +112 -6
  33. package/dist/sunspec-devices.js +408 -105
  34. package/dist/sunspec-interfaces.d.ts +66 -0
  35. package/dist/sunspec-interfaces.js +41 -0
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +7 -3
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CalibrationSnapshotService = exports.CALIBRATION_AUTO_STOP_MS = void 0;
4
+ /** Calibration is capped at 5 minutes of wall time, including across restarts. */
5
+ exports.CALIBRATION_AUTO_STOP_MS = 5 * 60 * 1000;
6
+ /**
7
+ * Per-appliance service that snapshots a SunSpec device's writable control registers when a
8
+ * calibration starts, tracks which registers other commands mutate while it is active, and
9
+ * restores those registers when calibration stops (either explicitly or via the 5-minute
10
+ * auto-stop timer).
11
+ *
12
+ * Snapshots are persisted via `EnergyAppStorage` so a crash/restart mid-calibration does not
13
+ * leave the device in a calibration state without a rollback. On `initialize()`, any stored
14
+ * snapshot is reloaded; the auto-stop deadline is computed from `startedAtIso` so total
15
+ * calibration time remains capped at `autoStopMs` across restarts.
16
+ */
17
+ class CalibrationSnapshotService {
18
+ storage;
19
+ applianceId;
20
+ onRestore;
21
+ autoStopMs;
22
+ snapshot;
23
+ autoStopTimer;
24
+ constructor(storage, applianceId, onRestore, autoStopMs = exports.CALIBRATION_AUTO_STOP_MS) {
25
+ this.storage = storage;
26
+ this.applianceId = applianceId;
27
+ this.onRestore = onRestore;
28
+ this.autoStopMs = autoStopMs;
29
+ }
30
+ storageKey() {
31
+ return `sunspec-calibration-snapshot-${this.applianceId}`;
32
+ }
33
+ async initialize() {
34
+ try {
35
+ const loaded = await this.storage.load(this.storageKey());
36
+ if (!loaded) {
37
+ return;
38
+ }
39
+ this.snapshot = loaded;
40
+ console.log(`CalibrationSnapshotService ${this.applianceId}: loaded persisted snapshot (started ${loaded.startedAtIso}, modifiedFields=[${loaded.modifiedFields.join(', ')}])`);
41
+ const remainingMs = this.remainingMs(loaded);
42
+ if (remainingMs <= 0) {
43
+ console.warn(`CalibrationSnapshotService ${this.applianceId}: snapshot exceeded ${this.autoStopMs}ms deadline — auto-restoring immediately`);
44
+ await this.fireAutoStop();
45
+ }
46
+ else {
47
+ this.scheduleAutoStop(remainingMs);
48
+ }
49
+ }
50
+ catch (error) {
51
+ console.error(`CalibrationSnapshotService ${this.applianceId}: failed to load persisted snapshot: ${error}`);
52
+ }
53
+ }
54
+ isCalibrating() {
55
+ return this.snapshot !== undefined;
56
+ }
57
+ getSnapshot() {
58
+ return this.snapshot;
59
+ }
60
+ async startCalibration(input) {
61
+ this.clearAutoStopTimer();
62
+ this.snapshot = {
63
+ applianceId: this.applianceId,
64
+ deviceType: input.deviceType,
65
+ unitId: input.unitId,
66
+ startedAtIso: new Date().toISOString(),
67
+ inverterControls: input.inverterControls,
68
+ batteryControls: input.batteryControls,
69
+ modifiedFields: [],
70
+ };
71
+ await this.persist();
72
+ this.scheduleAutoStop(this.autoStopMs);
73
+ console.log(`CalibrationSnapshotService ${this.applianceId}: calibration started for ${input.deviceType} (auto-stop in ${this.autoStopMs}ms)`);
74
+ }
75
+ async recordModification(fields) {
76
+ if (!this.snapshot) {
77
+ return;
78
+ }
79
+ let added = false;
80
+ for (const field of fields) {
81
+ if (!this.snapshot.modifiedFields.includes(field)) {
82
+ this.snapshot.modifiedFields.push(field);
83
+ added = true;
84
+ }
85
+ }
86
+ if (added) {
87
+ await this.persist();
88
+ console.log(`CalibrationSnapshotService ${this.applianceId}: tracked modified fields [${this.snapshot.modifiedFields.join(', ')}]`);
89
+ }
90
+ }
91
+ /**
92
+ * Clear the timer, remove the snapshot from memory + storage, and return the snapshot so
93
+ * the caller can restore the modified registers. Returns undefined if no calibration was
94
+ * active.
95
+ */
96
+ async stopCalibration() {
97
+ this.clearAutoStopTimer();
98
+ const snap = this.snapshot;
99
+ this.snapshot = undefined;
100
+ if (snap) {
101
+ try {
102
+ await this.storage.remove(this.storageKey());
103
+ }
104
+ catch (error) {
105
+ console.error(`CalibrationSnapshotService ${this.applianceId}: failed to remove persisted snapshot: ${error}`);
106
+ }
107
+ }
108
+ return snap;
109
+ }
110
+ remainingMs(snap) {
111
+ const startedAtMs = Date.parse(snap.startedAtIso);
112
+ if (Number.isNaN(startedAtMs)) {
113
+ return this.autoStopMs;
114
+ }
115
+ return startedAtMs + this.autoStopMs - Date.now();
116
+ }
117
+ scheduleAutoStop(delayMs) {
118
+ this.clearAutoStopTimer();
119
+ this.autoStopTimer = setTimeout(() => {
120
+ void this.fireAutoStop();
121
+ }, delayMs);
122
+ if (typeof this.autoStopTimer.unref === "function") {
123
+ this.autoStopTimer.unref();
124
+ }
125
+ }
126
+ clearAutoStopTimer() {
127
+ if (this.autoStopTimer) {
128
+ clearTimeout(this.autoStopTimer);
129
+ this.autoStopTimer = undefined;
130
+ }
131
+ }
132
+ async fireAutoStop() {
133
+ const snap = this.snapshot;
134
+ this.snapshot = undefined;
135
+ this.clearAutoStopTimer();
136
+ if (!snap) {
137
+ return;
138
+ }
139
+ try {
140
+ await this.storage.remove(this.storageKey());
141
+ }
142
+ catch (error) {
143
+ console.error(`CalibrationSnapshotService ${this.applianceId}: failed to remove persisted snapshot during auto-stop: ${error}`);
144
+ }
145
+ try {
146
+ await this.onRestore(snap, "auto");
147
+ }
148
+ catch (error) {
149
+ console.error(`CalibrationSnapshotService ${this.applianceId}: auto-stop restore failed: ${error}`);
150
+ }
151
+ }
152
+ async persist() {
153
+ if (!this.snapshot) {
154
+ return;
155
+ }
156
+ try {
157
+ await this.storage.save(this.storageKey(), this.snapshot);
158
+ }
159
+ catch (error) {
160
+ console.error(`CalibrationSnapshotService ${this.applianceId}: failed to persist snapshot: ${error}`);
161
+ }
162
+ }
163
+ }
164
+ exports.CalibrationSnapshotService = CalibrationSnapshotService;
@@ -0,0 +1,67 @@
1
+ import { EnergyAppStorage } from "@enyo-energy/energy-app-sdk/dist/packages/energy-app-storage.js";
2
+ import type { SunspecBatteryControls, SunspecInverterControls } from "./sunspec-interfaces.cjs";
3
+ export type CalibrationDeviceType = "inverter" | "battery";
4
+ /**
5
+ * Persisted snapshot of a device's writable control registers, taken at the start of a
6
+ * calibration. Used to roll the device back when calibration ends.
7
+ *
8
+ * `modifiedFields` records which control-block field names were touched by *other* write
9
+ * commands (SetInverterFeedInLimitV1, StartStorageGridChargeV1, etc.) while the calibration
10
+ * was active — on stop, only those fields are written back, leaving fields the calibration
11
+ * itself didn't disturb alone.
12
+ */
13
+ export interface CalibrationSnapshot {
14
+ applianceId: string;
15
+ deviceType: CalibrationDeviceType;
16
+ unitId: number;
17
+ startedAtIso: string;
18
+ inverterControls?: SunspecInverterControls;
19
+ batteryControls?: SunspecBatteryControls;
20
+ modifiedFields: string[];
21
+ }
22
+ /** Calibration is capped at 5 minutes of wall time, including across restarts. */
23
+ export declare const CALIBRATION_AUTO_STOP_MS: number;
24
+ export type CalibrationRestoreReason = "stop" | "auto";
25
+ export type CalibrationRestoreCallback = (snapshot: CalibrationSnapshot, reason: CalibrationRestoreReason) => Promise<void>;
26
+ /**
27
+ * Per-appliance service that snapshots a SunSpec device's writable control registers when a
28
+ * calibration starts, tracks which registers other commands mutate while it is active, and
29
+ * restores those registers when calibration stops (either explicitly or via the 5-minute
30
+ * auto-stop timer).
31
+ *
32
+ * Snapshots are persisted via `EnergyAppStorage` so a crash/restart mid-calibration does not
33
+ * leave the device in a calibration state without a rollback. On `initialize()`, any stored
34
+ * snapshot is reloaded; the auto-stop deadline is computed from `startedAtIso` so total
35
+ * calibration time remains capped at `autoStopMs` across restarts.
36
+ */
37
+ export declare class CalibrationSnapshotService {
38
+ private readonly storage;
39
+ private readonly applianceId;
40
+ private readonly onRestore;
41
+ private readonly autoStopMs;
42
+ private snapshot?;
43
+ private autoStopTimer?;
44
+ constructor(storage: EnergyAppStorage, applianceId: string, onRestore: CalibrationRestoreCallback, autoStopMs?: number);
45
+ private storageKey;
46
+ initialize(): Promise<void>;
47
+ isCalibrating(): boolean;
48
+ getSnapshot(): CalibrationSnapshot | undefined;
49
+ startCalibration(input: {
50
+ deviceType: CalibrationDeviceType;
51
+ unitId: number;
52
+ inverterControls?: SunspecInverterControls;
53
+ batteryControls?: SunspecBatteryControls;
54
+ }): Promise<void>;
55
+ recordModification(fields: string[]): Promise<void>;
56
+ /**
57
+ * Clear the timer, remove the snapshot from memory + storage, and return the snapshot so
58
+ * the caller can restore the modified registers. Returns undefined if no calibration was
59
+ * active.
60
+ */
61
+ stopCalibration(): Promise<CalibrationSnapshot | undefined>;
62
+ private remainingMs;
63
+ private scheduleAutoStop;
64
+ private clearAutoStopTimer;
65
+ private fireAutoStop;
66
+ private persist;
67
+ }
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.getSdkVersion = exports.SDK_VERSION = exports.ConnectionRetryManager = void 0;
17
+ exports.CalibrationTrigger = exports.CalibrationResultStore = exports.DEFAULT_BATTERY_CALIBRATOR_CONFIG = exports.BatteryCalibrator = exports.AbstractBatteryCalibrationDriver = exports.DEFAULT_AUTO_STOP_MS = exports.SnapshotService = exports.InMemoryCalibrationStorage = exports.AbstractCalibrationStorage = exports.EnyoStorageScheduleDirectionEnum = exports.EnyoStorageScheduleModeEnum = exports.SunspecBatteryScheduleHandler = exports.decodeFeatureResults = exports.SunspecBatteryFeatureCalibrator = exports.SunspecBatteryCalibrationDriver = exports.createSunspecCalibrationStorage = exports.SunspecCalibrationStorage = exports.getSdkVersion = exports.SDK_VERSION = exports.ConnectionRetryManager = void 0;
18
18
  __exportStar(require("./sunspec-interfaces.cjs"), exports);
19
19
  __exportStar(require("./sunspec-devices.cjs"), exports);
20
20
  __exportStar(require("./sunspec-modbus-client.cjs"), exports);
@@ -23,3 +23,32 @@ Object.defineProperty(exports, "ConnectionRetryManager", { enumerable: true, get
23
23
  var version_js_1 = require("./version.cjs");
24
24
  Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return version_js_1.SDK_VERSION; } });
25
25
  Object.defineProperty(exports, "getSdkVersion", { enumerable: true, get: function () { return version_js_1.getSdkVersion; } });
26
+ // New calibration integration with @enyo-energy/appliance-calibration
27
+ var sunspec_calibration_storage_js_1 = require("./sunspec-calibration-storage.cjs");
28
+ Object.defineProperty(exports, "SunspecCalibrationStorage", { enumerable: true, get: function () { return sunspec_calibration_storage_js_1.SunspecCalibrationStorage; } });
29
+ Object.defineProperty(exports, "createSunspecCalibrationStorage", { enumerable: true, get: function () { return sunspec_calibration_storage_js_1.createSunspecCalibrationStorage; } });
30
+ var sunspec_battery_calibration_driver_js_1 = require("./sunspec-battery-calibration-driver.cjs");
31
+ Object.defineProperty(exports, "SunspecBatteryCalibrationDriver", { enumerable: true, get: function () { return sunspec_battery_calibration_driver_js_1.SunspecBatteryCalibrationDriver; } });
32
+ var sunspec_battery_feature_calibrator_js_1 = require("./sunspec-battery-feature-calibrator.cjs");
33
+ Object.defineProperty(exports, "SunspecBatteryFeatureCalibrator", { enumerable: true, get: function () { return sunspec_battery_feature_calibrator_js_1.SunspecBatteryFeatureCalibrator; } });
34
+ Object.defineProperty(exports, "decodeFeatureResults", { enumerable: true, get: function () { return sunspec_battery_feature_calibrator_js_1.decodeFeatureResults; } });
35
+ var sunspec_battery_schedule_handler_js_1 = require("./sunspec-battery-schedule-handler.cjs");
36
+ Object.defineProperty(exports, "SunspecBatteryScheduleHandler", { enumerable: true, get: function () { return sunspec_battery_schedule_handler_js_1.SunspecBatteryScheduleHandler; } });
37
+ // Re-export the schedule message types from energy-app-sdk so consumers can
38
+ // build/inspect SetStorageScheduleV1 messages without a second import.
39
+ var enyo_data_bus_value_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js");
40
+ Object.defineProperty(exports, "EnyoStorageScheduleModeEnum", { enumerable: true, get: function () { return enyo_data_bus_value_js_1.EnyoStorageScheduleModeEnum; } });
41
+ Object.defineProperty(exports, "EnyoStorageScheduleDirectionEnum", { enumerable: true, get: function () { return enyo_data_bus_value_js_1.EnyoStorageScheduleDirectionEnum; } });
42
+ // Re-export the library symbols consumers need to wire up the trigger and gate
43
+ // commands. Avoids a second peer dependency on @enyo-energy/appliance-calibration
44
+ // while keeping the consumer wiring concise.
45
+ var appliance_calibration_1 = require("@enyo-energy/appliance-calibration");
46
+ Object.defineProperty(exports, "AbstractCalibrationStorage", { enumerable: true, get: function () { return appliance_calibration_1.AbstractCalibrationStorage; } });
47
+ Object.defineProperty(exports, "InMemoryCalibrationStorage", { enumerable: true, get: function () { return appliance_calibration_1.InMemoryCalibrationStorage; } });
48
+ Object.defineProperty(exports, "SnapshotService", { enumerable: true, get: function () { return appliance_calibration_1.SnapshotService; } });
49
+ Object.defineProperty(exports, "DEFAULT_AUTO_STOP_MS", { enumerable: true, get: function () { return appliance_calibration_1.DEFAULT_AUTO_STOP_MS; } });
50
+ Object.defineProperty(exports, "AbstractBatteryCalibrationDriver", { enumerable: true, get: function () { return appliance_calibration_1.AbstractBatteryCalibrationDriver; } });
51
+ Object.defineProperty(exports, "BatteryCalibrator", { enumerable: true, get: function () { return appliance_calibration_1.BatteryCalibrator; } });
52
+ Object.defineProperty(exports, "DEFAULT_BATTERY_CALIBRATOR_CONFIG", { enumerable: true, get: function () { return appliance_calibration_1.DEFAULT_BATTERY_CALIBRATOR_CONFIG; } });
53
+ Object.defineProperty(exports, "CalibrationResultStore", { enumerable: true, get: function () { return appliance_calibration_1.CalibrationResultStore; } });
54
+ Object.defineProperty(exports, "CalibrationTrigger", { enumerable: true, get: function () { return appliance_calibration_1.CalibrationTrigger; } });
@@ -3,3 +3,9 @@ export * from './sunspec-devices.cjs';
3
3
  export * from './sunspec-modbus-client.cjs';
4
4
  export { ConnectionRetryManager } from './connection-retry-manager.cjs';
5
5
  export { SDK_VERSION, getSdkVersion } from './version.cjs';
6
+ export { SunspecCalibrationStorage, createSunspecCalibrationStorage } from './sunspec-calibration-storage.cjs';
7
+ export { SunspecBatteryCalibrationDriver } from './sunspec-battery-calibration-driver.cjs';
8
+ export { SunspecBatteryFeatureCalibrator, type SunspecBatteryFeatureCalibratorOptions, decodeFeatureResults, } from './sunspec-battery-feature-calibrator.cjs';
9
+ export { SunspecBatteryScheduleHandler, type SunspecScheduleRegisters, type SunspecBatteryScheduleHandlerOptions, } from './sunspec-battery-schedule-handler.cjs';
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';
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SunspecBatteryCalibrationDriver = void 0;
4
+ const appliance_calibration_1 = require("@enyo-energy/appliance-calibration");
5
+ const enyo_data_bus_value_js_1 = require("@enyo-energy/energy-app-sdk/dist/types/enyo-data-bus-value.js");
6
+ const sunspec_interfaces_js_1 = require("./sunspec-interfaces.cjs");
7
+ /**
8
+ * Subset of {@link SunspecBatteryControls} that `writeBatteryControls` accepts.
9
+ * Used both as the calibrator's snapshot payload and as the restore whitelist.
10
+ */
11
+ const WRITABLE_BATTERY_FIELDS = [
12
+ "storCtlMod",
13
+ "chaGriSet",
14
+ "wChaMax",
15
+ "inWRte",
16
+ "outWRte",
17
+ "minRsvPct",
18
+ ];
19
+ /**
20
+ * Vendor seam between `@enyo-energy/appliance-calibration`'s `BatteryCalibrator`
21
+ * and a SunSpec battery exposed via {@link SunspecModbusClient}.
22
+ *
23
+ * Battery power is fed in by the owning {@link SunspecBattery} on every readData
24
+ * cycle (call {@link updateBatteryPowerCache}). Grid power is sourced from the
25
+ * data bus: the driver subscribes to `MeterValuesUpdateV1` for the lifetime of
26
+ * the calibrator and caches the latest reading. Call {@link stop} when the
27
+ * battery disconnects to unsubscribe.
28
+ */
29
+ class SunspecBatteryCalibrationDriver extends appliance_calibration_1.AbstractBatteryCalibrationDriver {
30
+ sunspecClient;
31
+ unitId;
32
+ dataBus;
33
+ batteryPowerCache = new appliance_calibration_1.LatestValueCache();
34
+ gridPowerCache = new appliance_calibration_1.LatestValueCache();
35
+ meterListenerId;
36
+ constructor(sunspecClient, unitId, dataBus) {
37
+ super();
38
+ this.sunspecClient = sunspecClient;
39
+ this.unitId = unitId;
40
+ this.dataBus = dataBus;
41
+ this.subscribeMeterUpdates();
42
+ }
43
+ subscribeMeterUpdates() {
44
+ this.meterListenerId = this.dataBus.listenForMessages([enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.MeterValuesUpdateV1], (entry) => {
45
+ if (entry.message !== enyo_data_bus_value_js_1.EnyoDataBusMessageEnum.MeterValuesUpdateV1) {
46
+ return;
47
+ }
48
+ const meter = entry;
49
+ if (meter.data.gridPowerW !== undefined) {
50
+ this.gridPowerCache.set(meter.data.gridPowerW);
51
+ }
52
+ });
53
+ }
54
+ /**
55
+ * Tear down the meter subscription. Idempotent — call from
56
+ * `SunspecBattery.disconnect()`.
57
+ */
58
+ stop() {
59
+ if (this.meterListenerId) {
60
+ this.dataBus.unsubscribe(this.meterListenerId);
61
+ this.meterListenerId = undefined;
62
+ }
63
+ }
64
+ /**
65
+ * Push the latest computed battery power (W, positive = charging into the
66
+ * battery) from the owning device. Called from the readData loop.
67
+ */
68
+ updateBatteryPowerCache(powerW) {
69
+ this.batteryPowerCache.set(powerW);
70
+ }
71
+ async captureSnapshot() {
72
+ const controls = await this.sunspecClient.readBatteryControls(this.unitId);
73
+ if (!controls) {
74
+ throw new Error(`SunspecBatteryCalibrationDriver: readBatteryControls returned null for unit ${this.unitId}`);
75
+ }
76
+ return controls;
77
+ }
78
+ /**
79
+ * Write only the fields present in the snapshot AND in the writable whitelist.
80
+ * Per the library's contract this is best-effort: log on failure rather than
81
+ * throw, because the persisted snapshot has already been removed by the time
82
+ * we run and any throw would silently swallow what just happened.
83
+ */
84
+ async restoreFromSnapshot(snapshot, reason) {
85
+ const partial = {};
86
+ for (const field of WRITABLE_BATTERY_FIELDS) {
87
+ const value = snapshot[field];
88
+ if (value !== undefined) {
89
+ partial[field] = value;
90
+ }
91
+ }
92
+ if (Object.keys(partial).length === 0) {
93
+ console.log(`SunspecBatteryCalibrationDriver ${this.unitId}: restore (${reason}) — nothing to write`);
94
+ return;
95
+ }
96
+ try {
97
+ const ok = await this.sunspecClient.writeBatteryControls(this.unitId, partial);
98
+ if (!ok) {
99
+ console.error(`SunspecBatteryCalibrationDriver ${this.unitId}: restore (${reason}) writeBatteryControls returned false for [${Object.keys(partial).join(", ")}]`);
100
+ }
101
+ }
102
+ catch (error) {
103
+ console.error(`SunspecBatteryCalibrationDriver ${this.unitId}: restore (${reason}) threw: ${error}`);
104
+ }
105
+ }
106
+ /**
107
+ * SunSpec model 124 has no explicit calibration-mode register — the
108
+ * snapshot / restore + storCtlMod sequence *is* the calibration protocol.
109
+ * Resolve immediately so the orchestrator advances to the test charge.
110
+ */
111
+ async enterCalibrationMode() {
112
+ return "accepted";
113
+ }
114
+ async exitCalibrationMode() {
115
+ return "accepted";
116
+ }
117
+ /**
118
+ * Drive the same 3-step sequence proven by `handleStartGridCharge` in
119
+ * {@link SunspecBattery}: charge cap, grid-charging enable, storage mode.
120
+ * Throws on any step failure so the calibrator records a `failed` result.
121
+ */
122
+ async startTestCharge(powerW) {
123
+ const enabledGrid = await this.sunspecClient.enableGridCharging(this.unitId, true);
124
+ if (!enabledGrid) {
125
+ throw new Error("startTestCharge: failed to enable grid charging");
126
+ }
127
+ const wroteChaMax = await this.sunspecClient.writeBatteryControls(this.unitId, { wChaMax: powerW });
128
+ if (!wroteChaMax) {
129
+ throw new Error("startTestCharge: failed to set wChaMax");
130
+ }
131
+ const setMode = await this.sunspecClient.setStorageMode(this.unitId, sunspec_interfaces_js_1.SunspecStorageMode.CHARGE);
132
+ if (!setMode) {
133
+ throw new Error("startTestCharge: failed to set storage mode CHARGE");
134
+ }
135
+ }
136
+ /**
137
+ * Hand the battery back to its normal AUTO / PV state. The snapshot
138
+ * service's restore takes care of returning wChaMax / inWRte / outWRte
139
+ * to baseline.
140
+ */
141
+ async stopTestCharge() {
142
+ const setMode = await this.sunspecClient.setStorageMode(this.unitId, sunspec_interfaces_js_1.SunspecStorageMode.AUTO);
143
+ if (!setMode) {
144
+ throw new Error("stopTestCharge: failed to set storage mode AUTO");
145
+ }
146
+ const disabledGrid = await this.sunspecClient.enableGridCharging(this.unitId, false);
147
+ if (!disabledGrid) {
148
+ throw new Error("stopTestCharge: failed to disable grid charging");
149
+ }
150
+ }
151
+ readBatteryPowerW() {
152
+ return this.batteryPowerCache.get();
153
+ }
154
+ readGridPowerW() {
155
+ return this.gridPowerCache.get();
156
+ }
157
+ }
158
+ exports.SunspecBatteryCalibrationDriver = SunspecBatteryCalibrationDriver;
@@ -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.cjs";
4
+ import { type SunspecBatteryControls } from "./sunspec-interfaces.cjs";
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
+ }