@enyo-energy/sunspec-sdk 0.0.51 → 0.0.54
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-devices.cjs +45 -42
- package/dist/cjs/sunspec-interfaces.cjs +3 -0
- package/dist/cjs/sunspec-interfaces.d.cts +7 -2
- package/dist/cjs/sunspec-modbus-client.cjs +490 -277
- package/dist/cjs/sunspec-modbus-client.d.cts +115 -53
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/sunspec-devices.js +45 -42
- package/dist/sunspec-interfaces.d.ts +7 -2
- package/dist/sunspec-interfaces.js +3 -0
- package/dist/sunspec-modbus-client.d.ts +115 -53
- package/dist/sunspec-modbus-client.js +488 -277
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -116,7 +116,7 @@ class BaseSunspecDevice {
|
|
|
116
116
|
* Check if the device is connected
|
|
117
117
|
*/
|
|
118
118
|
isConnected() {
|
|
119
|
-
return this.sunspecClient.isHealthy();
|
|
119
|
+
return this.sunspecClient.isHealthy(this.unitId);
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
122
|
* Get the appliance IDs managed by this device
|
|
@@ -125,11 +125,11 @@ class BaseSunspecDevice {
|
|
|
125
125
|
* Ensure the Sunspec client is connected and models are discovered
|
|
126
126
|
*/
|
|
127
127
|
async ensureConnected() {
|
|
128
|
-
if (!this.sunspecClient.isConnected()) {
|
|
128
|
+
if (!this.sunspecClient.isConnected(this.unitId)) {
|
|
129
129
|
await this.sunspecClient.connect(this.networkDevice.hostname, // primary
|
|
130
130
|
this.port, this.unitId, this.networkDevice.ipAddress // secondary fallback
|
|
131
131
|
);
|
|
132
|
-
await this.sunspecClient.discoverModels(this.baseAddress);
|
|
132
|
+
await this.sunspecClient.discoverModels(this.unitId, this.baseAddress);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
/**
|
|
@@ -149,12 +149,15 @@ class BaseSunspecDevice {
|
|
|
149
149
|
const stats = this.sunspecClient.getConnectionStats();
|
|
150
150
|
console.log(`${this.constructor.name} ${this.applianceId}: Reconnect attempt #${attempt} ` +
|
|
151
151
|
`(phase: ${phase.intervalMs / 1000}s interval, elapsed: ${elapsed}s, ` +
|
|
152
|
-
`opens=${stats.opens}, closes=${stats.closes},
|
|
152
|
+
`opens=${stats.opens}, closes=${stats.closes}, openUnits=${stats.openUnits})`);
|
|
153
153
|
try {
|
|
154
|
-
|
|
154
|
+
// Reconnect just this device's unit, not every unit on the shared client.
|
|
155
|
+
// This avoids thrashing sibling devices on the same network device when one
|
|
156
|
+
// device's poll loop detects a dropped connection.
|
|
157
|
+
const success = await this.sunspecClient.reconnectUnit(this.unitId);
|
|
155
158
|
if (success) {
|
|
156
|
-
// Re-discover models after reconnect
|
|
157
|
-
await this.sunspecClient.discoverModels(this.baseAddress);
|
|
159
|
+
// Re-discover models for this unit after reconnect
|
|
160
|
+
await this.sunspecClient.discoverModels(this.unitId, this.baseAddress);
|
|
158
161
|
this.retryManager.reset();
|
|
159
162
|
this.consecutiveReconnectFailures = 0;
|
|
160
163
|
// Update appliance state to Connected
|
|
@@ -162,7 +165,7 @@ class BaseSunspecDevice {
|
|
|
162
165
|
await this.applianceManager.updateApplianceState(this.applianceId, enyo_appliance_js_1.EnyoApplianceConnectionType.Connector, enyo_appliance_js_1.EnyoApplianceStateEnum.Connected);
|
|
163
166
|
}
|
|
164
167
|
const postStats = this.sunspecClient.getConnectionStats();
|
|
165
|
-
console.log(`${this.constructor.name} ${this.applianceId}: Reconnection successful after ${attempt} attempt(s) (opens=${postStats.opens}, closes=${postStats.closes},
|
|
168
|
+
console.log(`${this.constructor.name} ${this.applianceId}: Reconnection successful after ${attempt} attempt(s) (opens=${postStats.opens}, closes=${postStats.closes}, openUnits=${postStats.openUnits})`);
|
|
166
169
|
return true;
|
|
167
170
|
}
|
|
168
171
|
this.consecutiveReconnectFailures += 1;
|
|
@@ -190,15 +193,15 @@ class BaseSunspecDevice {
|
|
|
190
193
|
async markOffline() {
|
|
191
194
|
this.retryManager.markDisconnected();
|
|
192
195
|
try {
|
|
193
|
-
if (this.sunspecClient.isConnected()) {
|
|
194
|
-
await this.sunspecClient.
|
|
196
|
+
if (this.sunspecClient.isConnected(this.unitId)) {
|
|
197
|
+
await this.sunspecClient.disconnectUnit(this.unitId);
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
200
|
catch (error) {
|
|
198
201
|
console.error(`${this.constructor.name} ${this.applianceId}: error closing socket on markOffline: ${error}`);
|
|
199
202
|
}
|
|
200
203
|
const stats = this.sunspecClient.getConnectionStats();
|
|
201
|
-
console.log(`${this.constructor.name} ${this.applianceId}: marked offline (opens=${stats.opens}, closes=${stats.closes},
|
|
204
|
+
console.log(`${this.constructor.name} ${this.applianceId}: marked offline (opens=${stats.opens}, closes=${stats.closes}, openUnits=${stats.openUnits})`);
|
|
202
205
|
if (this.applianceId) {
|
|
203
206
|
try {
|
|
204
207
|
await this.applianceManager.updateApplianceState(this.applianceId, enyo_appliance_js_1.EnyoApplianceConnectionType.Connector, enyo_appliance_js_1.EnyoApplianceStateEnum.Offline);
|
|
@@ -246,16 +249,16 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
246
249
|
}
|
|
247
250
|
async connect() {
|
|
248
251
|
// Ensure Sunspec client is connected
|
|
249
|
-
if (!this.sunspecClient.isConnected()) {
|
|
252
|
+
if (!this.sunspecClient.isConnected(this.unitId)) {
|
|
250
253
|
await this.sunspecClient.connect(this.networkDevice.hostname, // primary
|
|
251
254
|
this.port, this.unitId, this.networkDevice.ipAddress // secondary fallback
|
|
252
255
|
);
|
|
253
|
-
await this.sunspecClient.discoverModels(this.baseAddress);
|
|
256
|
+
await this.sunspecClient.discoverModels(this.unitId, this.baseAddress);
|
|
254
257
|
}
|
|
255
258
|
// Get device info from common block
|
|
256
|
-
const commonData = await this.sunspecClient.readCommonBlock();
|
|
257
|
-
const inverterSettings = await this.sunspecClient.readInverterSettings();
|
|
258
|
-
const mpptDataList = await this.sunspecClient.readAllMPPTData();
|
|
259
|
+
const commonData = await this.sunspecClient.readCommonBlock(this.unitId);
|
|
260
|
+
const inverterSettings = await this.sunspecClient.readInverterSettings(this.unitId);
|
|
261
|
+
const mpptDataList = await this.sunspecClient.readAllMPPTData(this.unitId);
|
|
259
262
|
// Create or update appliance (skip if an existing appliance was provided)
|
|
260
263
|
if (!this.applianceId) {
|
|
261
264
|
try {
|
|
@@ -305,7 +308,7 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
305
308
|
}
|
|
306
309
|
}
|
|
307
310
|
// Check for MPPT models
|
|
308
|
-
const mpptModel = this.sunspecClient.findModel(sunspec_interfaces_js_1.SunspecModelId.MPPT);
|
|
311
|
+
const mpptModel = this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.MPPT);
|
|
309
312
|
if (mpptModel) {
|
|
310
313
|
console.log(`MPPT model found for inverter ${this.networkDevice.hostname}`);
|
|
311
314
|
}
|
|
@@ -329,9 +332,9 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
329
332
|
const timestamp = new Date();
|
|
330
333
|
try {
|
|
331
334
|
// Read inverter data
|
|
332
|
-
const inverterData = await this.sunspecClient.readInverterData();
|
|
333
|
-
const mpptDataList = await this.sunspecClient.readAllMPPTData();
|
|
334
|
-
const inverterSettings = await this.sunspecClient.readInverterSettings();
|
|
335
|
+
const inverterData = await this.sunspecClient.readInverterData(this.unitId);
|
|
336
|
+
const mpptDataList = await this.sunspecClient.readAllMPPTData(this.unitId);
|
|
337
|
+
const inverterSettings = await this.sunspecClient.readInverterSettings(this.unitId);
|
|
335
338
|
const dcStrings = this.mapMPPTToStrings(mpptDataList);
|
|
336
339
|
if (inverterData) {
|
|
337
340
|
const pvPowerW = dcStrings.reduce((sum, s) => sum + (s.powerW || 0), 0);
|
|
@@ -657,7 +660,7 @@ class SunspecInverter extends BaseSunspecDevice {
|
|
|
657
660
|
return;
|
|
658
661
|
}
|
|
659
662
|
console.log(`Inverter ${this.applianceId}: handling SetInverterFeedInLimitV1 (feedInLimitW=${msg.data.feedInLimitW})`);
|
|
660
|
-
const success = await this.sunspecClient.setFeedInLimit(msg.data.feedInLimitW);
|
|
663
|
+
const success = await this.sunspecClient.setFeedInLimit(this.unitId, msg.data.feedInLimitW);
|
|
661
664
|
if (!success) {
|
|
662
665
|
this.sendCommandAcknowledge(msg.id, msg.message, enyo_data_bus_value_js_1.EnyoCommandAcknowledgeAnswerEnum.Rejected, 'Failed to set feed-in limit');
|
|
663
666
|
return;
|
|
@@ -682,14 +685,14 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
682
685
|
// Ensure Sunspec client is connected
|
|
683
686
|
await this.ensureConnected();
|
|
684
687
|
// Check if battery models exist
|
|
685
|
-
const hasBattery = this.sunspecClient.findModel(sunspec_interfaces_js_1.SunspecModelId.Battery) !== undefined ||
|
|
686
|
-
this.sunspecClient.findModel(sunspec_interfaces_js_1.SunspecModelId.BatteryBase) !== undefined;
|
|
688
|
+
const hasBattery = this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.Battery) !== undefined ||
|
|
689
|
+
this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.BatteryBase) !== undefined;
|
|
687
690
|
if (!hasBattery) {
|
|
688
691
|
throw new Error('No battery model found in device');
|
|
689
692
|
}
|
|
690
693
|
// Get device info
|
|
691
|
-
const commonData = await this.sunspecClient.readCommonBlock();
|
|
692
|
-
const batteryData = await this.sunspecClient.readBatteryData();
|
|
694
|
+
const commonData = await this.sunspecClient.readCommonBlock(this.unitId);
|
|
695
|
+
const batteryData = await this.sunspecClient.readBatteryData(this.unitId);
|
|
693
696
|
const storageMode = this.determineStorageMode(batteryData);
|
|
694
697
|
const features = [];
|
|
695
698
|
if (batteryData?.chaGriSet !== undefined) {
|
|
@@ -798,12 +801,12 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
798
801
|
const timestamp = new Date();
|
|
799
802
|
try {
|
|
800
803
|
// Read actual battery data from SunSpec device
|
|
801
|
-
const batteryData = await this.sunspecClient.readBatteryData();
|
|
802
|
-
const mpptDataList = await this.sunspecClient.readAllMPPTData();
|
|
804
|
+
const batteryData = await this.sunspecClient.readBatteryData(this.unitId);
|
|
805
|
+
const mpptDataList = await this.sunspecClient.readAllMPPTData(this.unitId);
|
|
803
806
|
const mpptBatteryPowerW = this.extractBatteryPowerFromMPPT(mpptDataList);
|
|
804
807
|
if (batteryData) {
|
|
805
|
-
const advancedBatteryModel = this.sunspecClient.findModel(801);
|
|
806
|
-
const batteryBaseModel = this.sunspecClient.findModel(sunspec_interfaces_js_1.SunspecModelId.BatteryBase);
|
|
808
|
+
const advancedBatteryModel = this.sunspecClient.findModel(this.unitId, 801);
|
|
809
|
+
const batteryBaseModel = this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.BatteryBase);
|
|
807
810
|
// Determine battery power: prefer model 802 w field, then MPPT extraction, then undefined
|
|
808
811
|
let batteryPowerW;
|
|
809
812
|
if (batteryBaseModel && (batteryData.chargePower !== undefined || batteryData.dischargePower !== undefined)) {
|
|
@@ -913,7 +916,7 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
913
916
|
return false;
|
|
914
917
|
}
|
|
915
918
|
console.log(`Setting battery storage mode to: ${mode}`);
|
|
916
|
-
return this.sunspecClient.setStorageMode(mode);
|
|
919
|
+
return this.sunspecClient.setStorageMode(this.unitId, mode);
|
|
917
920
|
}
|
|
918
921
|
/**
|
|
919
922
|
* Enable or disable grid charging
|
|
@@ -933,7 +936,7 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
933
936
|
return false;
|
|
934
937
|
}
|
|
935
938
|
console.log(`${enable ? 'Enabling' : 'Disabling'} grid charging for battery`);
|
|
936
|
-
return this.sunspecClient.enableGridCharging(enable);
|
|
939
|
+
return this.sunspecClient.enableGridCharging(this.unitId, enable);
|
|
937
940
|
}
|
|
938
941
|
/**
|
|
939
942
|
* Set battery charging power from grid
|
|
@@ -951,7 +954,7 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
951
954
|
return false;
|
|
952
955
|
}
|
|
953
956
|
console.log(`Setting battery charging power to: ${powerW}W`);
|
|
954
|
-
return this.sunspecClient.writeBatteryControls({ wChaMax: powerW });
|
|
957
|
+
return this.sunspecClient.writeBatteryControls(this.unitId, { wChaMax: powerW });
|
|
955
958
|
}
|
|
956
959
|
/**
|
|
957
960
|
* Get current battery control settings
|
|
@@ -963,7 +966,7 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
963
966
|
console.error('Battery not connected');
|
|
964
967
|
return null;
|
|
965
968
|
}
|
|
966
|
-
return this.sunspecClient.readBatteryControls();
|
|
969
|
+
return this.sunspecClient.readBatteryControls(this.unitId);
|
|
967
970
|
}
|
|
968
971
|
/**
|
|
969
972
|
* Read full battery base data from Model 802
|
|
@@ -978,7 +981,7 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
978
981
|
console.error('Battery not connected');
|
|
979
982
|
return null;
|
|
980
983
|
}
|
|
981
|
-
return this.sunspecClient.readBatteryBaseData();
|
|
984
|
+
return this.sunspecClient.readBatteryBaseData(this.unitId);
|
|
982
985
|
}
|
|
983
986
|
/**
|
|
984
987
|
* Write custom battery control settings
|
|
@@ -995,7 +998,7 @@ class SunspecBattery extends BaseSunspecDevice {
|
|
|
995
998
|
return false;
|
|
996
999
|
}
|
|
997
1000
|
console.log('Writing battery controls:', controls);
|
|
998
|
-
return this.sunspecClient.writeBatteryControls(controls);
|
|
1001
|
+
return this.sunspecClient.writeBatteryControls(this.unitId, controls);
|
|
999
1002
|
}
|
|
1000
1003
|
mapToEnyoStorageMode(storageMode) {
|
|
1001
1004
|
switch (storageMode) {
|
|
@@ -1204,14 +1207,14 @@ class SunspecMeter extends BaseSunspecDevice {
|
|
|
1204
1207
|
// Connect with specific unit ID for meter
|
|
1205
1208
|
await this.ensureConnected();
|
|
1206
1209
|
// Check if meter models exist
|
|
1207
|
-
const hasMeter = this.sunspecClient.findModel(sunspec_interfaces_js_1.SunspecModelId.Meter3Phase) !== undefined ||
|
|
1208
|
-
this.sunspecClient.findModel(sunspec_interfaces_js_1.SunspecModelId.MeterWye) !== undefined ||
|
|
1209
|
-
this.sunspecClient.findModel(sunspec_interfaces_js_1.SunspecModelId.MeterSinglePhase) !== undefined;
|
|
1210
|
+
const hasMeter = this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.Meter3Phase) !== undefined ||
|
|
1211
|
+
this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.MeterWye) !== undefined ||
|
|
1212
|
+
this.sunspecClient.findModel(this.unitId, sunspec_interfaces_js_1.SunspecModelId.MeterSinglePhase) !== undefined;
|
|
1210
1213
|
if (!hasMeter) {
|
|
1211
1214
|
throw new Error('No meter model found in device');
|
|
1212
1215
|
}
|
|
1213
1216
|
// Get device info
|
|
1214
|
-
const commonData = await this.sunspecClient.readCommonBlock();
|
|
1217
|
+
const commonData = await this.sunspecClient.readCommonBlock(this.unitId);
|
|
1215
1218
|
// Create or update appliance (skip if an existing appliance was provided)
|
|
1216
1219
|
if (!this.applianceId) {
|
|
1217
1220
|
try {
|
|
@@ -1265,8 +1268,8 @@ class SunspecMeter extends BaseSunspecDevice {
|
|
|
1265
1268
|
if (this.applianceId) {
|
|
1266
1269
|
await this.applianceManager.updateApplianceState(this.applianceId, enyo_appliance_js_1.EnyoApplianceConnectionType.Connector, enyo_appliance_js_1.EnyoApplianceStateEnum.Offline);
|
|
1267
1270
|
}
|
|
1268
|
-
//
|
|
1269
|
-
await this.sunspecClient.
|
|
1271
|
+
// Close just this meter's unit; other devices on the same network device stay open.
|
|
1272
|
+
await this.sunspecClient.disconnectUnit(this.unitId);
|
|
1270
1273
|
}
|
|
1271
1274
|
/**
|
|
1272
1275
|
* Update meter data and return data bus messages
|
|
@@ -1281,7 +1284,7 @@ class SunspecMeter extends BaseSunspecDevice {
|
|
|
1281
1284
|
const timestamp = new Date();
|
|
1282
1285
|
try {
|
|
1283
1286
|
// Read meter data
|
|
1284
|
-
const meterData = await this.sunspecClient.readMeterData();
|
|
1287
|
+
const meterData = await this.sunspecClient.readMeterData(this.unitId);
|
|
1285
1288
|
if (meterData) {
|
|
1286
1289
|
const meterMessage = {
|
|
1287
1290
|
id: (0, node_crypto_1.randomUUID)(),
|
|
@@ -20,6 +20,9 @@ var SunspecModelId;
|
|
|
20
20
|
SunspecModelId[SunspecModelId["Common"] = 1] = "Common";
|
|
21
21
|
SunspecModelId[SunspecModelId["Inverter3Phase"] = 103] = "Inverter3Phase";
|
|
22
22
|
SunspecModelId[SunspecModelId["InverterSinglePhase"] = 101] = "InverterSinglePhase";
|
|
23
|
+
SunspecModelId[SunspecModelId["InverterSinglePhaseFloat"] = 111] = "InverterSinglePhaseFloat";
|
|
24
|
+
SunspecModelId[SunspecModelId["InverterSplitPhaseFloat"] = 112] = "InverterSplitPhaseFloat";
|
|
25
|
+
SunspecModelId[SunspecModelId["Inverter3PhaseFloat"] = 113] = "Inverter3PhaseFloat";
|
|
23
26
|
SunspecModelId[SunspecModelId["MPPT"] = 160] = "MPPT";
|
|
24
27
|
SunspecModelId[SunspecModelId["Battery"] = 124] = "Battery";
|
|
25
28
|
SunspecModelId[SunspecModelId["BatteryBase"] = 802] = "BatteryBase";
|
|
@@ -30,6 +30,9 @@ export declare enum SunspecModelId {
|
|
|
30
30
|
Common = 1,
|
|
31
31
|
Inverter3Phase = 103,
|
|
32
32
|
InverterSinglePhase = 101,
|
|
33
|
+
InverterSinglePhaseFloat = 111,
|
|
34
|
+
InverterSplitPhaseFloat = 112,
|
|
35
|
+
Inverter3PhaseFloat = 113,
|
|
33
36
|
MPPT = 160,
|
|
34
37
|
Battery = 124,
|
|
35
38
|
BatteryBase = 802,
|
|
@@ -74,10 +77,12 @@ export interface SunspecCommonBlock extends SunspecBlock {
|
|
|
74
77
|
deviceAddress?: number;
|
|
75
78
|
}
|
|
76
79
|
/**
|
|
77
|
-
* Inverter data structure
|
|
80
|
+
* Inverter data structure. Covers SunSpec int+SF inverter models 101/103 and float variants 111/112/113.
|
|
81
|
+
* The per-field offset comments below describe the int+SF (101/103) layout; float variants 111/112/113
|
|
82
|
+
* use 32-bit IEEE 754 float values at different offsets — see the float reader implementations.
|
|
78
83
|
*/
|
|
79
84
|
export interface SunspecInverterData extends SunspecBlock {
|
|
80
|
-
blockNumber: 103 | 101;
|
|
85
|
+
blockNumber: 103 | 101 | 113 | 112 | 111;
|
|
81
86
|
acCurrent?: number;
|
|
82
87
|
phaseACurrent?: number;
|
|
83
88
|
phaseBCurrent?: number;
|