@enyo-energy/sunspec-sdk 0.0.75 → 0.0.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/sunspec-battery-schedule-handler.cjs +16 -0
- package/dist/cjs/sunspec-devices.cjs +32 -11
- package/dist/cjs/sunspec-devices.d.cts +18 -0
- package/dist/cjs/sunspec-modbus-client.cjs +11 -10
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/sunspec-battery-schedule-handler.js +16 -0
- package/dist/sunspec-devices.d.ts +18 -0
- package/dist/sunspec-devices.js +31 -11
- package/dist/sunspec-modbus-client.js +11 -10
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -133,6 +133,22 @@ class SunspecBatteryScheduleHandler extends storage_schedule_handler_js_1.Storag
|
|
|
133
133
|
}
|
|
134
134
|
async applyEntry(active) {
|
|
135
135
|
const { direction, powerW } = active.entry;
|
|
136
|
+
if (direction === enyo_data_bus_value_js_1.EnyoStorageScheduleDirectionEnum.Idle) {
|
|
137
|
+
// Idle entry: hand the battery back to its own autonomous logic
|
|
138
|
+
// by restoring the snapshotted pre-schedule registers (same write
|
|
139
|
+
// set as the `mode: auto` rollback path). The schedule itself
|
|
140
|
+
// keeps running, so a subsequent Charge / Discharge entry can
|
|
141
|
+
// resume external control.
|
|
142
|
+
const baselineRegisters = this.originalBaseline;
|
|
143
|
+
if (!baselineRegisters) {
|
|
144
|
+
throw new Error(`no usable baseline for idle entry (originalBaseline=undefined)`);
|
|
145
|
+
}
|
|
146
|
+
await this.sunspecClient.writeBatteryControls(this.unitId, baselineRegisters);
|
|
147
|
+
await this.getSnapshotService()?.recordModification([
|
|
148
|
+
'storCtlMod', 'chaGriSet', 'wChaMax', 'inWRte', 'outWRte',
|
|
149
|
+
]);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
136
152
|
const baseline = this.installedWChaMax;
|
|
137
153
|
if (!baseline || baseline <= 0) {
|
|
138
154
|
throw new Error(`no usable wChaMax baseline (installedWChaMax=${baseline})`);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SunspecMeter = exports.SunspecBattery = exports.SunspecInverter = exports.BaseSunspecDevice = void 0;
|
|
4
4
|
exports.detectFeaturesFromRegisters = detectFeaturesFromRegisters;
|
|
5
|
+
exports.selectLiveBatteryPowerW = selectLiveBatteryPowerW;
|
|
5
6
|
exports.filterFeaturesByCalibrationResult = filterFeaturesByCalibrationResult;
|
|
6
7
|
const sunspec_interfaces_js_1 = require("./sunspec-interfaces.cjs");
|
|
7
8
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -852,6 +853,35 @@ function detectFeaturesFromRegisters(batteryData) {
|
|
|
852
853
|
* the full detected set to preserve the old all-or-nothing semantics on
|
|
853
854
|
* upgrade.
|
|
854
855
|
*/
|
|
856
|
+
/**
|
|
857
|
+
* Pick the live battery power, in preference order:
|
|
858
|
+
*
|
|
859
|
+
* 1. Model 160 (MPPT) `StCha 3` / `StDisCha 4` — direct DC-link reading,
|
|
860
|
+
* passed in as the already-summed `mpptBatteryPowerW`.
|
|
861
|
+
* 2. Model 802 `w` (offset 47) — surfaced as `chargePower` / `dischargePower`
|
|
862
|
+
* by the Model 802 path of `SunspecModbusClient.readBatteryData`.
|
|
863
|
+
* 3. `undefined` — no reliable live source on this device.
|
|
864
|
+
*
|
|
865
|
+
* The Model 124 path of `readBatteryData` no longer manufactures
|
|
866
|
+
* `chargePower` / `dischargePower` from `inWRte × wChaMax` (a commanded rate
|
|
867
|
+
* cap, not a live measurement). So whenever this function sees
|
|
868
|
+
* `chargePower` / `dischargePower` defined, they came from Model 802.
|
|
869
|
+
*
|
|
870
|
+
* Exported as a free function so the preference logic can be unit-tested
|
|
871
|
+
* without the full `SunspecBattery` scaffold.
|
|
872
|
+
*/
|
|
873
|
+
function selectLiveBatteryPowerW(mpptBatteryPowerW, batteryData) {
|
|
874
|
+
if (mpptBatteryPowerW !== undefined) {
|
|
875
|
+
return mpptBatteryPowerW;
|
|
876
|
+
}
|
|
877
|
+
if (!batteryData) {
|
|
878
|
+
return undefined;
|
|
879
|
+
}
|
|
880
|
+
if (batteryData.chargePower === undefined && batteryData.dischargePower === undefined) {
|
|
881
|
+
return undefined;
|
|
882
|
+
}
|
|
883
|
+
return (batteryData.chargePower ?? 0) - (batteryData.dischargePower ?? 0);
|
|
884
|
+
}
|
|
855
885
|
function filterFeaturesByCalibrationResult(detected, result, controllable) {
|
|
856
886
|
if (!result || result.state !== 'calibrated') {
|
|
857
887
|
return detected.filter(f => !controllable.includes(f));
|
|
@@ -1115,17 +1145,8 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
1115
1145
|
return messages;
|
|
1116
1146
|
}
|
|
1117
1147
|
if (batteryData) {
|
|
1118
|
-
|
|
1119
|
-
const
|
|
1120
|
-
// Determine battery power: prefer model 802 w field, then MPPT extraction, then undefined
|
|
1121
|
-
let batteryPowerW;
|
|
1122
|
-
if (batteryBaseModel && (batteryData.chargePower !== undefined || batteryData.dischargePower !== undefined)) {
|
|
1123
|
-
// Model 802 provides power directly: positive = charge, negative = discharge
|
|
1124
|
-
batteryPowerW = (batteryData.chargePower || 0) - (batteryData.dischargePower || 0);
|
|
1125
|
-
}
|
|
1126
|
-
else if (!advancedBatteryModel) {
|
|
1127
|
-
batteryPowerW = mpptBatteryPowerW;
|
|
1128
|
-
}
|
|
1148
|
+
// See `selectLiveBatteryPowerW` for the source-preference rationale.
|
|
1149
|
+
const batteryPowerW = selectLiveBatteryPowerW(mpptBatteryPowerW, batteryData);
|
|
1129
1150
|
// Feed the calibration driver's synchronous power cache. No-op when calibration
|
|
1130
1151
|
// was never configured.
|
|
1131
1152
|
if (batteryPowerW !== undefined && this.calibrationDriver) {
|
|
@@ -215,6 +215,24 @@ export declare function detectFeaturesFromRegisters(batteryData: SunspecBatteryD
|
|
|
215
215
|
* the full detected set to preserve the old all-or-nothing semantics on
|
|
216
216
|
* upgrade.
|
|
217
217
|
*/
|
|
218
|
+
/**
|
|
219
|
+
* Pick the live battery power, in preference order:
|
|
220
|
+
*
|
|
221
|
+
* 1. Model 160 (MPPT) `StCha 3` / `StDisCha 4` — direct DC-link reading,
|
|
222
|
+
* passed in as the already-summed `mpptBatteryPowerW`.
|
|
223
|
+
* 2. Model 802 `w` (offset 47) — surfaced as `chargePower` / `dischargePower`
|
|
224
|
+
* by the Model 802 path of `SunspecModbusClient.readBatteryData`.
|
|
225
|
+
* 3. `undefined` — no reliable live source on this device.
|
|
226
|
+
*
|
|
227
|
+
* The Model 124 path of `readBatteryData` no longer manufactures
|
|
228
|
+
* `chargePower` / `dischargePower` from `inWRte × wChaMax` (a commanded rate
|
|
229
|
+
* cap, not a live measurement). So whenever this function sees
|
|
230
|
+
* `chargePower` / `dischargePower` defined, they came from Model 802.
|
|
231
|
+
*
|
|
232
|
+
* Exported as a free function so the preference logic can be unit-tested
|
|
233
|
+
* without the full `SunspecBattery` scaffold.
|
|
234
|
+
*/
|
|
235
|
+
export declare function selectLiveBatteryPowerW(mpptBatteryPowerW: number | undefined, batteryData: SunspecBatteryData | null): number | undefined;
|
|
218
236
|
export declare function filterFeaturesByCalibrationResult(detected: EnyoBatteryFeature[], result: CalibrationResult | undefined, controllable: readonly EnyoBatteryFeature[]): EnyoBatteryFeature[];
|
|
219
237
|
/**
|
|
220
238
|
* Sunspec Battery implementation
|
|
@@ -1596,16 +1596,17 @@ class SunspecModbusClient {
|
|
|
1596
1596
|
voltage: this.applyScaleFactor(inBatVRaw, scaleFactors.InBatV_SF, 'uint16'),
|
|
1597
1597
|
status: !this.isUnimplementedValue(chaStRaw, 'enum16') ? chaStRaw : undefined
|
|
1598
1598
|
};
|
|
1599
|
-
//
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1599
|
+
// chargePower / dischargePower used to be derived from
|
|
1600
|
+
// `(inWRte / 100) * wChaMax` and `(outWRte / 100) * wChaMax`
|
|
1601
|
+
// here. That formula uses Model 124's COMMANDED RATE CAPS — not
|
|
1602
|
+
// a live measurement — and produces e.g. 5000 W of "charging"
|
|
1603
|
+
// for an idle battery with inWRte = 100 % and a 5 kW nameplate.
|
|
1604
|
+
// Live battery power must come from a real source: Model 160
|
|
1605
|
+
// StCha 3 / StDisCha 4 (see SunspecBattery.extractBatteryPower
|
|
1606
|
+
// FromMPPT) or Model 802 `w` at offset 47 (the Model 802 branch
|
|
1607
|
+
// of this method, just below). Model-124-only devices should
|
|
1608
|
+
// publish "no signal" rather than the commanded cap.
|
|
1609
|
+
// See plans/please-review-the-implementation-groovy-thacker.md.
|
|
1609
1610
|
// Single-line JSON debug dump — Node's default formatter would split
|
|
1610
1611
|
// the object across many lines; stringify keeps the whole snapshot
|
|
1611
1612
|
// on one log entry so it stays grep-able and round-trippable.
|
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.77';
|
|
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
|
@@ -130,6 +130,22 @@ export class SunspecBatteryScheduleHandler extends StorageScheduleHandler {
|
|
|
130
130
|
}
|
|
131
131
|
async applyEntry(active) {
|
|
132
132
|
const { direction, powerW } = active.entry;
|
|
133
|
+
if (direction === EnyoStorageScheduleDirectionEnum.Idle) {
|
|
134
|
+
// Idle entry: hand the battery back to its own autonomous logic
|
|
135
|
+
// by restoring the snapshotted pre-schedule registers (same write
|
|
136
|
+
// set as the `mode: auto` rollback path). The schedule itself
|
|
137
|
+
// keeps running, so a subsequent Charge / Discharge entry can
|
|
138
|
+
// resume external control.
|
|
139
|
+
const baselineRegisters = this.originalBaseline;
|
|
140
|
+
if (!baselineRegisters) {
|
|
141
|
+
throw new Error(`no usable baseline for idle entry (originalBaseline=undefined)`);
|
|
142
|
+
}
|
|
143
|
+
await this.sunspecClient.writeBatteryControls(this.unitId, baselineRegisters);
|
|
144
|
+
await this.getSnapshotService()?.recordModification([
|
|
145
|
+
'storCtlMod', 'chaGriSet', 'wChaMax', 'inWRte', 'outWRte',
|
|
146
|
+
]);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
133
149
|
const baseline = this.installedWChaMax;
|
|
134
150
|
if (!baseline || baseline <= 0) {
|
|
135
151
|
throw new Error(`no usable wChaMax baseline (installedWChaMax=${baseline})`);
|
|
@@ -215,6 +215,24 @@ export declare function detectFeaturesFromRegisters(batteryData: SunspecBatteryD
|
|
|
215
215
|
* the full detected set to preserve the old all-or-nothing semantics on
|
|
216
216
|
* upgrade.
|
|
217
217
|
*/
|
|
218
|
+
/**
|
|
219
|
+
* Pick the live battery power, in preference order:
|
|
220
|
+
*
|
|
221
|
+
* 1. Model 160 (MPPT) `StCha 3` / `StDisCha 4` — direct DC-link reading,
|
|
222
|
+
* passed in as the already-summed `mpptBatteryPowerW`.
|
|
223
|
+
* 2. Model 802 `w` (offset 47) — surfaced as `chargePower` / `dischargePower`
|
|
224
|
+
* by the Model 802 path of `SunspecModbusClient.readBatteryData`.
|
|
225
|
+
* 3. `undefined` — no reliable live source on this device.
|
|
226
|
+
*
|
|
227
|
+
* The Model 124 path of `readBatteryData` no longer manufactures
|
|
228
|
+
* `chargePower` / `dischargePower` from `inWRte × wChaMax` (a commanded rate
|
|
229
|
+
* cap, not a live measurement). So whenever this function sees
|
|
230
|
+
* `chargePower` / `dischargePower` defined, they came from Model 802.
|
|
231
|
+
*
|
|
232
|
+
* Exported as a free function so the preference logic can be unit-tested
|
|
233
|
+
* without the full `SunspecBattery` scaffold.
|
|
234
|
+
*/
|
|
235
|
+
export declare function selectLiveBatteryPowerW(mpptBatteryPowerW: number | undefined, batteryData: SunspecBatteryData | null): number | undefined;
|
|
218
236
|
export declare function filterFeaturesByCalibrationResult(detected: EnyoBatteryFeature[], result: CalibrationResult | undefined, controllable: readonly EnyoBatteryFeature[]): EnyoBatteryFeature[];
|
|
219
237
|
/**
|
|
220
238
|
* Sunspec Battery implementation
|
package/dist/sunspec-devices.js
CHANGED
|
@@ -845,6 +845,35 @@ export function detectFeaturesFromRegisters(batteryData) {
|
|
|
845
845
|
* the full detected set to preserve the old all-or-nothing semantics on
|
|
846
846
|
* upgrade.
|
|
847
847
|
*/
|
|
848
|
+
/**
|
|
849
|
+
* Pick the live battery power, in preference order:
|
|
850
|
+
*
|
|
851
|
+
* 1. Model 160 (MPPT) `StCha 3` / `StDisCha 4` — direct DC-link reading,
|
|
852
|
+
* passed in as the already-summed `mpptBatteryPowerW`.
|
|
853
|
+
* 2. Model 802 `w` (offset 47) — surfaced as `chargePower` / `dischargePower`
|
|
854
|
+
* by the Model 802 path of `SunspecModbusClient.readBatteryData`.
|
|
855
|
+
* 3. `undefined` — no reliable live source on this device.
|
|
856
|
+
*
|
|
857
|
+
* The Model 124 path of `readBatteryData` no longer manufactures
|
|
858
|
+
* `chargePower` / `dischargePower` from `inWRte × wChaMax` (a commanded rate
|
|
859
|
+
* cap, not a live measurement). So whenever this function sees
|
|
860
|
+
* `chargePower` / `dischargePower` defined, they came from Model 802.
|
|
861
|
+
*
|
|
862
|
+
* Exported as a free function so the preference logic can be unit-tested
|
|
863
|
+
* without the full `SunspecBattery` scaffold.
|
|
864
|
+
*/
|
|
865
|
+
export function selectLiveBatteryPowerW(mpptBatteryPowerW, batteryData) {
|
|
866
|
+
if (mpptBatteryPowerW !== undefined) {
|
|
867
|
+
return mpptBatteryPowerW;
|
|
868
|
+
}
|
|
869
|
+
if (!batteryData) {
|
|
870
|
+
return undefined;
|
|
871
|
+
}
|
|
872
|
+
if (batteryData.chargePower === undefined && batteryData.dischargePower === undefined) {
|
|
873
|
+
return undefined;
|
|
874
|
+
}
|
|
875
|
+
return (batteryData.chargePower ?? 0) - (batteryData.dischargePower ?? 0);
|
|
876
|
+
}
|
|
848
877
|
export function filterFeaturesByCalibrationResult(detected, result, controllable) {
|
|
849
878
|
if (!result || result.state !== 'calibrated') {
|
|
850
879
|
return detected.filter(f => !controllable.includes(f));
|
|
@@ -1108,17 +1137,8 @@ export class SunspecBattery extends BaseSunspecDevice {
|
|
|
1108
1137
|
return messages;
|
|
1109
1138
|
}
|
|
1110
1139
|
if (batteryData) {
|
|
1111
|
-
|
|
1112
|
-
const
|
|
1113
|
-
// Determine battery power: prefer model 802 w field, then MPPT extraction, then undefined
|
|
1114
|
-
let batteryPowerW;
|
|
1115
|
-
if (batteryBaseModel && (batteryData.chargePower !== undefined || batteryData.dischargePower !== undefined)) {
|
|
1116
|
-
// Model 802 provides power directly: positive = charge, negative = discharge
|
|
1117
|
-
batteryPowerW = (batteryData.chargePower || 0) - (batteryData.dischargePower || 0);
|
|
1118
|
-
}
|
|
1119
|
-
else if (!advancedBatteryModel) {
|
|
1120
|
-
batteryPowerW = mpptBatteryPowerW;
|
|
1121
|
-
}
|
|
1140
|
+
// See `selectLiveBatteryPowerW` for the source-preference rationale.
|
|
1141
|
+
const batteryPowerW = selectLiveBatteryPowerW(mpptBatteryPowerW, batteryData);
|
|
1122
1142
|
// Feed the calibration driver's synchronous power cache. No-op when calibration
|
|
1123
1143
|
// was never configured.
|
|
1124
1144
|
if (batteryPowerW !== undefined && this.calibrationDriver) {
|
|
@@ -1591,16 +1591,17 @@ export class SunspecModbusClient {
|
|
|
1591
1591
|
voltage: this.applyScaleFactor(inBatVRaw, scaleFactors.InBatV_SF, 'uint16'),
|
|
1592
1592
|
status: !this.isUnimplementedValue(chaStRaw, 'enum16') ? chaStRaw : undefined
|
|
1593
1593
|
};
|
|
1594
|
-
//
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1594
|
+
// chargePower / dischargePower used to be derived from
|
|
1595
|
+
// `(inWRte / 100) * wChaMax` and `(outWRte / 100) * wChaMax`
|
|
1596
|
+
// here. That formula uses Model 124's COMMANDED RATE CAPS — not
|
|
1597
|
+
// a live measurement — and produces e.g. 5000 W of "charging"
|
|
1598
|
+
// for an idle battery with inWRte = 100 % and a 5 kW nameplate.
|
|
1599
|
+
// Live battery power must come from a real source: Model 160
|
|
1600
|
+
// StCha 3 / StDisCha 4 (see SunspecBattery.extractBatteryPower
|
|
1601
|
+
// FromMPPT) or Model 802 `w` at offset 47 (the Model 802 branch
|
|
1602
|
+
// of this method, just below). Model-124-only devices should
|
|
1603
|
+
// publish "no signal" rather than the commanded cap.
|
|
1604
|
+
// See plans/please-review-the-implementation-groovy-thacker.md.
|
|
1604
1605
|
// Single-line JSON debug dump — Node's default formatter would split
|
|
1605
1606
|
// the object across many lines; stringify keeps the whole snapshot
|
|
1606
1607
|
// on one log entry so it stays grep-able and round-trippable.
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enyo-energy/sunspec-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.77",
|
|
4
4
|
"description": "enyo Energy Sunspec SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@enyo-energy/appliance-calibration": "0.0.2",
|
|
44
|
-
"@enyo-energy/energy-app-sdk": "0.0.
|
|
44
|
+
"@enyo-energy/energy-app-sdk": "^0.0.149"
|
|
45
45
|
},
|
|
46
46
|
"volta": {
|
|
47
47
|
"node": "22.17.0"
|