@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.
@@ -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}, current=${stats.currentlyOpen})`);
152
+ `opens=${stats.opens}, closes=${stats.closes}, openUnits=${stats.openUnits})`);
153
153
  try {
154
- const success = await this.sunspecClient.reconnect();
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}, current=${postStats.currentlyOpen})`);
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.disconnect();
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}, current=${stats.currentlyOpen})`);
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
- // Disconnect the client since meter uses its own connection
1269
- await this.sunspecClient.disconnect();
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 based on Model 103
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;