@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.
- package/README.md +302 -0
- package/dist/calibration-snapshot-service.d.ts +67 -0
- package/dist/calibration-snapshot-service.js +160 -0
- package/dist/cjs/calibration-snapshot-service.cjs +164 -0
- package/dist/cjs/calibration-snapshot-service.d.cts +67 -0
- package/dist/cjs/index.cjs +30 -1
- package/dist/cjs/index.d.cts +6 -0
- 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 +407 -104
- package/dist/cjs/sunspec-devices.d.cts +112 -6
- 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 -0
- package/dist/index.js +12 -0
- 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 +112 -6
- package/dist/sunspec-devices.js +408 -105
- 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
|
@@ -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
|
+
}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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; } });
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -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
|
+
}
|